switchroom 0.20.18 → 0.20.20

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.
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.20.18", COMMIT_SHA = "7b49b13d";
2123
+ var VERSION = "0.20.20", COMMIT_SHA = "f444ea4a";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -14046,6 +14046,12 @@ var init_schema = __esm(() => {
14046
14046
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
14047
14047
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
14048
14048
  }).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."),
14049
+ recall_pool: exports_external.object({
14050
+ enabled: exports_external.boolean().optional().describe("Turn on the recall/background WORKER SPLIT: a second container, " + "`switchroom-hindsight-recall`, serves recall/reflect only with N " + "uvicorn workers, while the existing `switchroom-hindsight` " + "container keeps pg0, the single background WorkerPoller, the " + "healthcheck and all consolidation/retain processing but moves off " + "the public port onto public+1. The public port (memory.config.url) " + "does not change \u2014 the pool binds it instead. Absent/false (the " + "default) \u2192 the single-container topology, unchanged. Opt-in only. " + "Raises recall throughput ~2.2x on the fleet (2.6\u21925.6 rps)."),
14051
+ workers: exports_external.number().int().positive().optional().describe("uvicorn `--workers` for the recall pool (HINDSIGHT_API_WORKERS). " + "Default 4. Each worker holds its own GPU cross-encoder reranker, " + "so past ~4 workers contend for VRAM rather than adding throughput " + "on a single GPU \u2014 raise only on a bigger GPU or a CPU reranker. " + "The per-worker DB pools are auto-sized down from the pg0 " + "connection budget so N*(write+read)+60 background stays under " + "max_connections=250; a worker count that would overflow that " + "budget is rejected at launch."),
14052
+ db_pool_max_size: exports_external.number().int().positive().optional().describe("Override the per-worker WRITE pool cap " + "(HINDSIGHT_API_DB_POOL_MAX_SIZE). Absent \u2192 auto-sized from the " + "worker count. Set this only to override the auto-sizing; the " + "connection-budget check still applies and rejects an overflow."),
14053
+ read_db_pool_max_size: exports_external.number().int().positive().optional().describe("Override the per-worker READ pool cap " + "(HINDSIGHT_API_READ_DB_POOL_MAX_SIZE). Absent \u2192 auto-sized from " + "the worker count. Set this only to override the auto-sizing; the " + "connection-budget check still applies and rejects an overflow.")
14054
+ }).optional().describe("Optional recall/background container split for the hindsight " + "singleton. Off by default; see `enabled`. Provisioned by " + "`switchroom memory setup` (src/setup/hindsight-recall-pool.ts) so it " + "survives `switchroom apply` \u2014 the hand-applied topology it replaces " + "did not."),
14049
14055
  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 \u2014 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 \u2014 a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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; and " + "HINDSIGHT_MM_REFRESH_MIN_INTERVAL_S \u2014 the rollback/tuning knob for " + "switchroom's MM-refresh-debounce image patch: the minimum seconds " + "between consolidation-triggered refreshes of one mental model; unset " + "means the image's baked 3600, 0 restores upstream's " + "refresh-every-round behaviour; explicit and cron-scheduled refreshes " + "are never debounced; and " + "HINDSIGHT_API_QUERY_ANALYZER_LANGUAGES \u2014 the language set dateparser is " + "restricted to during temporal query analysis (upstream #3154, which " + "superseded switchroom's own temporal-language image patch at v0.9.0); " + "comma-separated; switchroom EMITS `en` on every host because upstream's " + "unset default auto-detects across 200+ locales inline on the shared " + "asyncio loop (~165x slower per call), set e.g. `en,es` for i18n parsing; " + "and " + "HINDSIGHT_API_TEMPORAL_MAX_QUERY_CHARS \u2014 the max chars of query text " + "dateparser.search_dates() is handed during temporal analysis (0 = " + "unlimited), made live by switchroom's temporal-offload image patch " + "(which moved the synchronous extraction off the shared asyncio loop onto " + "a single-worker thread and bounds its input, ending multi-second loop " + "stalls on multi-KB consolidation queries); unset means the image's baked " + "2000, set 0 to restore an unbounded full scan); and " + "HINDSIGHT_API_TEXT_SEARCH_EXTENSION_NATIVE_LANGUAGE \u2014 the Postgres " + "text-search regconfig the native BM25 arms and the search_vector index " + "write run to_tsvector/to_tsquery against; unset means upstream's " + "always-safe `english`, set `hindsight_english` to use switchroom's " + "junk-stopping config (dict + stopword file provisioned durably by the " + "image entrypoint, recall degrading to semantic-only if it is ever " + "missing); and " + "HINDSIGHT_API_BM25_MAX_QUERY_TERMS \u2014 the max number of query tokens the " + "keyword (BM25) recall arm searches for; unset means upstream's 0 = " + "unbounded, which makes a multi-KB consolidation query a disjunction " + "over hundreds of terms; set e.g. 64 to cap it, at the cost of " + "silently dropping the tail of a long query; and " + "HINDSIGHT_API_EMBEDDINGS_PROVIDER with the four " + "HINDSIGHT_API_EMBEDDINGS_ONNX_{MODEL_ID,POOLING,QUERY_PREFIX," + "PASSAGE_PREFIX} keys \u2014 the ONNX-embeddings cutover: unset keeps " + "upstream's slower `local` (PyTorch) backend, set `onnx` plus the model " + "id/pooling to run the SAME model on ONNX-CPU at a fraction of the " + "latency; the two _PREFIX keys are the query/passage affixes and are " + "set to the empty string for bge parity (vectors are byte-identical " + "between the two backends, so no re-embedding is needed) \u2014 unusually " + "for this block an EMPTY value here is honoured, not dropped, because an " + "affix-free prefix is the intended value; and the v0.9.0 knobs " + "switchroom allowlists for reach but emits no value for \u2014 " + "HINDSIGHT_API_RERANKER_MAX_CANDIDATES_{LOW,MID,HIGH} (per-recall-budget " + "rerank caps; unset/0 falls back to the global " + "HINDSIGHT_API_RERANKER_MAX_CANDIDATES switchroom does pin), " + "HINDSIGHT_API_EMBEDDINGS_MAX_INPUT_TOKENS (embedding-input truncation " + "ceiling; unset means the model's own limit), " + "HINDSIGHT_API_ENTITY_TRGM_SIMILARITY_THRESHOLD (entity-resolution fuzzy " + "match floor; unset means upstream's 0.15) and " + "HINDSIGHT_API_ENABLE_{TEMPORAL_RETRIEVAL,GRAPH_RETRIEVAL,RERANKING} " + "(global per-stage kill switches, all default true \u2014 the emergency lever " + "if one retrieval stage is implicated in a latency incident, at the cost " + "of recall quality); 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 \u2014 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 \u2014 a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
14050
14056
  });
14051
14057
  MicrosoftWorkspaceConfigSchema = exports_external.object({
@@ -16200,7 +16206,7 @@ function hindsightPerfEnv(caps, overrides = new Map) {
16200
16206
  function findUnmanagedHindsightEnvKeys(configEnv) {
16201
16207
  return Object.keys(configEnv ?? {}).filter((key) => !HINDSIGHT_PERF_ENV_KEYS.has(key) && !HINDSIGHT_PG_ENV_KEYS.has(key)).sort();
16202
16208
  }
16203
- var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150, HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE, HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50, HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2, HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low", HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT, HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true", HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128, HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true", HINDSIGHT_DEFAULT_LLM_MAX_RETRIES = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250, HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6, HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS = 1, HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3, HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false", HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential", HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30, HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION = "pg_search", HINDSIGHT_DEFAULT_QUERY_ANALYZER_LANGUAGES = "en", HINDSIGHT_PERF_DEFAULTS_UNGATED, HINDSIGHT_PERF_DEFAULTS_GPU, HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM, HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS, HINDSIGHT_PERF_ALLOW_EMPTY_KEYS, HINDSIGHT_WORKER_SLOT_TYPES, HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES, HINDSIGHT_PERF_ENV_KEYS;
16209
+ var HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION = 150, HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE, HINDSIGHT_DEFAULT_LINK_EXPANSION_PER_ENTITY_LIMIT = 50, HINDSIGHT_DEFAULT_LINK_EXPANSION_TIMEOUT_S = 2, HINDSIGHT_DEFAULT_LLM_REASONING_EFFORT = "low", HINDSIGHT_DEFAULT_LLM_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RETAIN_LLM_MAX_CONCURRENT = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_MAX_CONCURRENT, HINDSIGHT_DEFAULT_RERANKER_LOCAL_FP16 = "true", HINDSIGHT_DEFAULT_RERANKER_LOCAL_BATCH_SIZE = 128, HINDSIGHT_DEFAULT_LLM_STRICT_SCHEMA = "true", HINDSIGHT_DEFAULT_LLM_MAX_RETRIES = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 250, HINDSIGHT_DEFAULT_CONSOLIDATION_SLOT_LIMIT = 6, HINDSIGHT_DEFAULT_CONSOLIDATION_RESERVED_SLOTS = 1, HINDSIGHT_DEFAULT_GRAPH_SEED_MIN_SIMILARITY = 0.3, HINDSIGHT_DEFAULT_LLM_SUPPORTS_MAX_ITEMS = "false", HINDSIGHT_DEFAULT_RECENCY_DECAY_FUNCTION = "exponential", HINDSIGHT_DEFAULT_RECENCY_DECAY_HALFLIFE_DAYS = 30, HINDSIGHT_DEFAULT_TEXT_SEARCH_EXTENSION = "pg_search", HINDSIGHT_DEFAULT_QUERY_ANALYZER_LANGUAGES = "en", HINDSIGHT_PERF_DEFAULTS_UNGATED, HINDSIGHT_PERF_DEFAULTS_GPU, HINDSIGHT_PERF_DEFAULTS_LOCAL_LLM, HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS, HINDSIGHT_PERF_ALLOW_EMPTY_KEYS, HINDSIGHT_WORKER_SLOT_TYPES, HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES, HINDSIGHT_RECALL_POOL_ENV_KEYS, HINDSIGHT_PERF_ENV_KEYS;
16204
16210
  var init_hindsight_perf_defaults = __esm(() => {
16205
16211
  init_hindsight_pg_defaults();
16206
16212
  HINDSIGHT_DEFAULT_RECALL_MAX_CANDIDATES_PER_SOURCE = Math.ceil(HINDSIGHT_RERANKER_MAX_CANDIDATES_FOR_DERIVATION * 0.4);
@@ -16343,6 +16349,15 @@ var init_hindsight_perf_defaults = __esm(() => {
16343
16349
  `HINDSIGHT_API_WORKER_${type.toUpperCase()}_MAX_SLOTS`,
16344
16350
  `HINDSIGHT_API_WORKER_${type.toUpperCase()}_RESERVED_SLOTS`
16345
16351
  ]));
16352
+ HINDSIGHT_RECALL_POOL_ENV_KEYS = [
16353
+ "HINDSIGHT_API_WORKERS",
16354
+ "HINDSIGHT_API_WORKER_ENABLED",
16355
+ "HINDSIGHT_API_DATABASE_URL",
16356
+ "HINDSIGHT_API_DB_URL",
16357
+ "HINDSIGHT_API_DB_POOL_MAX_SIZE",
16358
+ "HINDSIGHT_API_READ_DB_POOL_MAX_SIZE",
16359
+ "HINDSIGHT_API_RUN_MIGRATIONS_ON_STARTUP"
16360
+ ];
16346
16361
  HINDSIGHT_PERF_ENV_KEYS = new Set([
16347
16362
  ...[
16348
16363
  ...HINDSIGHT_PERF_DEFAULTS_UNGATED,
@@ -16351,7 +16366,8 @@ var init_hindsight_perf_defaults = __esm(() => {
16351
16366
  ].map(([k]) => k),
16352
16367
  ...HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS,
16353
16368
  ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.keys(),
16354
- ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values()
16369
+ ...HINDSIGHT_WORKER_RESERVED_SLOT_ALIASES.values(),
16370
+ ...HINDSIGHT_RECALL_POOL_ENV_KEYS
16355
16371
  ]);
16356
16372
  });
16357
16373
 
@@ -16521,7 +16537,7 @@ function resolveCheckedHindsightContextBudget(llm) {
16521
16537
  assertHindsightContextBudgetFits(budget);
16522
16538
  return budget;
16523
16539
  }
16524
- var HINDSIGHT_CLAUDE_CONTEXT_WINDOW = 200000, HINDSIGHT_CONSERVATIVE_CONTEXT_WINDOW = 32768, HINDSIGHT_CONTEXT_SAFETY_FRACTION = 0.2, HINDSIGHT_CONSOLIDATION_PROMPT_OVERHEAD_TOKENS = 2270, HINDSIGHT_CONSOLIDATION_TOKENS_PER_FACT = 2500, HINDSIGHT_RETAIN_PROMPT_ESTIMATE_TOKENS = 8000, HINDSIGHT_CONSOLIDATION_BATCH_SIZE_CEILING = 12, HINDSIGHT_CONSOLIDATION_BATCH_SIZE_FLOOR = 1, HINDSIGHT_CONSOLIDATION_MAX_COMPLETION_CEILING = 16384, HINDSIGHT_RETAIN_MAX_COMPLETION_CEILING = 16384, HINDSIGHT_UPSTREAM_RETAIN_CHUNK_SIZE = 3000, HINDSIGHT_CONSOLIDATION_MAX_COMPLETION_FLOOR = 1024, HINDSIGHT_RETAIN_MAX_COMPLETION_FLOOR, HINDSIGHT_REFLECT_MAX_CONTEXT_FLOOR = 2048, HindsightContextBudgetError;
16540
+ var HINDSIGHT_CLAUDE_CONTEXT_WINDOW = 200000, HINDSIGHT_CONSERVATIVE_CONTEXT_WINDOW = 32768, HINDSIGHT_CONTEXT_SAFETY_FRACTION = 0.2, HINDSIGHT_CONSOLIDATION_PROMPT_OVERHEAD_TOKENS = 2270, HINDSIGHT_CONSOLIDATION_TOKENS_PER_FACT = 5000, HINDSIGHT_RETAIN_PROMPT_ESTIMATE_TOKENS = 8000, HINDSIGHT_CONSOLIDATION_BATCH_SIZE_CEILING = 12, HINDSIGHT_CONSOLIDATION_BATCH_SIZE_FLOOR = 1, HINDSIGHT_CONSOLIDATION_MAX_COMPLETION_CEILING = 16384, HINDSIGHT_RETAIN_MAX_COMPLETION_CEILING = 16384, HINDSIGHT_UPSTREAM_RETAIN_CHUNK_SIZE = 3000, HINDSIGHT_CONSOLIDATION_MAX_COMPLETION_FLOOR = 1024, HINDSIGHT_RETAIN_MAX_COMPLETION_FLOOR, HINDSIGHT_REFLECT_MAX_CONTEXT_FLOOR = 2048, HindsightContextBudgetError;
16525
16541
  var init_hindsight_context_budget = __esm(() => {
16526
16542
  HINDSIGHT_RETAIN_MAX_COMPLETION_FLOOR = HINDSIGHT_UPSTREAM_RETAIN_CHUNK_SIZE + 72;
16527
16543
  HindsightContextBudgetError = class HindsightContextBudgetError extends Error {
@@ -18727,20 +18743,16 @@ function probeDockerAvailability(probe = defaultDockerProbe) {
18727
18743
  function isDockerAvailable(probe = defaultDockerProbe) {
18728
18744
  return probeDockerAvailability(probe) === "ok";
18729
18745
  }
18746
+ function dockerNameMatchesExactly(probe, args, want) {
18747
+ const out = probe(args);
18748
+ return (out ?? "").split(`
18749
+ `).map((n) => n.trim()).some((n) => n === want);
18750
+ }
18730
18751
  function isHindsightRunning(probe = defaultDockerProbe) {
18731
- const out = probe(["ps", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"]);
18732
- return (out ?? "").trim().length > 0;
18752
+ return dockerNameMatchesExactly(probe, ["ps", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], HINDSIGHT_CONTAINER_NAME);
18733
18753
  }
18734
18754
  function isHindsightContainerExists(probe = defaultDockerProbe) {
18735
- const out = probe([
18736
- "ps",
18737
- "-a",
18738
- "--filter",
18739
- "name=switchroom-hindsight",
18740
- "--format",
18741
- "{{.Names}}"
18742
- ]);
18743
- return (out ?? "").trim().length > 0;
18755
+ return dockerNameMatchesExactly(probe, ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], HINDSIGHT_CONTAINER_NAME);
18744
18756
  }
18745
18757
  function resolveHindsightPerOpLlm(llm) {
18746
18758
  const out = [];
@@ -19273,11 +19285,28 @@ function hindsightResolvedCapabilities(llm, litellm, gpu, perf) {
19273
19285
  localLlm: hindsightLocalLlmEnabled(llm, litellm, perf?.localLlm)
19274
19286
  };
19275
19287
  }
19276
- function getHindsightStatus() {
19288
+ function getHindsightStatus(exec = (cmd, args) => execFileSync2(cmd, args, { stdio: "pipe", encoding: "utf-8" })) {
19277
19289
  try {
19278
- const output = execFileSync2("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
19279
- const status = output.trim();
19280
- return status.length > 0 ? status : null;
19290
+ const output = exec("docker", [
19291
+ "ps",
19292
+ "-a",
19293
+ "--filter",
19294
+ "name=switchroom-hindsight",
19295
+ "--format",
19296
+ "{{.Names}}\t{{.Status}}"
19297
+ ]);
19298
+ for (const line of output.split(`
19299
+ `)) {
19300
+ const tab = line.indexOf("\t");
19301
+ if (tab === -1)
19302
+ continue;
19303
+ if (line.slice(0, tab).trim() !== HINDSIGHT_CONTAINER_NAME)
19304
+ continue;
19305
+ const status = line.slice(tab + 1).trim();
19306
+ if (status.length > 0)
19307
+ return status;
19308
+ }
19309
+ return null;
19281
19310
  } catch {
19282
19311
  return null;
19283
19312
  }
@@ -19439,7 +19468,7 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
19439
19468
  atomicWriteFileSync3(configPath, tail, mode);
19440
19469
  return { added: true, reason: "added" };
19441
19470
  }
19442
- var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_WORKER_ID = "switchroom-hindsight", HINDSIGHT_DATA_VOLUME = "switchroom-hindsight-data", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_DEFAULT_REQUEUE_DEAD_LETTERS = "1", HINDSIGHT_DEFAULT_REQUEUE_MAX = "25", HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_RETAIN_CHUNK_SIZE = 3000, HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384, HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6, HINDSIGHT_RETAIN_CLIENT_DEADLINE_S, HINDSIGHT_DEFAULT_MEM_LIMIT = "16g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, DOCKER_PROBE_TIMEOUT_MS, defaultDockerProbe = (args) => {
19471
+ var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_WORKER_ID = "switchroom-hindsight", HINDSIGHT_CONTAINER_NAME = "switchroom-hindsight", HINDSIGHT_DATA_VOLUME = "switchroom-hindsight-data", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_DEFAULT_REQUEUE_DEAD_LETTERS = "1", HINDSIGHT_DEFAULT_REQUEUE_MAX = "25", HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_RETAIN_CHUNK_SIZE = 3000, HINDSIGHT_DEFAULT_RETAIN_MAX_COMPLETION_TOKENS = 16384, HINDSIGHT_RETAIN_MIN_TOKENS_PER_SECOND = 80.6, HINDSIGHT_RETAIN_CLIENT_DEADLINE_S, HINDSIGHT_DEFAULT_MEM_LIMIT = "16g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, DOCKER_PROBE_TIMEOUT_MS, defaultDockerProbe = (args) => {
19443
19472
  try {
19444
19473
  return execFileSync2("docker", args, {
19445
19474
  stdio: "pipe",
@@ -32805,6 +32834,9 @@ var init_scaffold = __esm(() => {
32805
32834
  "mcp__hindsight__delete_document",
32806
32835
  "mcp__hindsight__get_mental_model",
32807
32836
  "mcp__hindsight__list_mental_models",
32837
+ "mcp__hindsight__search_knowledge_pages",
32838
+ "mcp__hindsight__get_knowledge_page",
32839
+ "mcp__hindsight__get_knowledge_tree",
32808
32840
  "mcp__hindsight__list_tags",
32809
32841
  "mcp__hindsight__get_operation",
32810
32842
  "mcp__hindsight__list_operations",
@@ -47618,7 +47650,7 @@ function buildRepairBankArgv(opts) {
47618
47650
  const script = `if command -v hindsight-admin >/dev/null 2>&1; then exec hindsight-admin "$@"; fi; ` + `exec ${HINDSIGHT_ADMIN_BIN} "$@"`;
47619
47651
  return [
47620
47652
  "exec",
47621
- opts.container ?? HINDSIGHT_CONTAINER_NAME,
47653
+ opts.container ?? HINDSIGHT_CONTAINER_NAME2,
47622
47654
  "sh",
47623
47655
  "-c",
47624
47656
  script,
@@ -47731,7 +47763,7 @@ function classifyRepairOutcome(run) {
47731
47763
  detail: run.dryRun ? `Coverage complete: ${run.summary.alreadyPresent} index(es) present across ` + `${run.summary.banksScanned} bank(s), nothing to create.` : `${run.summary.created} created, ${run.summary.alreadyPresent} already present ` + `across ${run.summary.banksScanned} bank(s). Re-run \`switchroom doctor\` to confirm.`
47732
47764
  };
47733
47765
  }
47734
- var HINDSIGHT_CONTAINER_NAME = "switchroom-hindsight", HINDSIGHT_ADMIN_BIN = "/app/api/.venv/bin/hindsight-admin", REPAIR_FAILED_EXIT = 1, REPAIR_COVERAGE_MISSING_EXIT = 3, REPAIR_UNVERIFIED_EXIT = 4;
47766
+ var HINDSIGHT_CONTAINER_NAME2 = "switchroom-hindsight", HINDSIGHT_ADMIN_BIN = "/app/api/.venv/bin/hindsight-admin", REPAIR_FAILED_EXIT = 1, REPAIR_COVERAGE_MISSING_EXIT = 3, REPAIR_UNVERIFIED_EXIT = 4;
47735
47767
  var init_hindsight_repair = __esm(() => {
47736
47768
  init_hindsight_tools();
47737
47769
  });
@@ -83069,7 +83101,7 @@ __export(exports_server2, {
83069
83101
  TOOLS: () => TOOLS2
83070
83102
  });
83071
83103
  import { randomBytes as randomBytes18 } from "node:crypto";
83072
- import { existsSync as existsSync118 } from "node:fs";
83104
+ import { existsSync as existsSync119 } from "node:fs";
83073
83105
  function selfSocketPath() {
83074
83106
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
83075
83107
  }
@@ -83096,7 +83128,7 @@ async function dispatchTool2(name, args) {
83096
83128
  return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
83097
83129
  }
83098
83130
  const sockPath = selfSocketPath();
83099
- if (!existsSync118(sockPath)) {
83131
+ if (!existsSync119(sockPath)) {
83100
83132
  return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
83101
83133
  }
83102
83134
  let req;
@@ -83347,13 +83379,13 @@ function resolveAuditLogPath() {
83347
83379
  if (process.env.HOSTD_AUDIT_LOG_PATH)
83348
83380
  return process.env.HOSTD_AUDIT_LOG_PATH;
83349
83381
  const bindMounted = "/host-home/.switchroom/host-control-audit.log";
83350
- if (existsSync118(bindMounted))
83382
+ if (existsSync119(bindMounted))
83351
83383
  return bindMounted;
83352
83384
  return defaultAuditLogPath2();
83353
83385
  }
83354
83386
  function getLastUpdateApplyStatus() {
83355
83387
  const path10 = resolveAuditLogPath();
83356
- if (!existsSync118(path10)) {
83388
+ if (!existsSync119(path10)) {
83357
83389
  return errorText2(`get_status: audit log not found at ${path10}. No update_apply has run yet?`);
83358
83390
  }
83359
83391
  let raw;
@@ -83907,6 +83939,18 @@ var init_mapping = __esm(() => {
83907
83939
  job_spec: "keep-my-subscription-honest",
83908
83940
  signature: "litellm-header-passthrough:oauth-leak-scope"
83909
83941
  },
83942
+ "litellm-callback-mount-missing": {
83943
+ failure_mode: "constraint-violation",
83944
+ severity: 3,
83945
+ job_spec: "fleet-stays-healthy",
83946
+ signature: "litellm-callback-mount:missing-bind"
83947
+ },
83948
+ "litellm-passthrough-mount-stale": {
83949
+ failure_mode: "constraint-violation",
83950
+ severity: 3,
83951
+ job_spec: "fleet-stays-healthy",
83952
+ signature: "litellm-passthrough-mount:stale-python-version"
83953
+ },
83910
83954
  "litellm-timeout-budget-drift": {
83911
83955
  failure_mode: "drift",
83912
83956
  severity: 3,
@@ -84109,11 +84153,11 @@ var init_test_agents = __esm(() => {
84109
84153
  });
84110
84154
 
84111
84155
  // src/litellm/header-passthrough-guard.ts
84112
- import { existsSync as existsSync121, readdirSync as readdirSync46 } from "node:fs";
84156
+ import { existsSync as existsSync122, readdirSync as readdirSync46 } from "node:fs";
84113
84157
  function discoverLiveLitellmConfigPath(opts) {
84114
84158
  const servicesDir = opts?.servicesDir ?? COOLIFY_SERVICES_DIR;
84115
84159
  const readdir2 = opts?.readdirFn ?? ((p) => readdirSync46(p));
84116
- const exists = opts?.existsFn ?? existsSync121;
84160
+ const exists = opts?.existsFn ?? existsSync122;
84117
84161
  let entries;
84118
84162
  try {
84119
84163
  entries = readdir2(servicesDir);
@@ -84202,8 +84246,159 @@ var init_header_passthrough_guard = __esm(() => {
84202
84246
  ]);
84203
84247
  });
84204
84248
 
84249
+ // src/litellm/callback-mount-guard.ts
84250
+ function canReadComposeMounts(composeText) {
84251
+ if (composeText == null)
84252
+ return false;
84253
+ return extractComposeBindTargets(composeText) !== null;
84254
+ }
84255
+ function extractCustomCallbackModules(parsed) {
84256
+ const settings = parsed?.litellm_settings;
84257
+ if (settings == null || typeof settings !== "object")
84258
+ return [];
84259
+ const out = [];
84260
+ const seen = new Set;
84261
+ for (const key of CALLBACK_KEYS) {
84262
+ const raw = settings[key];
84263
+ if (!Array.isArray(raw))
84264
+ continue;
84265
+ for (const entry of raw) {
84266
+ if (typeof entry !== "string")
84267
+ continue;
84268
+ const reference = entry.trim();
84269
+ const parts = reference.split(".");
84270
+ if (parts.length !== 2)
84271
+ continue;
84272
+ const [module, attr] = parts;
84273
+ if (!module || !attr)
84274
+ continue;
84275
+ if (seen.has(module))
84276
+ continue;
84277
+ seen.add(module);
84278
+ out.push({ module, reference });
84279
+ }
84280
+ }
84281
+ return out;
84282
+ }
84283
+ function extractComposeBindTargets(composeText) {
84284
+ const targets = new Set;
84285
+ let doc2;
84286
+ try {
84287
+ doc2 = import_yaml28.parse(composeText);
84288
+ } catch {
84289
+ return null;
84290
+ }
84291
+ const services = doc2?.services;
84292
+ if (services == null || typeof services !== "object")
84293
+ return null;
84294
+ for (const svc of Object.values(services)) {
84295
+ const volumes = svc?.volumes;
84296
+ if (!Array.isArray(volumes))
84297
+ continue;
84298
+ for (const v of volumes) {
84299
+ if (typeof v === "string") {
84300
+ const parts = v.split(":");
84301
+ if (parts.length >= 2 && parts[1])
84302
+ targets.add(parts[1].trim());
84303
+ } else if (v != null && typeof v === "object") {
84304
+ const t = v.target;
84305
+ if (typeof t === "string" && t)
84306
+ targets.add(t.trim());
84307
+ }
84308
+ }
84309
+ }
84310
+ return targets;
84311
+ }
84312
+ function detectMissingCallbackMounts(parsedConfig, composeText) {
84313
+ const modules = extractCustomCallbackModules(parsedConfig);
84314
+ if (modules.length === 0 || composeText == null)
84315
+ return [];
84316
+ const targets = extractComposeBindTargets(composeText);
84317
+ if (targets === null)
84318
+ return [];
84319
+ const violations = [];
84320
+ for (const { module, reference } of modules) {
84321
+ const expectedTarget = `${CONTAINER_APP_DIR}/${module}.py`;
84322
+ if (targets.has(expectedTarget))
84323
+ continue;
84324
+ violations.push({
84325
+ module,
84326
+ reference,
84327
+ expectedTarget,
84328
+ detail: `litellm_settings names custom callback '${reference}' but no bind mount targets ` + `${expectedTarget} \u2014 the proxy will abort startup with ` + `ModuleNotFoundError: No module named '${module}'. Coolify regenerates this compose ` + `from services.docker_compose_raw in its database, so the mount must be restored THERE ` + `(a hand edit to the generated file is wiped on the next deploy).`
84329
+ });
84330
+ }
84331
+ return violations;
84332
+ }
84333
+ var import_yaml28, CONTAINER_APP_DIR = "/app", LIVE_COMPOSE_BASENAME = "docker-compose.yml", CALLBACK_KEYS;
84334
+ var init_callback_mount_guard = __esm(() => {
84335
+ import_yaml28 = __toESM(require_dist(), 1);
84336
+ CALLBACK_KEYS = ["callbacks", "success_callback", "failure_callback"];
84337
+ });
84338
+
84339
+ // src/litellm/passthrough-mount-guard.ts
84340
+ function extractVersionedSitePackagesMounts(composeText) {
84341
+ const targets = extractComposeBindTargets(composeText);
84342
+ if (targets === null)
84343
+ return null;
84344
+ const out = [];
84345
+ for (const target of targets) {
84346
+ const m = VERSIONED_SITE_PACKAGES_RE.exec(target);
84347
+ if (m)
84348
+ out.push({ target, declaredPythonVersion: m[1] });
84349
+ }
84350
+ return out;
84351
+ }
84352
+ function detectStalePassthroughMounts(composeText, actualPythonVersion) {
84353
+ if (composeText == null || actualPythonVersion == null)
84354
+ return [];
84355
+ const mounts = extractVersionedSitePackagesMounts(composeText);
84356
+ if (mounts === null)
84357
+ return [];
84358
+ const violations = [];
84359
+ for (const { target, declaredPythonVersion } of mounts) {
84360
+ if (declaredPythonVersion === actualPythonVersion)
84361
+ continue;
84362
+ violations.push({
84363
+ target,
84364
+ declaredPythonVersion,
84365
+ actualPythonVersion,
84366
+ detail: `bind mount targets ${target}, hard-coding CPython ${declaredPythonVersion}, but the live ` + `litellm image ships CPython ${actualPythonVersion} \u2014 its site-packages live under ` + `python${actualPythonVersion}/, so this mount lands at an inert path and the patch is ` + `SILENTLY dropped (no crash, no log; the proxy comes up "healthy" on unpatched code). ` + `Update the mount target to python${actualPythonVersion}/ in services.docker_compose_raw ` + `(a hand edit to the generated compose is wiped on the next Coolify deploy), then redeploy.`
84367
+ });
84368
+ }
84369
+ return violations;
84370
+ }
84371
+ var VERSIONED_SITE_PACKAGES_RE;
84372
+ var init_passthrough_mount_guard = __esm(() => {
84373
+ init_callback_mount_guard();
84374
+ VERSIONED_SITE_PACKAGES_RE = /\/python(\d+\.\d+)\/site-packages\//;
84375
+ });
84376
+
84205
84377
  // src/fleet-health/litellm-config-sensor.ts
84206
- import { readFileSync as readFileSync110, existsSync as existsSync122 } from "node:fs";
84378
+ import { execFileSync as execFileSync35 } from "node:child_process";
84379
+ import { readFileSync as readFileSync110, existsSync as existsSync123 } from "node:fs";
84380
+ import { dirname as dirname50, join as join124 } from "node:path";
84381
+ function resolveLiveLitellmPythonVersion() {
84382
+ try {
84383
+ const ps = execFileSync35("docker", ["ps", "--format", "{{.Names}}\t{{.Image}}"], {
84384
+ encoding: "utf-8",
84385
+ timeout: 5000,
84386
+ stdio: ["ignore", "pipe", "ignore"]
84387
+ });
84388
+ const line = ps.split(`
84389
+ `).find((l) => /litellm-database|(^|\s)litellm/i.test(l) && l.trim().length > 0);
84390
+ if (!line)
84391
+ return null;
84392
+ const name = line.split("\t")[0]?.trim();
84393
+ if (!name)
84394
+ return null;
84395
+ const lsOut = execFileSync35("docker", ["exec", name, "sh", "-c", "ls -d /app/.venv/lib/python*/ 2>/dev/null"], { encoding: "utf-8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
84396
+ const m = lsOut.match(/python(\d+\.\d+)/);
84397
+ return m ? m[1] : null;
84398
+ } catch {
84399
+ return null;
84400
+ }
84401
+ }
84207
84402
  function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitellmConfigPath()) {
84208
84403
  if (explicit)
84209
84404
  return explicit;
@@ -84214,7 +84409,7 @@ function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitel
84214
84409
  }
84215
84410
  function scanLitellmConfig(opts = {}) {
84216
84411
  const path10 = resolveLitellmConfigPath(opts.path, opts.discoverFn);
84217
- const exists = opts.existsFn ?? existsSync122;
84412
+ const exists = opts.existsFn ?? existsSync123;
84218
84413
  const read = opts.readFn ?? ((p) => readFileSync110(p, "utf-8"));
84219
84414
  const log = opts.log ?? (() => {});
84220
84415
  const nowIso = opts.nowIso ?? new Date().toISOString();
@@ -84266,6 +84461,52 @@ function scanLitellmConfig(opts = {}) {
84266
84461
  ts: nowIso
84267
84462
  });
84268
84463
  }
84464
+ const composePath = join124(dirname50(path10), LIVE_COMPOSE_BASENAME);
84465
+ let composeText = null;
84466
+ if (exists(composePath)) {
84467
+ try {
84468
+ composeText = read(composePath);
84469
+ } catch (e) {
84470
+ log(`fleet-health: litellm-config sensor \u2014 callback-mount check SKIPPED, ` + `${composePath} unreadable: ${String(e)}`);
84471
+ }
84472
+ } else {
84473
+ log(`fleet-health: litellm-config sensor \u2014 callback-mount check SKIPPED, ` + `no ${LIVE_COMPOSE_BASENAME} beside ${path10}`);
84474
+ }
84475
+ const missingMounts = detectMissingCallbackMounts(parsed, composeText);
84476
+ if (composeText !== null && !canReadComposeMounts(composeText)) {
84477
+ log(`fleet-health: litellm-config sensor \u2014 callback-mount check SKIPPED, ` + `${composePath} has no readable services/volumes mapping`);
84478
+ } else if (composeText !== null && missingMounts.length === 0) {
84479
+ log(`fleet-health: litellm-config sensor OK \u2014 every custom callback module is ` + `bind-mounted by ${composePath}`);
84480
+ }
84481
+ for (const m of missingMounts) {
84482
+ findings.push({
84483
+ signal: "litellm-callback-mount-missing",
84484
+ agent: LITELLM_PROXY_PSEUDO_AGENT,
84485
+ turn_id: `litellm-callback-mount:${m.module}`,
84486
+ log_pointer: `${composePath}: ${m.detail}`,
84487
+ ts: nowIso
84488
+ });
84489
+ }
84490
+ const versionedMounts = composeText !== null ? extractVersionedSitePackagesMounts(composeText) : null;
84491
+ const hasVersionedMounts = versionedMounts !== null && versionedMounts.length > 0;
84492
+ const pythonVersionFn = opts.pythonVersionFn ?? resolveLiveLitellmPythonVersion;
84493
+ const actualPythonVersion = hasVersionedMounts ? pythonVersionFn() : null;
84494
+ if (hasVersionedMounts && actualPythonVersion === null) {
84495
+ log(`fleet-health: litellm-config sensor \u2014 passthrough-mount check SKIPPED, ` + `could not resolve the live litellm image's CPython version (docker down, no ` + `container, or off-host CI/dev)`);
84496
+ }
84497
+ const staleMounts = detectStalePassthroughMounts(composeText, actualPythonVersion);
84498
+ if (hasVersionedMounts && actualPythonVersion !== null && staleMounts.length === 0) {
84499
+ log(`fleet-health: litellm-config sensor OK \u2014 every versioned site-packages shadow ` + `mount in ${composePath} matches the live image's CPython ${actualPythonVersion}`);
84500
+ }
84501
+ for (const s of staleMounts) {
84502
+ findings.push({
84503
+ signal: "litellm-passthrough-mount-stale",
84504
+ agent: LITELLM_PROXY_PSEUDO_AGENT,
84505
+ turn_id: `litellm-passthrough-mount:${s.declaredPythonVersion}->${s.actualPythonVersion}`,
84506
+ log_pointer: `${composePath}: ${s.detail}`,
84507
+ ts: nowIso
84508
+ });
84509
+ }
84269
84510
  if (findings.length === 0)
84270
84511
  return { status: "ok", path: path10, findings: [] };
84271
84512
  for (const f of findings) {
@@ -84277,6 +84518,8 @@ var LITELLM_PROXY_PSEUDO_AGENT = "litellm-proxy";
84277
84518
  var init_litellm_config_sensor = __esm(() => {
84278
84519
  init_header_passthrough_guard();
84279
84520
  init_timeout_budget();
84521
+ init_callback_mount_guard();
84522
+ init_passthrough_mount_guard();
84280
84523
  });
84281
84524
 
84282
84525
  // src/fleet-health/hindsight-gpu-drift-sensor.ts
@@ -84323,18 +84566,18 @@ __export(exports_scan, {
84323
84566
  import {
84324
84567
  readFileSync as readFileSync111,
84325
84568
  readdirSync as readdirSync47,
84326
- existsSync as existsSync123,
84569
+ existsSync as existsSync124,
84327
84570
  mkdirSync as mkdirSync70,
84328
84571
  writeFileSync as writeFileSync51
84329
84572
  } from "node:fs";
84330
- import { resolve as resolve64, dirname as dirname50 } from "node:path";
84573
+ import { resolve as resolve64, dirname as dirname51 } from "node:path";
84331
84574
  import { homedir as homedir67 } from "node:os";
84332
84575
  function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir67()) {
84333
84576
  return resolve64(home2, ".switchroom");
84334
84577
  }
84335
84578
  function listAgents(base) {
84336
84579
  const dir = resolve64(base, "agents");
84337
- if (!existsSync123(dir))
84580
+ if (!existsSync124(dir))
84338
84581
  return [];
84339
84582
  try {
84340
84583
  return readdirSync47(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
@@ -84363,7 +84606,7 @@ function runScan(opts = {}) {
84363
84606
  let gwText = "";
84364
84607
  let sawArtifact = false;
84365
84608
  try {
84366
- if (existsSync123(turnsPath)) {
84609
+ if (existsSync124(turnsPath)) {
84367
84610
  turnsText = readFileSync111(turnsPath, "utf-8");
84368
84611
  sawArtifact = true;
84369
84612
  }
@@ -84371,7 +84614,7 @@ function runScan(opts = {}) {
84371
84614
  log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
84372
84615
  }
84373
84616
  try {
84374
- if (existsSync123(gwPath)) {
84617
+ if (existsSync124(gwPath)) {
84375
84618
  gwText = readFileSync111(gwPath, "utf-8");
84376
84619
  sawArtifact = true;
84377
84620
  }
@@ -84416,7 +84659,7 @@ function runScan(opts = {}) {
84416
84659
  function readLedgerIfPresent(base) {
84417
84660
  const path10 = ledgerPathForBase(base);
84418
84661
  try {
84419
- if (!existsSync123(path10))
84662
+ if (!existsSync124(path10))
84420
84663
  return null;
84421
84664
  return JSON.parse(readFileSync111(path10, "utf-8"));
84422
84665
  } catch {
@@ -84424,11 +84667,11 @@ function readLedgerIfPresent(base) {
84424
84667
  }
84425
84668
  }
84426
84669
  function ledgerPathForBase(base) {
84427
- return fleetHealthLedgerPath(dirname50(base));
84670
+ return fleetHealthLedgerPath(dirname51(base));
84428
84671
  }
84429
84672
  function writeLedger(base, ledger) {
84430
84673
  const path10 = ledgerPathForBase(base);
84431
- mkdirSync70(dirname50(path10), { recursive: true });
84674
+ mkdirSync70(dirname51(path10), { recursive: true });
84432
84675
  writeFileSync51(path10, JSON.stringify(ledger, null, 2) + `
84433
84676
  `, "utf-8");
84434
84677
  return path10;
@@ -106739,6 +106982,323 @@ function redactMemoryWriteBody(body) {
106739
106982
  init_helpers();
106740
106983
  init_hindsight();
106741
106984
 
106985
+ // src/setup/hindsight-recall-pool.ts
106986
+ init_hindsight();
106987
+ init_hindsight_perf_defaults();
106988
+ import { execFileSync as execFileSync26 } from "node:child_process";
106989
+ var HINDSIGHT_RECALL_POOL_CONTAINER = "switchroom-hindsight-recall";
106990
+ var HINDSIGHT_RECALL_POOL_WORKER_ID = "switchroom-hindsight-recall";
106991
+ var HINDSIGHT_PG_MAX_CONNECTIONS = 250;
106992
+ var HINDSIGHT_BACKGROUND_DB_POOL_MAX_SIZE = 40;
106993
+ var HINDSIGHT_BACKGROUND_READ_DB_POOL_MAX_SIZE = 20;
106994
+ var HINDSIGHT_BACKGROUND_CONNECTION_BUDGET = HINDSIGHT_BACKGROUND_DB_POOL_MAX_SIZE + HINDSIGHT_BACKGROUND_READ_DB_POOL_MAX_SIZE;
106995
+ var HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS = 3;
106996
+ var HINDSIGHT_RECALL_POOL_HEADROOM = 10;
106997
+ if (HINDSIGHT_RECALL_POOL_HEADROOM < HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS) {
106998
+ throw new Error(`HINDSIGHT_RECALL_POOL_HEADROOM (${HINDSIGHT_RECALL_POOL_HEADROOM}) must be \u2265 ` + `HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS ` + `(${HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS}).`);
106999
+ }
107000
+ var HINDSIGHT_RECALL_POOL_MAX_TOTAL_CONNECTIONS = HINDSIGHT_PG_MAX_CONNECTIONS - HINDSIGHT_RECALL_POOL_HEADROOM;
107001
+ var HINDSIGHT_RECALL_POOL_CONNECTION_BUDGET = HINDSIGHT_RECALL_POOL_MAX_TOTAL_CONNECTIONS - HINDSIGHT_BACKGROUND_CONNECTION_BUDGET;
107002
+ var HINDSIGHT_RECALL_POOL_DEFAULT_WORKERS = 4;
107003
+ var HINDSIGHT_RECALL_POOL_PG_DSN = "postgresql://hindsight:hindsight@127.0.0.1:5432/hindsight";
107004
+ function hindsightBackgroundApiPort(publicApiPort) {
107005
+ return publicApiPort + 1;
107006
+ }
107007
+ function resolveHindsightLaunchPorts(input) {
107008
+ const publicApiPort = input.configUrlPort ?? input.reuseApiPort ?? input.defaultApiPort;
107009
+ const apiPort = input.recallEnabled ? hindsightBackgroundApiPort(publicApiPort) : publicApiPort;
107010
+ return { publicApiPort, basePorts: { apiPort, uiPort: input.uiPort } };
107011
+ }
107012
+ function publicPortIsUnserved(input) {
107013
+ if (input.configUrlPort == null || input.authorityApiPort == null)
107014
+ return false;
107015
+ if (input.poolRunning)
107016
+ return false;
107017
+ return input.authorityApiPort !== input.configUrlPort;
107018
+ }
107019
+ function planStaleRecallPoolConvergence(input) {
107020
+ if (input.recallEnabled)
107021
+ return { action: "none" };
107022
+ if (!input.poolRunning)
107023
+ return { action: "none" };
107024
+ if (input.configUrlPort == null || input.authorityApiPort == null) {
107025
+ return { action: "none" };
107026
+ }
107027
+ if (input.authorityApiPort === input.configUrlPort)
107028
+ return { action: "stop-pool" };
107029
+ return { action: "relaunch-single-on-public", publicApiPort: input.configUrlPort };
107030
+ }
107031
+ function recallPoolDbPoolSizing(workers) {
107032
+ const perWorker = Math.floor(HINDSIGHT_RECALL_POOL_CONNECTION_BUDGET / workers);
107033
+ const dbPoolMaxSize = Math.max(1, Math.floor(perWorker * 0.4));
107034
+ const readDbPoolMaxSize = Math.max(1, perWorker - dbPoolMaxSize);
107035
+ return { dbPoolMaxSize, readDbPoolMaxSize };
107036
+ }
107037
+ function assertRecallPoolConnectionBudget(cfg) {
107038
+ const poolDemand = cfg.workers * (cfg.dbPoolMaxSize + cfg.readDbPoolMaxSize);
107039
+ const total = poolDemand + HINDSIGHT_BACKGROUND_CONNECTION_BUDGET;
107040
+ if (total > HINDSIGHT_RECALL_POOL_MAX_TOTAL_CONNECTIONS) {
107041
+ throw new Error(`hindsight.recall_pool connection budget overflow: ${cfg.workers} workers \u00d7 ` + `(${cfg.dbPoolMaxSize} write + ${cfg.readDbPoolMaxSize} read) = ${poolDemand}, ` + `plus ${HINDSIGHT_BACKGROUND_CONNECTION_BUDGET} background = ${total} > the ` + `${HINDSIGHT_RECALL_POOL_MAX_TOTAL_CONNECTIONS}-connection ceiling ` + `(pg0 max_connections=${HINDSIGHT_PG_MAX_CONNECTIONS} \u2212 ` + `${HINDSIGHT_RECALL_POOL_HEADROOM} headroom, which reserves PG's ` + `${HINDSIGHT_PG_SUPERUSER_RESERVED_CONNECTIONS} superuser slots plus ` + `maintenance). Lower hindsight.recall_pool.workers or its ` + `db_pool_max_size/read_db_pool_max_size.`);
107042
+ }
107043
+ }
107044
+ function resolveRecallPoolConfig(input) {
107045
+ if (!input || input.enabled !== true)
107046
+ return null;
107047
+ const workers = input.workers && input.workers > 0 ? Math.floor(input.workers) : HINDSIGHT_RECALL_POOL_DEFAULT_WORKERS;
107048
+ const autoSized = recallPoolDbPoolSizing(workers);
107049
+ const cfg = {
107050
+ workers,
107051
+ dbPoolMaxSize: input.db_pool_max_size && input.db_pool_max_size > 0 ? Math.floor(input.db_pool_max_size) : autoSized.dbPoolMaxSize,
107052
+ readDbPoolMaxSize: input.read_db_pool_max_size && input.read_db_pool_max_size > 0 ? Math.floor(input.read_db_pool_max_size) : autoSized.readDbPoolMaxSize
107053
+ };
107054
+ assertRecallPoolConnectionBudget(cfg);
107055
+ return cfg;
107056
+ }
107057
+ function backgroundContainerPoolEnv() {
107058
+ return {
107059
+ HINDSIGHT_API_DB_POOL_MAX_SIZE: String(HINDSIGHT_BACKGROUND_DB_POOL_MAX_SIZE),
107060
+ HINDSIGHT_API_READ_DB_POOL_MAX_SIZE: String(HINDSIGHT_BACKGROUND_READ_DB_POOL_MAX_SIZE)
107061
+ };
107062
+ }
107063
+ var HINDSIGHT_SPLIT_MANAGED_DB_POOL_KEYS = [
107064
+ "HINDSIGHT_API_DB_POOL_MAX_SIZE",
107065
+ "HINDSIGHT_API_READ_DB_POOL_MAX_SIZE"
107066
+ ];
107067
+ function splitDbPoolEnvConflicts(operatorEnv, cfg) {
107068
+ if (!operatorEnv)
107069
+ return [];
107070
+ const authority = backgroundContainerPoolEnv();
107071
+ const pool = {
107072
+ HINDSIGHT_API_DB_POOL_MAX_SIZE: String(cfg.dbPoolMaxSize),
107073
+ HINDSIGHT_API_READ_DB_POOL_MAX_SIZE: String(cfg.readDbPoolMaxSize)
107074
+ };
107075
+ const out = [];
107076
+ for (const key of HINDSIGHT_SPLIT_MANAGED_DB_POOL_KEYS) {
107077
+ const operatorValue = operatorEnv[key];
107078
+ if (operatorValue === undefined)
107079
+ continue;
107080
+ out.push({
107081
+ key,
107082
+ operatorValue: String(operatorValue),
107083
+ authorityValue: authority[key],
107084
+ poolValue: pool[key]
107085
+ });
107086
+ }
107087
+ return out;
107088
+ }
107089
+ function splitDbPoolEnvConflictWarning(c) {
107090
+ const knob = c.key === "HINDSIGHT_API_DB_POOL_MAX_SIZE" ? "hindsight.recall_pool.db_pool_max_size" : "hindsight.recall_pool.read_db_pool_max_size";
107091
+ return `hindsight.env.${c.key}=${c.operatorValue} is IGNORED while ` + `hindsight.recall_pool.enabled is true: the split computes this cap for both ` + `containers (authority ${c.authorityValue}, recall pool ${c.poolValue}) so the ` + `pg0 connection budget stays provable. Set \`${knob}\` instead \u2014 it is budget-checked \u2014 ` + `or set hindsight.recall_pool.enabled: false to have hindsight.env win again.`;
107092
+ }
107093
+ function splitContainerPerfEnv(operatorEnv) {
107094
+ return { ...operatorEnv ?? {}, ...backgroundContainerPoolEnv() };
107095
+ }
107096
+ function recallPoolEnvOverrides(cfg, poolPort) {
107097
+ return [
107098
+ ["HINDSIGHT_API_HOST", HINDSIGHT_HOST_NETWORK_BIND_ADDR],
107099
+ ["HINDSIGHT_API_PORT", String(poolPort)],
107100
+ ["HINDSIGHT_API_DATABASE_URL", HINDSIGHT_RECALL_POOL_PG_DSN],
107101
+ ["HINDSIGHT_API_DB_URL", HINDSIGHT_RECALL_POOL_PG_DSN],
107102
+ ["HINDSIGHT_API_WORKERS", String(cfg.workers)],
107103
+ ["HINDSIGHT_API_WORKER_ENABLED", "false"],
107104
+ ["HINDSIGHT_API_RUN_MIGRATIONS_ON_STARTUP", "false"],
107105
+ ["HINDSIGHT_ENABLE_CP", "false"],
107106
+ ["HINDSIGHT_API_WORKER_ID", HINDSIGHT_RECALL_POOL_WORKER_ID],
107107
+ ["HINDSIGHT_API_DB_POOL_MAX_SIZE", String(cfg.dbPoolMaxSize)],
107108
+ ["HINDSIGHT_API_READ_DB_POOL_MAX_SIZE", String(cfg.readDbPoolMaxSize)]
107109
+ ];
107110
+ }
107111
+ function applyRecallPoolEnvOverrides(base, overrides) {
107112
+ const overrideMap = new Map(overrides);
107113
+ const seen = new Set;
107114
+ const out = [];
107115
+ for (const [k, v] of base) {
107116
+ if (overrideMap.has(k)) {
107117
+ out.push([k, overrideMap.get(k)]);
107118
+ seen.add(k);
107119
+ } else {
107120
+ out.push([k, v]);
107121
+ }
107122
+ }
107123
+ for (const [k, v] of overrides) {
107124
+ if (!seen.has(k))
107125
+ out.push([k, v]);
107126
+ }
107127
+ return out;
107128
+ }
107129
+ function hindsightRecallPoolEnvPairs(opts) {
107130
+ const { cfg, poolPort, litellm, llm, gpu, perf, cpAccessKey } = opts;
107131
+ const base = hindsightContainerEnvPairs({
107132
+ apiPort: poolPort,
107133
+ litellm,
107134
+ llm,
107135
+ gpu,
107136
+ perf,
107137
+ cpAccessKey
107138
+ });
107139
+ return applyRecallPoolEnvOverrides(base, recallPoolEnvOverrides(cfg, poolPort));
107140
+ }
107141
+ function startHindsightRecallPool(opts) {
107142
+ const {
107143
+ cfg,
107144
+ poolPort,
107145
+ imageTag,
107146
+ litellm,
107147
+ llm,
107148
+ gpu,
107149
+ perf,
107150
+ cpAccessKey,
107151
+ mirrorDir,
107152
+ exec = (cmd, args2) => {
107153
+ execFileSync26(cmd, args2, { stdio: "pipe" });
107154
+ }
107155
+ } = opts;
107156
+ assertRecallPoolConnectionBudget(cfg);
107157
+ const envArgs = hindsightRecallPoolEnvPairs({
107158
+ cfg,
107159
+ poolPort,
107160
+ litellm,
107161
+ llm,
107162
+ gpu,
107163
+ perf,
107164
+ cpAccessKey
107165
+ }).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
107166
+ const probe2 = pickHindsightLiteLlmProbeUrl(llm, litellm);
107167
+ const healthCmd = probe2 ? buildLiteLlmAwareHealthCmd(poolPort, probe2) : HINDSIGHT_HEALTHCHECK_CMD.replace("localhost:8888", `localhost:${poolPort}`);
107168
+ const args = [
107169
+ "run",
107170
+ "-d",
107171
+ "--name",
107172
+ HINDSIGHT_RECALL_POOL_CONTAINER,
107173
+ "--restart",
107174
+ "always",
107175
+ "--shm-size",
107176
+ HINDSIGHT_DEFAULT_SHM_SIZE,
107177
+ ...hindsightGpuEnabled(gpu) ? ["--gpus", "all"] : [],
107178
+ "--network",
107179
+ "host",
107180
+ "--user",
107181
+ "hindsight",
107182
+ "--health-cmd",
107183
+ healthCmd,
107184
+ "--health-interval",
107185
+ "30s",
107186
+ "--health-timeout",
107187
+ "5s",
107188
+ "--health-retries",
107189
+ "3",
107190
+ "--health-start-period",
107191
+ "60s",
107192
+ ...mirrorDir ? ["-v", `${HINDSIGHT_CREDS_MIRROR_VOLUME}:${HINDSIGHT_CRED_DIR}`] : ["--tmpfs", `${HINDSIGHT_CRED_DIR}:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`],
107193
+ "-v",
107194
+ `${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`,
107195
+ ...envArgs,
107196
+ hindsightImageRef(imageTag)
107197
+ ];
107198
+ exec("docker", args);
107199
+ }
107200
+ function stopHindsightRecallPool(exec = (cmd, args) => {
107201
+ execFileSync26(cmd, args, { stdio: "pipe" });
107202
+ }) {
107203
+ try {
107204
+ exec("docker", ["update", "--restart=no", HINDSIGHT_RECALL_POOL_CONTAINER]);
107205
+ } catch {}
107206
+ try {
107207
+ exec("docker", ["stop", HINDSIGHT_RECALL_POOL_CONTAINER]);
107208
+ } catch {}
107209
+ try {
107210
+ exec("docker", ["rm", "-f", HINDSIGHT_RECALL_POOL_CONTAINER]);
107211
+ } catch {}
107212
+ }
107213
+ function isHindsightRecallPoolRunning(probe2 = defaultDockerProbe) {
107214
+ return dockerNameMatchesExactly(probe2, ["ps", "--filter", `name=${HINDSIGHT_RECALL_POOL_CONTAINER}`, "--format", "{{.Names}}"], HINDSIGHT_RECALL_POOL_CONTAINER);
107215
+ }
107216
+ var HINDSIGHT_HEALTH_MIN_TIMEOUT_MS = 150000;
107217
+ var HINDSIGHT_HEALTH_PHASE_BUDGET_MS = {
107218
+ apiStartup: 60000,
107219
+ pgFirstBoot: 90000,
107220
+ migrations: 90000,
107221
+ perExtraWorker: 15000
107222
+ };
107223
+ function hindsightHealthTimeoutMs(input) {
107224
+ const p = HINDSIGHT_HEALTH_PHASE_BUDGET_MS;
107225
+ let budget;
107226
+ switch (input.role) {
107227
+ case "authority":
107228
+ case "single-container":
107229
+ budget = p.apiStartup + p.pgFirstBoot + p.migrations;
107230
+ break;
107231
+ case "recall-pool": {
107232
+ const workers = input.workers && input.workers > 0 ? input.workers : 1;
107233
+ budget = p.apiStartup + (workers - 1) * p.perExtraWorker;
107234
+ break;
107235
+ }
107236
+ }
107237
+ return Math.max(HINDSIGHT_HEALTH_MIN_TIMEOUT_MS, budget);
107238
+ }
107239
+ async function waitForHindsightHealthy(port, opts = {}) {
107240
+ const {
107241
+ timeoutMs = opts.role ? hindsightHealthTimeoutMs({ role: opts.role, workers: opts.workers }) : HINDSIGHT_HEALTH_MIN_TIMEOUT_MS,
107242
+ intervalMs = 3000,
107243
+ fetchImpl = fetch,
107244
+ sleep: sleep4 = (ms) => new Promise((r) => setTimeout(r, ms)),
107245
+ now: now2 = () => Date.now()
107246
+ } = opts;
107247
+ const deadline = now2() + timeoutMs;
107248
+ for (;; ) {
107249
+ try {
107250
+ const res = await fetchImpl(`http://127.0.0.1:${port}/health`, {
107251
+ signal: AbortSignal.timeout(4000)
107252
+ });
107253
+ if (res.ok)
107254
+ return true;
107255
+ } catch {}
107256
+ if (now2() >= deadline)
107257
+ return false;
107258
+ await sleep4(intervalMs);
107259
+ }
107260
+ }
107261
+ async function launchRecallPoolHealthGated(opts) {
107262
+ const log = opts.log ?? (() => {});
107263
+ log(` Waiting for the authority container to become healthy on ` + `${opts.authorityApiPort} (pg0 startup)...`);
107264
+ if (!await opts.waitForHealthy(opts.authorityApiPort, "authority")) {
107265
+ return { outcome: "authority-unhealthy" };
107266
+ }
107267
+ opts.stopPool();
107268
+ log(` Starting recall pool (${opts.workers} workers) on ${opts.publicApiPort}...`);
107269
+ opts.startPool();
107270
+ if (await opts.waitForHealthy(opts.publicApiPort, "recall-pool")) {
107271
+ return { outcome: "split" };
107272
+ }
107273
+ const reason = `the recall pool did not become healthy on ${opts.publicApiPort}`;
107274
+ log(` Recall pool unhealthy on ${opts.publicApiPort}; degrading to a single ` + `container on the public port so memory.config.url stays served...`);
107275
+ opts.degradeToSingleContainer();
107276
+ if (await opts.waitForHealthy(opts.publicApiPort, "single-container")) {
107277
+ return { outcome: "degraded-single-container", reason };
107278
+ }
107279
+ return { outcome: "degrade-failed", reason };
107280
+ }
107281
+ async function convergeStaleRecallPool(opts) {
107282
+ const log = opts.log ?? (() => {});
107283
+ switch (opts.plan.action) {
107284
+ case "none":
107285
+ return { outcome: "nothing-to-do" };
107286
+ case "stop-pool":
107287
+ log(" hindsight.recall_pool is disabled but a recall pool container is running; " + "the authority already serves the public port, so removing the orphan pool...");
107288
+ opts.stopPool();
107289
+ return { outcome: "pool-stopped" };
107290
+ case "relaunch-single-on-public": {
107291
+ const publicApiPort = opts.plan.publicApiPort;
107292
+ log(` hindsight.recall_pool is disabled but a recall pool container still holds the ` + `public port ${publicApiPort}; relaunching a single container on it to match the ` + `declared topology...`);
107293
+ opts.relaunchSingleOnPublic(publicApiPort);
107294
+ if (await opts.waitForHealthy(publicApiPort, "single-container")) {
107295
+ return { outcome: "relaunched-single", publicApiPort };
107296
+ }
107297
+ return { outcome: "relaunch-failed", publicApiPort };
107298
+ }
107299
+ }
107300
+ }
107301
+
106742
107302
  // src/setup/hindsight-gpu-guard.ts
106743
107303
  init_hindsight();
106744
107304
  function assessHindsightGpuDrop(args) {
@@ -107116,6 +107676,7 @@ Cross-agent reflection plan
107116
107676
  return;
107117
107677
  }
107118
107678
  console.log(source_default.gray(" Stopping switchroom-hindsight..."));
107679
+ stopHindsightRecallPool();
107119
107680
  stopHindsight();
107120
107681
  console.log(source_default.green(" Hindsight container stopped and removed."));
107121
107682
  return;
@@ -107171,6 +107732,182 @@ Cross-agent reflection plan
107171
107732
  }
107172
107733
  const reusePorts = recreate ? getRunningHindsightPorts() : null;
107173
107734
  if (isHindsightRunning() && !recreate) {
107735
+ let noRecreatePoolCfg = null;
107736
+ try {
107737
+ noRecreatePoolCfg = resolveRecallPoolConfig(getConfig(program3).hindsight?.recall_pool);
107738
+ } catch {
107739
+ noRecreatePoolCfg = null;
107740
+ }
107741
+ if (noRecreatePoolCfg && !isHindsightRecallPoolRunning()) {
107742
+ console.log(source_default.gray(`
107743
+ Authority container is up but the recall pool is not \u2014 restoring the pool...`));
107744
+ const repairConfig = getConfig(program3);
107745
+ const repairGpu = hindsightGpuDecision(resolveHindsightGpuOverride({ flag: opts.gpu, config: repairConfig.hindsight?.gpu }));
107746
+ const repairConfigUrlPort = (() => {
107747
+ try {
107748
+ const p = Number(new URL(repairConfig.memory?.config?.url ?? "").port);
107749
+ return Number.isInteger(p) && p > 0 ? p : undefined;
107750
+ } catch {
107751
+ return;
107752
+ }
107753
+ })();
107754
+ const { publicApiPort: repairPublicPort, basePorts: repairBasePorts } = resolveHindsightLaunchPorts({
107755
+ recallEnabled: true,
107756
+ configUrlPort: repairConfigUrlPort,
107757
+ reuseApiPort: undefined,
107758
+ uiPort: 0,
107759
+ defaultApiPort: HINDSIGHT_DEFAULT_API_PORT
107760
+ });
107761
+ try {
107762
+ const { litellm: repairLitellm } = await resolveLiteLLMForHindsight(repairConfig);
107763
+ const repairLlm = await getResolvedLlm();
107764
+ const repairCpAccessKey = await resolveHindsightCpAccessKey(repairConfig);
107765
+ for (const conflict of splitDbPoolEnvConflicts(repairConfig.hindsight?.env, noRecreatePoolCfg)) {
107766
+ console.log(source_default.yellow(` ! ${splitDbPoolEnvConflictWarning(conflict)}`));
107767
+ }
107768
+ const repairAuthorityEnv = splitContainerPerfEnv(repairConfig.hindsight?.env);
107769
+ const repairResult = await launchRecallPoolHealthGated({
107770
+ publicApiPort: repairPublicPort,
107771
+ authorityApiPort: repairBasePorts.apiPort,
107772
+ workers: noRecreatePoolCfg.workers,
107773
+ waitForHealthy: (p, role) => waitForHindsightHealthy(p, { role, workers: noRecreatePoolCfg.workers }),
107774
+ stopPool: () => stopHindsightRecallPool(),
107775
+ startPool: () => startHindsightRecallPool({
107776
+ cfg: noRecreatePoolCfg,
107777
+ poolPort: repairPublicPort,
107778
+ imageTag: effectiveTag,
107779
+ litellm: repairLitellm,
107780
+ llm: repairLlm,
107781
+ gpu: repairGpu.enabled,
107782
+ perf: {
107783
+ env: repairAuthorityEnv,
107784
+ memLimit: repairConfig.hindsight?.mem_limit
107785
+ },
107786
+ cpAccessKey: repairCpAccessKey,
107787
+ mirrorDir: hindsightConsumerMirrorDir(repairConfig)
107788
+ }),
107789
+ degradeToSingleContainer: () => {
107790
+ const uiPort2 = getRunningHindsightPorts()?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT;
107791
+ stopHindsightRecallPool();
107792
+ stopHindsight();
107793
+ startHindsight({ apiPort: repairPublicPort, uiPort: uiPort2 }, repairLitellm, effectiveTag, repairLlm, hindsightConsumerMirrorDir(repairConfig), repairGpu.enabled, { env: repairConfig.hindsight?.env, memLimit: repairConfig.hindsight?.mem_limit }, repairCpAccessKey);
107794
+ },
107795
+ log: (m) => console.log(source_default.gray(m))
107796
+ });
107797
+ switch (repairResult.outcome) {
107798
+ case "split":
107799
+ console.log(source_default.green(`
107800
+ Recall pool restored on ${repairPublicPort} ` + `(authority on ${repairBasePorts.apiPort}).
107801
+ `));
107802
+ break;
107803
+ case "degraded-single-container":
107804
+ console.log(source_default.yellow(`
107805
+ ! Recall pool failed (${repairResult.reason}); DEGRADED to a single ` + `container on the public port ${repairPublicPort}. memory.config.url IS ` + `served, but the recall split is OFF \u2014 inspect \`docker logs ` + `${HINDSIGHT_RECALL_POOL_CONTAINER}\` and re-run \`switchroom memory setup ` + `--recreate\` once fixed, or set hindsight.recall_pool.enabled: false.
107806
+ `));
107807
+ break;
107808
+ case "authority-unhealthy":
107809
+ console.error(source_default.red(`
107810
+ The running authority container is not answering /health on ` + `${repairBasePorts.apiPort}; cannot restore the pool. Inspect ` + `\`docker logs switchroom-hindsight\` and re-run \`switchroom memory setup ` + `--recreate\`.
107811
+ `));
107812
+ process.exit(1);
107813
+ break;
107814
+ case "degrade-failed":
107815
+ console.error(source_default.red(`
107816
+ Recall pool restore failed AND the single-container fallback could not ` + `bind the public port ${repairPublicPort}. Inspect \`docker logs ` + `switchroom-hindsight\` and \`docker logs ${HINDSIGHT_RECALL_POOL_CONTAINER}\`, ` + `then re-run \`switchroom memory setup --recreate\`.
107817
+ `));
107818
+ process.exit(1);
107819
+ break;
107820
+ }
107821
+ } catch (err) {
107822
+ console.error(source_default.red(`
107823
+ Failed to restore the recall pool: ${err.message}
107824
+ `));
107825
+ process.exit(1);
107826
+ }
107827
+ const repairUrl = `http://127.0.0.1:${repairPublicPort}/mcp/`;
107828
+ try {
107829
+ if (persistMemoryConfigUrl(getConfigPath(program3), "claude-code", repairUrl)) {
107830
+ console.log(source_default.gray(` Updated ${getConfigPath(program3)} with memory.config.url = ${repairUrl}`));
107831
+ }
107832
+ } catch (err) {
107833
+ console.error(source_default.yellow(` Note: could not auto-update switchroom.yaml: ${err.message}`));
107834
+ }
107835
+ return;
107836
+ }
107837
+ const livePorts = getRunningHindsightPorts();
107838
+ const liveApiPort = livePorts?.apiPort;
107839
+ const liveConfigUrlPort = (() => {
107840
+ try {
107841
+ const p = Number(new URL(getConfig(program3).memory?.config?.url ?? "").port);
107842
+ return Number.isInteger(p) && p > 0 ? p : undefined;
107843
+ } catch {
107844
+ return;
107845
+ }
107846
+ })();
107847
+ const stalePlan = planStaleRecallPoolConvergence({
107848
+ recallEnabled: noRecreatePoolCfg !== null,
107849
+ poolRunning: isHindsightRecallPoolRunning(),
107850
+ authorityApiPort: liveApiPort,
107851
+ configUrlPort: liveConfigUrlPort
107852
+ });
107853
+ if (stalePlan.action !== "none") {
107854
+ const staleConfig = getConfig(program3);
107855
+ const staleGpu = hindsightGpuDecision(resolveHindsightGpuOverride({ flag: opts.gpu, config: staleConfig.hindsight?.gpu }));
107856
+ try {
107857
+ const needsSecrets = stalePlan.action === "relaunch-single-on-public";
107858
+ const staleLitellm = needsSecrets ? (await resolveLiteLLMForHindsight(staleConfig)).litellm : undefined;
107859
+ const staleLlm = needsSecrets ? await getResolvedLlm() : undefined;
107860
+ const staleCpAccessKey = needsSecrets ? await resolveHindsightCpAccessKey(staleConfig) : undefined;
107861
+ const staleResult = await convergeStaleRecallPool({
107862
+ plan: stalePlan,
107863
+ stopPool: () => stopHindsightRecallPool(),
107864
+ relaunchSingleOnPublic: (publicApiPort2) => {
107865
+ stopHindsightRecallPool();
107866
+ stopHindsight();
107867
+ startHindsight({ apiPort: publicApiPort2, uiPort: livePorts?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT }, staleLitellm, effectiveTag, staleLlm, hindsightConsumerMirrorDir(staleConfig), staleGpu.enabled, { env: staleConfig.hindsight?.env, memLimit: staleConfig.hindsight?.mem_limit }, staleCpAccessKey);
107868
+ },
107869
+ waitForHealthy: (p, role) => waitForHindsightHealthy(p, { role }),
107870
+ log: (m) => console.log(source_default.gray(m))
107871
+ });
107872
+ switch (staleResult.outcome) {
107873
+ case "pool-stopped":
107874
+ console.log(source_default.green(`
107875
+ Removed the orphan recall pool (hindsight.recall_pool is disabled).
107876
+ `));
107877
+ break;
107878
+ case "relaunched-single":
107879
+ console.log(source_default.green(`
107880
+ Converged to the declared single-container topology on ` + `${staleResult.publicApiPort}.
107881
+ `));
107882
+ break;
107883
+ case "relaunch-failed":
107884
+ console.error(source_default.red(`
107885
+ hindsight.recall_pool is disabled, but the single container relaunched ` + `on the public port ${staleResult.publicApiPort} did not become healthy \u2014 ` + `nothing is serving fleet memory. Inspect \`docker logs ` + `switchroom-hindsight --tail 50\`, then re-run \`switchroom memory setup ` + `--recreate\`.
107886
+ `));
107887
+ process.exit(1);
107888
+ break;
107889
+ case "nothing-to-do":
107890
+ break;
107891
+ }
107892
+ } catch (err) {
107893
+ console.error(source_default.red(`
107894
+ Failed to converge the stale recall pool: ${err.message}
107895
+ `));
107896
+ process.exit(1);
107897
+ }
107898
+ return;
107899
+ }
107900
+ if (publicPortIsUnserved({
107901
+ configUrlPort: liveConfigUrlPort,
107902
+ authorityApiPort: liveApiPort,
107903
+ poolRunning: isHindsightRecallPoolRunning()
107904
+ })) {
107905
+ console.error(source_default.red(`
107906
+ switchroom-hindsight is running on port ${liveApiPort}, but ` + `memory.config.url points at ${liveConfigUrlPort} and nothing is serving ` + `it \u2014 fleet memory is DOWN.
107907
+ ` + ` Run \`switchroom memory setup --recreate\` to rebind it to ` + `${liveConfigUrlPort}, or set hindsight.recall_pool.enabled: true to restore ` + `the recall pool on the public port.
107908
+ `));
107909
+ process.exit(1);
107910
+ }
107174
107911
  console.log(source_default.green(`
107175
107912
  Hindsight container is already running (switchroom-hindsight).
107176
107913
  `));
@@ -107253,31 +107990,24 @@ Cross-agent reflection plan
107253
107990
  process.exit(1);
107254
107991
  }
107255
107992
  }
107993
+ stopHindsightRecallPool();
107256
107994
  if (isHindsightContainerExists()) {
107257
107995
  console.log(source_default.gray(" Removing existing switchroom-hindsight container..."));
107258
107996
  stopHindsight();
107259
107997
  }
107260
- let ports;
107998
+ let recallPoolCfg;
107999
+ try {
108000
+ recallPoolCfg = resolveRecallPoolConfig(getConfig(program3).hindsight?.recall_pool);
108001
+ } catch (err) {
108002
+ console.log(source_default.yellow(` ! Ignoring hindsight.recall_pool (${err.message}); ` + `starting the single-container topology.`));
108003
+ recallPoolCfg = null;
108004
+ }
108005
+ let uiPort;
107261
108006
  if (reusePorts) {
107262
- ports = reusePorts;
107263
- if (reusePorts.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
107264
- let explicitUrl;
107265
- try {
107266
- explicitUrl = getConfig(program3).memory?.config?.url;
107267
- } catch {
107268
- explicitUrl = undefined;
107269
- }
107270
- if (!explicitUrl) {
107271
- console.log(source_default.yellow(`
107272
- \u26a0 MIGRATION HAZARD: reusing stale host port ${reusePorts.apiPort} ` + `for switchroom-hindsight, but scaffolding now defaults to ` + `${HINDSIGHT_DEFAULT_API_PORT} and no explicit memory.config.url is set.`));
107273
- console.log(source_default.yellow(` Agents may be repointed to a dead URL (http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/) ` + `while the container listens on ${reusePorts.apiPort}.`));
107274
- console.log(source_default.yellow(` Fix: set memory.config.url: http://127.0.0.1:${reusePorts.apiPort}/mcp/ explicitly, ` + `or migrate the container to port ${HINDSIGHT_DEFAULT_API_PORT}.
107275
- `));
107276
- }
107277
- }
108007
+ uiPort = reusePorts.uiPort;
107278
108008
  } else {
107279
108009
  try {
107280
- ports = await pickHindsightPorts();
108010
+ uiPort = (await pickHindsightPorts()).uiPort;
107281
108011
  } catch (err) {
107282
108012
  console.error(source_default.red(`
107283
108013
  ${err.message}
@@ -107285,31 +108015,39 @@ Cross-agent reflection plan
107285
108015
  process.exit(1);
107286
108016
  }
107287
108017
  }
107288
- if (ports.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
107289
- console.log(source_default.yellow(` Port ${HINDSIGHT_DEFAULT_API_PORT} is already in use; ` + `using ${ports.apiPort}/${ports.uiPort} instead.`));
107290
- }
107291
- let conflict = await preflightHindsightPorts(ports);
107292
- if (conflict) {
107293
- const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
107294
- console.log(source_default.yellow(`
107295
- Chosen Hindsight port ${conflict.port} is occupied${heldBy}; ` + `selecting a free port instead of crash-looping.`));
108018
+ const configUrlPort = (() => {
107296
108019
  try {
107297
- ports = await pickHindsightPorts();
107298
- } catch (err) {
107299
- console.error(source_default.red(`
107300
- ${err.message}
107301
- `));
107302
- process.exit(1);
108020
+ const p = Number(new URL(getConfig(program3).memory?.config?.url ?? "").port);
108021
+ return Number.isInteger(p) && p > 0 ? p : undefined;
108022
+ } catch {
108023
+ return;
107303
108024
  }
107304
- conflict = await preflightHindsightPorts(ports);
108025
+ })();
108026
+ const { publicApiPort, basePorts } = resolveHindsightLaunchPorts({
108027
+ recallEnabled: recallPoolCfg != null,
108028
+ configUrlPort,
108029
+ reuseApiPort: reusePorts?.apiPort,
108030
+ uiPort,
108031
+ defaultApiPort: HINDSIGHT_DEFAULT_API_PORT
108032
+ });
108033
+ if (!recallPoolCfg && configUrlPort == null && reusePorts && reusePorts.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
108034
+ console.log(source_default.yellow(`
108035
+ Note: reusing host port ${reusePorts.apiPort} for switchroom-hindsight ` + `(scaffolding default is ${HINDSIGHT_DEFAULT_API_PORT}); memory.config.url will be ` + `pinned to ${reusePorts.apiPort} so agents follow the live port. Migrate deliberately ` + `if you want ${HINDSIGHT_DEFAULT_API_PORT}.
108036
+ `));
108037
+ }
108038
+ if (recallPoolCfg) {
108039
+ console.log(source_default.gray(` recall_pool enabled: authority (pg0 + background) on ${basePorts.apiPort}, ` + `${recallPoolCfg.workers}-worker recall pool on ${publicApiPort}.`));
108040
+ }
108041
+ const portsToBind = recallPoolCfg ? [publicApiPort, basePorts.apiPort, basePorts.uiPort] : [basePorts.apiPort, basePorts.uiPort];
108042
+ for (const p of portsToBind) {
108043
+ const conflict = await preflightHindsightPorts({ apiPort: p, uiPort: p });
107305
108044
  if (conflict) {
107306
- const stillHeldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
108045
+ const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
107307
108046
  console.error(source_default.red(`
107308
- Refusing to start Hindsight: port ${conflict.port} is still ` + `occupied${stillHeldBy} after reassignment. Free it and retry ` + `\`switchroom memory --start\`.
108047
+ Refusing to start Hindsight: port ${conflict.port} is occupied${heldBy}. ` + `Free it and retry \`switchroom memory setup --recreate\`.
107309
108048
  `));
107310
108049
  process.exit(1);
107311
108050
  }
107312
- console.log(source_default.green(` Reassigned Hindsight to port ${ports.apiPort}/${ports.uiPort}.`));
107313
108051
  }
107314
108052
  if (opts.provider && opts.provider !== "claude-code") {
107315
108053
  console.log(source_default.gray(` Note: --provider=${opts.provider} ignored. switchroom-hindsight is pinned to the ` + "`claude-code` provider (subscription-honest). The flag is kept for back-compat."));
@@ -107329,23 +108067,83 @@ Cross-agent reflection plan
107329
108067
  for (const drop of await diffDroppedHindsightLlmVaultKeys(hindsightConfig.hindsight?.llm, resolvedLlm)) {
107330
108068
  console.log(source_default.yellow(` ! ${hindsightLlmDroppedKeyWarning(drop)}`));
107331
108069
  }
107332
- startHindsight(ports, litellmCfg, effectiveTag, resolvedLlm, hindsightConsumerMirrorDir(hindsightConfig), gpuDecision.enabled, {
107333
- env: hindsightConfig.hindsight?.env,
108070
+ if (recallPoolCfg) {
108071
+ for (const conflict of splitDbPoolEnvConflicts(hindsightConfig.hindsight?.env, recallPoolCfg)) {
108072
+ console.log(source_default.yellow(` ! ${splitDbPoolEnvConflictWarning(conflict)}`));
108073
+ }
108074
+ }
108075
+ const authorityEnv = recallPoolCfg ? splitContainerPerfEnv(hindsightConfig.hindsight?.env) : hindsightConfig.hindsight?.env;
108076
+ startHindsight(basePorts, litellmCfg, effectiveTag, resolvedLlm, hindsightConsumerMirrorDir(hindsightConfig), gpuDecision.enabled, {
108077
+ env: authorityEnv,
107334
108078
  memLimit: hindsightConfig.hindsight?.mem_limit
107335
108079
  }, cpAccessKey);
107336
108080
  if (litellmCfg) {
107337
108081
  console.log(source_default.gray(" LiteLLM routing enabled for hindsight (--network host)."));
107338
108082
  }
107339
- console.log(source_default.green(`
107340
- Hindsight container started (switchroom-hindsight) on port ${ports.apiPort}.
108083
+ if (recallPoolCfg) {
108084
+ const launchResult = await launchRecallPoolHealthGated({
108085
+ publicApiPort,
108086
+ authorityApiPort: basePorts.apiPort,
108087
+ workers: recallPoolCfg.workers,
108088
+ waitForHealthy: (p, role) => waitForHindsightHealthy(p, { role, workers: recallPoolCfg.workers }),
108089
+ stopPool: () => stopHindsightRecallPool(),
108090
+ startPool: () => startHindsightRecallPool({
108091
+ cfg: recallPoolCfg,
108092
+ poolPort: publicApiPort,
108093
+ imageTag: effectiveTag,
108094
+ litellm: litellmCfg,
108095
+ llm: resolvedLlm,
108096
+ gpu: gpuDecision.enabled,
108097
+ perf: {
108098
+ env: authorityEnv,
108099
+ memLimit: hindsightConfig.hindsight?.mem_limit
108100
+ },
108101
+ cpAccessKey,
108102
+ mirrorDir: hindsightConsumerMirrorDir(hindsightConfig)
108103
+ }),
108104
+ degradeToSingleContainer: () => {
108105
+ stopHindsightRecallPool();
108106
+ stopHindsight();
108107
+ startHindsight({ apiPort: publicApiPort, uiPort: basePorts.uiPort }, litellmCfg, effectiveTag, resolvedLlm, hindsightConsumerMirrorDir(hindsightConfig), gpuDecision.enabled, { env: hindsightConfig.hindsight?.env, memLimit: hindsightConfig.hindsight?.mem_limit }, cpAccessKey);
108108
+ },
108109
+ log: (m) => console.log(source_default.gray(m))
108110
+ });
108111
+ switch (launchResult.outcome) {
108112
+ case "split":
108113
+ console.log(source_default.green(`
108114
+ Hindsight recall split started: authority (pg0 + background) on ` + `${basePorts.apiPort}, ${recallPoolCfg.workers}-worker recall pool on ${publicApiPort}.
108115
+ `));
108116
+ break;
108117
+ case "degraded-single-container":
108118
+ console.log(source_default.yellow(`
108119
+ ! Recall pool failed (${launchResult.reason}); DEGRADED to a single ` + `container on the public port ${publicApiPort}. memory.config.url IS served, ` + `but the recall split is OFF \u2014 inspect \`docker logs ` + `${HINDSIGHT_RECALL_POOL_CONTAINER}\` and re-run \`switchroom memory setup ` + `--recreate\` once fixed, or set hindsight.recall_pool.enabled: false.
108120
+ `));
108121
+ break;
108122
+ case "authority-unhealthy":
108123
+ console.error(source_default.red(`
108124
+ Authority container did not become healthy on ${basePorts.apiPort}. ` + `The recall pool was NOT started and no single container could share its ` + `pg0. Investigate \`docker logs switchroom-hindsight\` and re-run ` + `\`switchroom memory setup --recreate\`.
108125
+ `));
108126
+ process.exit(1);
108127
+ break;
108128
+ case "degrade-failed":
108129
+ console.error(source_default.red(`
108130
+ Recall pool did not become healthy on ${publicApiPort} AND the ` + `single-container fallback also failed to bind the public port. Inspect ` + `\`docker logs switchroom-hindsight\` and \`docker logs ` + `${HINDSIGHT_RECALL_POOL_CONTAINER}\`, then re-run \`switchroom memory setup ` + `--recreate\`.
107341
108131
  `));
108132
+ process.exit(1);
108133
+ break;
108134
+ }
108135
+ } else {
108136
+ console.log(source_default.green(`
108137
+ Hindsight container started (switchroom-hindsight) on port ${basePorts.apiPort}.
108138
+ `));
108139
+ }
107342
108140
  } catch (err) {
107343
108141
  console.error(source_default.red(`
107344
108142
  Failed to start Hindsight: ${err.message}
107345
108143
  `));
107346
108144
  process.exit(1);
107347
108145
  }
107348
- const url = `http://127.0.0.1:${ports.apiPort}/mcp/`;
108146
+ const url = `http://127.0.0.1:${publicApiPort}/mcp/`;
107349
108147
  const configPath = getConfigPath(program3);
107350
108148
  try {
107351
108149
  if (persistMemoryConfigUrl(configPath, provider, url)) {
@@ -107401,7 +108199,7 @@ Cross-agent reflection plan
107401
108199
  }
107402
108200
  console.log();
107403
108201
  }));
107404
- memory.command("repair").description("Verify and rebuild a bank's per-(bank, fact_type) vector index coverage (hindsight >= 0.8.5) \u2014 the fix for a restored/upgraded bank whose recall silently under-returns").option("--bank <id>", "Bank id to repair (mutually exclusive with --all)").option("--agent <name>", "Repair the bank belonging to this agent (resolves the bank id for you)").option("--all", "Repair every bank in the base schema and all discovered tenant schemas").option("--schema <name>", "Limit to a single schema (default: base + discovered tenant schemas)").option("--dry-run", "Report what would be repaired without creating or dropping any index").option("--container <name>", `Hindsight container name (default: ${HINDSIGHT_CONTAINER_NAME})`).action(withConfigError(async (opts) => {
108202
+ memory.command("repair").description("Verify and rebuild a bank's per-(bank, fact_type) vector index coverage (hindsight >= 0.8.5) \u2014 the fix for a restored/upgraded bank whose recall silently under-returns").option("--bank <id>", "Bank id to repair (mutually exclusive with --all)").option("--agent <name>", "Repair the bank belonging to this agent (resolves the bank id for you)").option("--all", "Repair every bank in the base schema and all discovered tenant schemas").option("--schema <name>", "Limit to a single schema (default: base + discovered tenant schemas)").option("--dry-run", "Report what would be repaired without creating or dropping any index").option("--container <name>", `Hindsight container name (default: ${HINDSIGHT_CONTAINER_NAME2})`).action(withConfigError(async (opts) => {
107405
108203
  const config = getConfig(program3);
107406
108204
  if (opts.agent && opts.bank) {
107407
108205
  console.error(source_default.red("Pass either --agent or --bank, not both."));
@@ -112043,8 +112841,8 @@ init_embedded_examples();
112043
112841
  init_atomic();
112044
112842
  init_loader();
112045
112843
  init_scaffold();
112046
- import { existsSync as existsSync85, readFileSync as readFileSync79, mkdirSync as mkdirSync47, statSync as statSync47, writeFileSync as writeFileSync33 } from "node:fs";
112047
- import { execFileSync as execFileSync26 } from "node:child_process";
112844
+ import { existsSync as existsSync86, readFileSync as readFileSync79, mkdirSync as mkdirSync47, statSync as statSync47, writeFileSync as writeFileSync33 } from "node:fs";
112845
+ import { execFileSync as execFileSync27 } from "node:child_process";
112048
112846
  import { resolve as resolve46, dirname as dirname36 } from "node:path";
112049
112847
  init_state();
112050
112848
  init_vault();
@@ -112052,7 +112850,6 @@ init_manager();
112052
112850
  init_onboarding();
112053
112851
  init_gpu_detect();
112054
112852
  init_host_capabilities();
112055
- init_hindsight();
112056
112853
  init_posthog();
112057
112854
 
112058
112855
  // src/cli/setup-posture-rewrite.ts
@@ -112443,6 +113240,9 @@ class SetupVerificationError extends Error {
112443
113240
  init_lifecycle();
112444
113241
  init_timezone();
112445
113242
  init_schema();
113243
+
113244
+ // src/cli/setup-step-ui.ts
113245
+ init_source();
112446
113246
  var STEP_PENDING = source_default.gray("\u25cb");
112447
113247
  var STEP_ACTIVE = source_default.blue("->");
112448
113248
  var STEP_DONE = source_default.green("OK");
@@ -112453,6 +113253,315 @@ function stepHeaderTo(log, num3, title, status) {
112453
113253
  log(`
112454
113254
  ${status} ${source_default.bold(`Step ${num3}:`)} ${title}`);
112455
113255
  }
113256
+
113257
+ // src/cli/setup-memory-backend.ts
113258
+ init_source();
113259
+ init_loader();
113260
+ init_hindsight();
113261
+ import { existsSync as existsSync85 } from "node:fs";
113262
+ var HINDSIGHT_READY_RETRIES = 5;
113263
+ var HINDSIGHT_READY_INTERVAL_MS = 1000;
113264
+ async function resolveLiteLLMForHindsight2(config, deps = {}) {
113265
+ return resolveHindsightLiteLlm(config, deps);
113266
+ }
113267
+ function isMemoryBackendDisabled(config) {
113268
+ const memoryBackend = config.memory?.backend ?? "hindsight";
113269
+ return process.env.SWITCHROOM_MEMORY_BACKEND === "none" || memoryBackend === "none";
113270
+ }
113271
+ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, deps = {}) {
113272
+ stepHeader(6, "Memory backend", STEP_ACTIVE);
113273
+ const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
113274
+ const pickPorts = deps.pickPorts ?? pickHindsightPorts;
113275
+ const preflightPorts = deps.preflightPorts ?? preflightHindsightPorts;
113276
+ if (isMemoryBackendDisabled(config)) {
113277
+ console.log(source_default.gray(" Memory backend disabled (set to 'none')."));
113278
+ console.log(source_default.green(` ${STEP_DONE} Skipped`));
113279
+ return { hindsightExpected: false, optedOut: true };
113280
+ }
113281
+ let setupHindsight = true;
113282
+ if (!nonInteractive) {
113283
+ console.log(source_default.gray(" Hindsight will use Anthropic OAuth via the auth-broker. The fleet's"));
113284
+ console.log(source_default.gray(" active account (auth.active) is shared \u2014 no OpenAI API key is needed."));
113285
+ setupHindsight = await askYesNo(" Set up Hindsight memory? (recommended)", true);
113286
+ }
113287
+ if (!setupHindsight) {
113288
+ console.log(source_default.gray(" Skipping Hindsight setup."));
113289
+ console.log(source_default.green(` ${STEP_DONE} Skipped`));
113290
+ return { hindsightExpected: false, optedOut: false };
113291
+ }
113292
+ if (process.env.HINDSIGHT_API_LLM_API_KEY) {
113293
+ console.log(source_default.gray(" Note: HINDSIGHT_API_LLM_API_KEY is set in your env but is no longer used. " + "You can remove it."));
113294
+ }
113295
+ try {
113296
+ const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
113297
+ const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
113298
+ if (passphrase && existsSync85(vaultPath)) {
113299
+ const { getStringSecret: getStringSecret2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
113300
+ const existing = getStringSecret2(passphrase, vaultPath, "hindsight-api-key");
113301
+ if (existing) {
113302
+ console.log(source_default.gray(" Note: legacy 'hindsight-api-key' is in your vault but is no longer used. You can remove it with `switchroom vault rm hindsight-api-key`."));
113303
+ }
113304
+ }
113305
+ } catch {}
113306
+ {
113307
+ try {
113308
+ const result = await ensureHindsightConsumer(switchroomConfigPath);
113309
+ if (result.added) {
113310
+ console.log(source_default.green(` ${STEP_DONE} Registered auth.consumers[${HINDSIGHT_CONSUMER_NAME}] (follows the fleet active account)`));
113311
+ } else {
113312
+ console.log(source_default.gray(` auth.consumers[${HINDSIGHT_CONSUMER_NAME}] already present.`));
113313
+ }
113314
+ } catch (err) {
113315
+ console.log(source_default.yellow(` Warning: could not write auth.consumers entry: ${err.message}`));
113316
+ }
113317
+ }
113318
+ const dockerState = probeDockerAvailability(deps.dockerProbe);
113319
+ if (dockerState !== "ok") {
113320
+ const noCli = dockerState === "no-cli";
113321
+ const headline = noCli ? "Docker is not installed (no `docker` command on PATH)." : "Docker is installed but its daemon is not responding.";
113322
+ const remedy = noCli ? "Install Docker (https://docs.docker.com/get-docker/), then re-run `switchroom setup`" : "Start Docker (on macOS: open Docker Desktop; on Linux: `sudo systemctl start docker`), then re-run `switchroom setup`";
113323
+ console.log(source_default.red(` x ${headline}`));
113324
+ console.log(source_default.gray(" Hindsight memory runs as a Docker container (and so does every agent)."));
113325
+ console.log(source_default.gray(` ${remedy} \u2014 or re-run with SWITCHROOM_MEMORY_BACKEND=none to skip memory setup.`));
113326
+ throw new Error(`Memory backend setup failed: ${headline} ${remedy}, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
113327
+ }
113328
+ let recallPoolCfg;
113329
+ try {
113330
+ recallPoolCfg = resolveRecallPoolConfig(config.hindsight?.recall_pool);
113331
+ } catch (err) {
113332
+ console.log(source_default.yellow(` ! Ignoring hindsight.recall_pool (${err.message}); starting the single-container topology.`));
113333
+ recallPoolCfg = null;
113334
+ }
113335
+ if (recallPoolCfg) {
113336
+ for (const conflict2 of splitDbPoolEnvConflicts(config.hindsight?.env, recallPoolCfg)) {
113337
+ console.log(source_default.yellow(` ! ${splitDbPoolEnvConflictWarning(conflict2)}`));
113338
+ }
113339
+ }
113340
+ const startPool = deps.startRecallPool ?? startHindsightRecallPool;
113341
+ const waitHealthy = deps.waitForHealthy ?? waitForHindsightHealthy;
113342
+ const waitHealthyFor = (port, role) => waitHealthy(port, { role, workers: recallPoolCfg?.workers });
113343
+ const poolIsRunning = deps.recallPoolRunningProbe ?? isHindsightRecallPoolRunning;
113344
+ const stopPool = deps.stopRecallPool ?? stopHindsightRecallPool;
113345
+ const parseUrlPort = (u) => {
113346
+ try {
113347
+ const p = Number(new URL(u ?? "").port);
113348
+ return Number.isInteger(p) && p > 0 ? p : undefined;
113349
+ } catch {
113350
+ return;
113351
+ }
113352
+ };
113353
+ const resolveLaunchSecrets = async () => {
113354
+ const { litellm: litellmCfg, droppedRef: litellmDroppedRef } = await resolveLiteLLMForHindsight2(config, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
113355
+ if (litellmDroppedRef) {
113356
+ console.log(source_default.yellow(` ! ${hindsightLiteLlmDroppedKeyWarning(litellmDroppedRef)}`));
113357
+ }
113358
+ const cpAccessKey = await resolveHindsightCpAccessKey(config);
113359
+ if (!cpAccessKey) {
113360
+ console.log(source_default.yellow(` ! ${HINDSIGHT_CP_NO_ACCESS_KEY_WARNING}`));
113361
+ }
113362
+ const resolvedLlm = await resolveHindsightLlmSecrets(config.hindsight?.llm, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
113363
+ for (const drop of await diffDroppedHindsightLlmVaultKeys(config.hindsight?.llm, resolvedLlm)) {
113364
+ console.log(source_default.yellow(` ! ${hindsightLlmDroppedKeyWarning(drop)}`));
113365
+ }
113366
+ return { litellmCfg, cpAccessKey, resolvedLlm };
113367
+ };
113368
+ const launchRecallPool = async (cfg, publicApiPort2, authorityApiPort, fallbackUiPort, secrets) => {
113369
+ const startContainer = deps.startContainer ?? startHindsight;
113370
+ const result = await launchRecallPoolHealthGated({
113371
+ publicApiPort: publicApiPort2,
113372
+ authorityApiPort,
113373
+ workers: cfg.workers,
113374
+ waitForHealthy: waitHealthyFor,
113375
+ stopPool,
113376
+ startPool: () => startPool({
113377
+ cfg,
113378
+ poolPort: publicApiPort2,
113379
+ litellm: secrets.litellmCfg,
113380
+ llm: secrets.resolvedLlm,
113381
+ perf: {
113382
+ env: splitContainerPerfEnv(config.hindsight?.env),
113383
+ memLimit: config.hindsight?.mem_limit
113384
+ },
113385
+ cpAccessKey: secrets.cpAccessKey,
113386
+ mirrorDir: hindsightConsumerMirrorDir(config)
113387
+ }),
113388
+ degradeToSingleContainer: () => {
113389
+ stopPool();
113390
+ (deps.stopContainer ?? stopHindsight)();
113391
+ startContainer({ apiPort: publicApiPort2, uiPort: fallbackUiPort }, secrets.litellmCfg, undefined, secrets.resolvedLlm, hindsightConsumerMirrorDir(config), undefined, { env: config.hindsight?.env, memLimit: config.hindsight?.mem_limit }, secrets.cpAccessKey);
113392
+ },
113393
+ log: (m) => console.log(source_default.gray(m))
113394
+ });
113395
+ switch (result.outcome) {
113396
+ case "split":
113397
+ return "split";
113398
+ case "degraded-single-container":
113399
+ console.log(source_default.yellow(` ! Recall pool failed (${result.reason}); DEGRADED to a single container on the public port ${publicApiPort2}. memory.config.url IS served, but the recall split is ` + `OFF \u2014 inspect \`docker logs ${HINDSIGHT_RECALL_POOL_CONTAINER}\` and re-run \`switchroom setup\` once fixed, or set hindsight.recall_pool.enabled: false.`));
113400
+ return "degraded";
113401
+ case "authority-unhealthy":
113402
+ throw new Error(`Memory backend setup failed: the authority container did not become healthy on ${authorityApiPort}. Check \`docker logs switchroom-hindsight --tail 50\`, then re-run \`switchroom setup\`, or set hindsight.recall_pool.enabled: false to skip the split.`);
113403
+ case "degrade-failed":
113404
+ throw new Error(`Memory backend setup failed: the recall pool did not become healthy on ${publicApiPort2} AND the single-container fallback also failed to bind the public port. Inspect \`docker logs switchroom-hindsight --tail 50\` and \`docker logs ${HINDSIGHT_RECALL_POOL_CONTAINER}\`, then re-run \`switchroom setup\`, or set hindsight.recall_pool.enabled: false to skip the split.`);
113405
+ }
113406
+ };
113407
+ if (isHindsightRunning(deps.dockerProbe)) {
113408
+ if (recallPoolCfg && !poolIsRunning(deps.dockerProbe)) {
113409
+ console.log(source_default.gray(" Authority container is up but the recall pool is not \u2014 restoring the pool..."));
113410
+ const secrets = await resolveLaunchSecrets();
113411
+ const { publicApiPort: publicApiPort2, basePorts: basePorts2 } = resolveHindsightLaunchPorts({
113412
+ recallEnabled: true,
113413
+ configUrlPort: parseUrlPort(config.memory?.config?.url),
113414
+ reuseApiPort: undefined,
113415
+ uiPort: 0,
113416
+ defaultApiPort: HINDSIGHT_DEFAULT_API_PORT
113417
+ });
113418
+ const topo = await launchRecallPool(recallPoolCfg, publicApiPort2, basePorts2.apiPort, HINDSIGHT_DEFAULT_UI_PORT, secrets);
113419
+ if (topo === "split") {
113420
+ console.log(source_default.green(` ${STEP_DONE} Recall pool restored on ${publicApiPort2} (authority on ${basePorts2.apiPort})`));
113421
+ }
113422
+ return { hindsightExpected: true, optedOut: false };
113423
+ }
113424
+ const staleRunningPorts = (deps.runningPortsProbe ?? getRunningHindsightPorts)();
113425
+ const stalePlan = planStaleRecallPoolConvergence({
113426
+ recallEnabled: recallPoolCfg !== null,
113427
+ poolRunning: poolIsRunning(deps.dockerProbe),
113428
+ authorityApiPort: staleRunningPorts?.apiPort,
113429
+ configUrlPort: parseUrlPort(config.memory?.config?.url)
113430
+ });
113431
+ if (stalePlan.action !== "none") {
113432
+ const staleSecrets = stalePlan.action === "relaunch-single-on-public" ? await resolveLaunchSecrets() : undefined;
113433
+ const startContainer = deps.startContainer ?? startHindsight;
113434
+ const staleResult = await convergeStaleRecallPool({
113435
+ plan: stalePlan,
113436
+ stopPool,
113437
+ relaunchSingleOnPublic: (publicApiPort2) => {
113438
+ stopPool();
113439
+ (deps.stopContainer ?? stopHindsight)();
113440
+ startContainer({
113441
+ apiPort: publicApiPort2,
113442
+ uiPort: staleRunningPorts?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT
113443
+ }, staleSecrets.litellmCfg, undefined, staleSecrets.resolvedLlm, hindsightConsumerMirrorDir(config), undefined, { env: config.hindsight?.env, memLimit: config.hindsight?.mem_limit }, staleSecrets.cpAccessKey);
113444
+ },
113445
+ waitForHealthy: waitHealthyFor,
113446
+ log: (m) => console.log(source_default.gray(m))
113447
+ });
113448
+ if (staleResult.outcome === "relaunch-failed") {
113449
+ const msg = `hindsight.recall_pool is disabled, but the single container relaunched on the public port ${staleResult.publicApiPort} did not become healthy \u2014 nothing is ` + `serving fleet memory.`;
113450
+ console.log(source_default.red(` x ${msg}`));
113451
+ throw new Error(`Memory backend setup failed: ${msg} Inspect \`docker logs switchroom-hindsight --tail 50\`, then re-run \`switchroom setup\`.`);
113452
+ }
113453
+ if (staleResult.outcome === "pool-stopped") {
113454
+ console.log(source_default.green(` ${STEP_DONE} Removed the orphan recall pool (hindsight.recall_pool is disabled)`));
113455
+ } else if (staleResult.outcome === "relaunched-single") {
113456
+ console.log(source_default.green(` ${STEP_DONE} Converged to the declared single-container topology on ${staleResult.publicApiPort}`));
113457
+ }
113458
+ return { hindsightExpected: true, optedOut: false };
113459
+ }
113460
+ const runningPorts = staleRunningPorts;
113461
+ if (publicPortIsUnserved({
113462
+ configUrlPort: parseUrlPort(config.memory?.config?.url),
113463
+ authorityApiPort: runningPorts?.apiPort,
113464
+ poolRunning: poolIsRunning(deps.dockerProbe)
113465
+ })) {
113466
+ const publicPort = parseUrlPort(config.memory?.config?.url);
113467
+ const msg = `switchroom-hindsight is running on port ${runningPorts?.apiPort}, but memory.config.url points at ${publicPort} and nothing is serving it \u2014 ` + `fleet memory is DOWN.`;
113468
+ console.log(source_default.red(` x ${msg}`));
113469
+ throw new Error(`Memory backend setup failed: ${msg} Run \`switchroom memory setup --recreate\` to rebind it to ${publicPort}, or set hindsight.recall_pool.enabled: true to restore the recall pool on the public port.`);
113470
+ }
113471
+ console.log(source_default.green(` ${STEP_DONE} Hindsight container already running (switchroom-hindsight)`));
113472
+ return { hindsightExpected: true, optedOut: false };
113473
+ }
113474
+ if (isHindsightContainerExists(deps.dockerProbe)) {
113475
+ console.log(source_default.gray(" Found stopped switchroom-hindsight container, removing..."));
113476
+ stopHindsight();
113477
+ }
113478
+ stopPool();
113479
+ let ports;
113480
+ try {
113481
+ ports = await pickPorts();
113482
+ } catch (err) {
113483
+ console.log(source_default.red(` ${err.message}`));
113484
+ throw new Error(`Memory backend setup failed: could not allocate Hindsight ports: ${err.message}. Free a port and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
113485
+ }
113486
+ if (ports.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
113487
+ console.log(source_default.yellow(` Port ${HINDSIGHT_DEFAULT_API_PORT} is already in use; using ${ports.apiPort}/${ports.uiPort} instead.`));
113488
+ }
113489
+ let conflict = await preflightPorts(ports);
113490
+ if (conflict) {
113491
+ const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
113492
+ console.log(source_default.yellow(` Chosen Hindsight port ${conflict.port} is occupied${heldBy}; selecting a free port instead of crash-looping.`));
113493
+ try {
113494
+ ports = await pickPorts();
113495
+ } catch (err) {
113496
+ console.log(source_default.red(` ${err.message}`));
113497
+ throw new Error(`Memory backend setup failed: could not allocate Hindsight ports after reassignment: ${err.message}. Free a port and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
113498
+ }
113499
+ conflict = await preflightPorts(ports);
113500
+ if (conflict) {
113501
+ const stillHeldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
113502
+ const msg = `Refusing to start Hindsight: port ${conflict.port} is still occupied${stillHeldBy} after reassignment.`;
113503
+ console.log(source_default.red(` ${msg}`));
113504
+ throw new Error(`Memory backend setup failed: ${msg} Free it and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
113505
+ }
113506
+ console.log(source_default.green(` Reassigned Hindsight to port ${ports.apiPort}/${ports.uiPort}.`));
113507
+ }
113508
+ const { publicApiPort, basePorts } = resolveHindsightLaunchPorts({
113509
+ recallEnabled: recallPoolCfg != null,
113510
+ configUrlPort: parseUrlPort(config.memory?.config?.url),
113511
+ reuseApiPort: ports.apiPort,
113512
+ uiPort: ports.uiPort,
113513
+ defaultApiPort: HINDSIGHT_DEFAULT_API_PORT
113514
+ });
113515
+ if (recallPoolCfg) {
113516
+ const bgConflict = await preflightPorts({
113517
+ apiPort: basePorts.apiPort,
113518
+ uiPort: basePorts.apiPort
113519
+ });
113520
+ if (bgConflict) {
113521
+ const heldBy = bgConflict.holder ? ` (held by ${bgConflict.holder})` : "";
113522
+ const msg = `Refusing to start the hindsight recall split: authority port ${bgConflict.port} is occupied${heldBy}.`;
113523
+ console.log(source_default.red(` ${msg}`));
113524
+ throw new Error(`Memory backend setup failed: ${msg} Free it and re-run \`switchroom setup\`, or set hindsight.recall_pool.enabled: false to skip the split.`);
113525
+ }
113526
+ }
113527
+ const spin = spinner("Starting Hindsight Docker container...");
113528
+ let launchSecrets;
113529
+ try {
113530
+ launchSecrets = await resolveLaunchSecrets();
113531
+ const authorityEnv = recallPoolCfg ? splitContainerPerfEnv(config.hindsight?.env) : config.hindsight?.env;
113532
+ const startContainer = deps.startContainer ?? startHindsight;
113533
+ startContainer(basePorts, launchSecrets.litellmCfg, undefined, launchSecrets.resolvedLlm, hindsightConsumerMirrorDir(config), undefined, { env: authorityEnv, memLimit: config.hindsight?.mem_limit }, launchSecrets.cpAccessKey);
113534
+ if (launchSecrets.litellmCfg) {
113535
+ console.log(source_default.gray(" LiteLLM routing enabled (--network host, ANTHROPIC_BASE_URL set)."));
113536
+ }
113537
+ } catch (err) {
113538
+ spin.stop(source_default.red(`Failed to start Hindsight: ${err.message}`));
113539
+ console.log(source_default.gray(` Make sure \`switchroom apply\` has run so the auth-broker consumer socket volume (auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock) exists.`));
113540
+ throw new Error(`Memory backend setup failed: ${err.message}. Fix the cause and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
113541
+ }
113542
+ let running = isHindsightRunning(deps.dockerProbe);
113543
+ const retries = deps.readyRetries ?? HINDSIGHT_READY_RETRIES;
113544
+ for (let i2 = 0;!running && i2 < retries; i2++) {
113545
+ await sleep4(HINDSIGHT_READY_INTERVAL_MS);
113546
+ running = isHindsightRunning(deps.dockerProbe);
113547
+ }
113548
+ if (!running) {
113549
+ spin.stop(source_default.red("Hindsight container did not stay running after start"));
113550
+ throw new Error("Memory backend setup failed: switchroom-hindsight did not stay running after start. Check `docker logs switchroom-hindsight --tail 50`, then re-run `switchroom setup`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.");
113551
+ }
113552
+ spin.stop(source_default.green(`${STEP_DONE} Hindsight container started (switchroom-hindsight)`));
113553
+ if (recallPoolCfg) {
113554
+ const topo = await launchRecallPool(recallPoolCfg, publicApiPort, basePorts.apiPort, basePorts.uiPort, launchSecrets);
113555
+ if (topo === "split") {
113556
+ console.log(source_default.green(` ${STEP_DONE} Hindsight recall split started: authority (pg0 + background) on ${basePorts.apiPort}, ${recallPoolCfg.workers}-worker recall pool on ${publicApiPort}`));
113557
+ }
113558
+ }
113559
+ console.log(source_default.gray(` API: http://localhost:${publicApiPort}/mcp`));
113560
+ console.log(source_default.gray(` UI: http://localhost:${basePorts.uiPort}`));
113561
+ return { hindsightExpected: true, optedOut: false };
113562
+ }
113563
+
113564
+ // src/cli/setup.ts
112456
113565
  function writeSwitchroomYaml(configPath, text) {
112457
113566
  let mode = 420;
112458
113567
  try {
@@ -112555,7 +113664,7 @@ async function stepConfigFile(configPath, nonInteractive) {
112555
113664
  existingConfig = null;
112556
113665
  }
112557
113666
  }
112558
- if (existingConfig && existsSync85(existingConfig)) {
113667
+ if (existingConfig && existsSync86(existingConfig)) {
112559
113668
  if (!nonInteractive) {
112560
113669
  const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
112561
113670
  if (!useExisting) {
@@ -112701,7 +113810,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
112701
113810
  try {
112702
113811
  const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
112703
113812
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
112704
- if (existsSync85(vaultPath)) {
113813
+ if (existsSync86(vaultPath)) {
112705
113814
  const secrets = openVault2(passphrase, vaultPath);
112706
113815
  const key = rawToken.replace("vault:", "");
112707
113816
  const entry = secrets[key];
@@ -112729,7 +113838,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
112729
113838
  async function storeTokenInVault(config, vaultRef, token) {
112730
113839
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
112731
113840
  const key = vaultRef.replace("vault:", "");
112732
- if (!existsSync85(vaultPath)) {
113841
+ if (!existsSync86(vaultPath)) {
112733
113842
  console.log(source_default.gray(" Creating encrypted vault..."));
112734
113843
  let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
112735
113844
  if (!passphrase) {
@@ -112876,145 +113985,6 @@ async function stepCreateTopics(config, botToken, nonInteractive) {
112876
113985
  }
112877
113986
  }
112878
113987
  }
112879
- async function resolveLiteLLMForHindsight2(config, deps = {}) {
112880
- return resolveHindsightLiteLlm(config, deps);
112881
- }
112882
- function isMemoryBackendDisabled(config) {
112883
- const memoryBackend = config.memory?.backend ?? "hindsight";
112884
- return process.env.SWITCHROOM_MEMORY_BACKEND === "none" || memoryBackend === "none";
112885
- }
112886
- async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, deps = {}) {
112887
- stepHeader(6, "Memory backend", STEP_ACTIVE);
112888
- const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
112889
- if (isMemoryBackendDisabled(config)) {
112890
- console.log(source_default.gray(" Memory backend disabled (set to 'none')."));
112891
- console.log(source_default.green(` ${STEP_DONE} Skipped`));
112892
- return { hindsightExpected: false, optedOut: true };
112893
- }
112894
- let setupHindsight = true;
112895
- if (!nonInteractive) {
112896
- console.log(source_default.gray(" Hindsight will use Anthropic OAuth via the auth-broker. The fleet's"));
112897
- console.log(source_default.gray(" active account (auth.active) is shared \u2014 no OpenAI API key is needed."));
112898
- setupHindsight = await askYesNo(" Set up Hindsight memory? (recommended)", true);
112899
- }
112900
- if (!setupHindsight) {
112901
- console.log(source_default.gray(" Skipping Hindsight setup."));
112902
- console.log(source_default.green(` ${STEP_DONE} Skipped`));
112903
- return { hindsightExpected: false, optedOut: false };
112904
- }
112905
- if (process.env.HINDSIGHT_API_LLM_API_KEY) {
112906
- console.log(source_default.gray(" Note: HINDSIGHT_API_LLM_API_KEY is set in your env but is no longer used. You can remove it."));
112907
- }
112908
- try {
112909
- const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
112910
- const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
112911
- if (passphrase && existsSync85(vaultPath)) {
112912
- const existing = getStringSecret(passphrase, vaultPath, "hindsight-api-key");
112913
- if (existing) {
112914
- console.log(source_default.gray(" Note: legacy 'hindsight-api-key' is in your vault but is no longer used. You can remove it with `switchroom vault rm hindsight-api-key`."));
112915
- }
112916
- }
112917
- } catch {}
112918
- {
112919
- try {
112920
- const result = await ensureHindsightConsumer(switchroomConfigPath);
112921
- if (result.added) {
112922
- console.log(source_default.green(` ${STEP_DONE} Registered auth.consumers[${HINDSIGHT_CONSUMER_NAME}] (follows the fleet active account)`));
112923
- } else {
112924
- console.log(source_default.gray(` auth.consumers[${HINDSIGHT_CONSUMER_NAME}] already present.`));
112925
- }
112926
- } catch (err) {
112927
- console.log(source_default.yellow(` Warning: could not write auth.consumers entry: ${err.message}`));
112928
- }
112929
- }
112930
- const dockerState = probeDockerAvailability(deps.dockerProbe);
112931
- if (dockerState !== "ok") {
112932
- const noCli = dockerState === "no-cli";
112933
- const headline = noCli ? "Docker is not installed (no `docker` command on PATH)." : "Docker is installed but its daemon is not responding.";
112934
- const remedy = noCli ? "Install Docker (https://docs.docker.com/get-docker/), then re-run `switchroom setup`" : "Start Docker (on macOS: open Docker Desktop; on Linux: `sudo systemctl start docker`), then re-run `switchroom setup`";
112935
- console.log(source_default.red(` x ${headline}`));
112936
- console.log(source_default.gray(" Hindsight memory runs as a Docker container (and so does every agent)."));
112937
- console.log(source_default.gray(` ${remedy} \u2014 or re-run with SWITCHROOM_MEMORY_BACKEND=none to skip memory setup.`));
112938
- throw new Error(`Memory backend setup failed: ${headline} ${remedy}, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
112939
- }
112940
- if (isHindsightRunning(deps.dockerProbe)) {
112941
- console.log(source_default.green(` ${STEP_DONE} Hindsight container already running (switchroom-hindsight)`));
112942
- return { hindsightExpected: true, optedOut: false };
112943
- }
112944
- if (isHindsightContainerExists(deps.dockerProbe)) {
112945
- console.log(source_default.gray(" Found stopped switchroom-hindsight container, removing..."));
112946
- stopHindsight();
112947
- }
112948
- let ports;
112949
- try {
112950
- ports = await pickHindsightPorts();
112951
- } catch (err) {
112952
- console.log(source_default.red(` ${err.message}`));
112953
- throw new Error(`Memory backend setup failed: could not allocate Hindsight ports: ${err.message}. Free a port and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
112954
- }
112955
- if (ports.apiPort !== HINDSIGHT_DEFAULT_API_PORT) {
112956
- console.log(source_default.yellow(` Port ${HINDSIGHT_DEFAULT_API_PORT} is already in use; using ${ports.apiPort}/${ports.uiPort} instead.`));
112957
- }
112958
- let conflict = await preflightHindsightPorts(ports);
112959
- if (conflict) {
112960
- const heldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
112961
- console.log(source_default.yellow(` Chosen Hindsight port ${conflict.port} is occupied${heldBy}; selecting a free port instead of crash-looping.`));
112962
- try {
112963
- ports = await pickHindsightPorts();
112964
- } catch (err) {
112965
- console.log(source_default.red(` ${err.message}`));
112966
- throw new Error(`Memory backend setup failed: could not allocate Hindsight ports after reassignment: ${err.message}. Free a port and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
112967
- }
112968
- conflict = await preflightHindsightPorts(ports);
112969
- if (conflict) {
112970
- const stillHeldBy = conflict.holder ? ` (held by ${conflict.holder})` : "";
112971
- const msg = `Refusing to start Hindsight: port ${conflict.port} is still occupied${stillHeldBy} after reassignment.`;
112972
- console.log(source_default.red(` ${msg}`));
112973
- throw new Error(`Memory backend setup failed: ${msg} Free it and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
112974
- }
112975
- console.log(source_default.green(` Reassigned Hindsight to port ${ports.apiPort}/${ports.uiPort}.`));
112976
- }
112977
- const spin = spinner("Starting Hindsight Docker container...");
112978
- try {
112979
- const { litellm: litellmCfg, droppedRef: litellmDroppedRef } = await resolveLiteLLMForHindsight2(config, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
112980
- if (litellmDroppedRef) {
112981
- console.log(source_default.yellow(` ! ${hindsightLiteLlmDroppedKeyWarning(litellmDroppedRef)}`));
112982
- }
112983
- const cpAccessKey = await resolveHindsightCpAccessKey(config);
112984
- if (!cpAccessKey) {
112985
- console.log(source_default.yellow(` ! ${HINDSIGHT_CP_NO_ACCESS_KEY_WARNING}`));
112986
- }
112987
- const resolvedLlm = await resolveHindsightLlmSecrets(config.hindsight?.llm, deps.getViaBrokerStructured ? { getViaBrokerStructured: deps.getViaBrokerStructured } : {});
112988
- for (const drop of await diffDroppedHindsightLlmVaultKeys(config.hindsight?.llm, resolvedLlm)) {
112989
- console.log(source_default.yellow(` ! ${hindsightLlmDroppedKeyWarning(drop)}`));
112990
- }
112991
- const startContainer = deps.startContainer ?? startHindsight;
112992
- startContainer(ports, litellmCfg, undefined, resolvedLlm, hindsightConsumerMirrorDir(config), undefined, { env: config.hindsight?.env, memLimit: config.hindsight?.mem_limit }, cpAccessKey);
112993
- if (litellmCfg) {
112994
- console.log(source_default.gray(" LiteLLM routing enabled (--network host, ANTHROPIC_BASE_URL set)."));
112995
- }
112996
- } catch (err) {
112997
- spin.stop(source_default.red(`Failed to start Hindsight: ${err.message}`));
112998
- console.log(source_default.gray(` Make sure \`switchroom apply\` has run so the auth-broker consumer socket volume (auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock) exists.`));
112999
- throw new Error(`Memory backend setup failed: ${err.message}. Fix the cause and re-run \`switchroom setup\`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.`);
113000
- }
113001
- let running = isHindsightRunning(deps.dockerProbe);
113002
- const retries = deps.readyRetries ?? HINDSIGHT_READY_RETRIES;
113003
- for (let i2 = 0;!running && i2 < retries; i2++) {
113004
- await sleep4(HINDSIGHT_READY_INTERVAL_MS);
113005
- running = isHindsightRunning(deps.dockerProbe);
113006
- }
113007
- if (!running) {
113008
- spin.stop(source_default.red("Hindsight container did not stay running after start"));
113009
- throw new Error("Memory backend setup failed: switchroom-hindsight did not stay running after start. Check `docker logs switchroom-hindsight --tail 50`, then re-run `switchroom setup`, or set SWITCHROOM_MEMORY_BACKEND=none to skip it.");
113010
- }
113011
- spin.stop(source_default.green(`${STEP_DONE} Hindsight container started (switchroom-hindsight)`));
113012
- console.log(source_default.gray(` API: http://localhost:${ports.apiPort}/mcp`));
113013
- console.log(source_default.gray(` UI: http://localhost:${ports.uiPort}`));
113014
- return { hindsightExpected: true, optedOut: false };
113015
- }
113016
- var HINDSIGHT_READY_RETRIES = 5;
113017
- var HINDSIGHT_READY_INTERVAL_MS = 1000;
113018
113988
  function stepVoiceEngine() {
113019
113989
  stepHeader(7, "Voice engine", STEP_ACTIVE);
113020
113990
  let caps;
@@ -113151,13 +114121,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
113151
114121
  return;
113152
114122
  }
113153
114123
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
113154
- if (!existsSync85(vaultPath)) {
114124
+ if (!existsSync86(vaultPath)) {
113155
114125
  console.log(source_default.gray(" Skipping (vault not created yet)."));
113156
114126
  return;
113157
114127
  }
113158
114128
  const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
113159
114129
  const credPath = resolvePath(credPathRaw);
113160
- if (config.vault?.broker?.autoUnlock === true && existsSync85(credPath)) {
114130
+ if (config.vault?.broker?.autoUnlock === true && existsSync86(credPath)) {
113161
114131
  console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
113162
114132
  return;
113163
114133
  }
@@ -113235,7 +114205,7 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
113235
114205
  }
113236
114206
  }
113237
114207
  function persistApprovalAuthTelegramId(configPath) {
113238
- if (!existsSync85(configPath)) {
114208
+ if (!existsSync86(configPath)) {
113239
114209
  throw new ConfigError(`Cannot persist vault.broker.approvalAuth: config not found at ${configPath}`);
113240
114210
  }
113241
114211
  const content = readFileSync79(configPath, "utf-8");
@@ -113246,7 +114216,7 @@ function persistApprovalAuthTelegramId(configPath) {
113246
114216
  return result.kind;
113247
114217
  }
113248
114218
  function persistDangerousMode(config, configPath) {
113249
- if (!existsSync85(configPath)) {
114219
+ if (!existsSync86(configPath)) {
113250
114220
  throw new ConfigError(`Cannot persist dangerous_mode: config not found at ${configPath}`);
113251
114221
  }
113252
114222
  let content = readFileSync79(configPath, "utf-8");
@@ -113357,7 +114327,7 @@ var START_OUTPUT_TAIL_LINES = 12;
113357
114327
  function defaultStartAgent(name) {
113358
114328
  let out = "";
113359
114329
  try {
113360
- out = execFileSync26("switchroom", ["agent", "start", name], {
114330
+ out = execFileSync27("switchroom", ["agent", "start", name], {
113361
114331
  stdio: ["ignore", "pipe", "pipe"],
113362
114332
  encoding: "utf-8"
113363
114333
  });
@@ -113385,7 +114355,7 @@ async function stepVerification(config, nonInteractive, deps = {}, expect = {})
113385
114355
  const findings = [];
113386
114356
  let applyPending = false;
113387
114357
  if (!nonInteractive && agentNames.length > 0) {
113388
- const composeExists = (deps.composeFileExists ?? (() => existsSync85(composeFilePath())))();
114358
+ const composeExists = (deps.composeFileExists ?? (() => existsSync86(composeFilePath())))();
113389
114359
  if (!composeExists) {
113390
114360
  applyPending = true;
113391
114361
  log(source_default.gray(" Not started: `switchroom apply` has not generated the compose file yet."));
@@ -113482,7 +114452,7 @@ init_source();
113482
114452
  init_loader();
113483
114453
  init_lifecycle();
113484
114454
  init_compose_env();
113485
- import { existsSync as existsSync88, mkdirSync as mkdirSync50, readFileSync as readFileSync82, realpathSync as realpathSync8, statSync as statSync49, chownSync as chownSync10 } from "node:fs";
114455
+ import { existsSync as existsSync89, mkdirSync as mkdirSync50, readFileSync as readFileSync82, realpathSync as realpathSync8, statSync as statSync49, chownSync as chownSync10 } from "node:fs";
113486
114456
  import { spawnSync as spawnSync20 } from "node:child_process";
113487
114457
  import { join as join92, dirname as dirname39 } from "node:path";
113488
114458
  import { homedir as homedir48 } from "node:os";
@@ -113929,7 +114899,7 @@ import {
113929
114899
  closeSync as closeSync15,
113930
114900
  copyFileSync as copyFileSync12,
113931
114901
  createWriteStream,
113932
- existsSync as existsSync86,
114902
+ existsSync as existsSync87,
113933
114903
  lstatSync as lstatSync13,
113934
114904
  mkdirSync as mkdirSync48,
113935
114905
  openSync as openSync15,
@@ -114012,7 +114982,7 @@ function defaultSelfUpdateIO() {
114012
114982
  rmSync15(path7, { force: true });
114013
114983
  },
114014
114984
  exists(path7) {
114015
- return existsSync86(path7);
114985
+ return existsSync87(path7);
114016
114986
  },
114017
114987
  dirname(path7) {
114018
114988
  return dirname37(path7);
@@ -114067,7 +115037,7 @@ init_install_cron2();
114067
115037
 
114068
115038
  // src/host-cli-self-heal/install-timer.ts
114069
115039
  import { spawnSync as spawnSync19 } from "node:child_process";
114070
- import { existsSync as existsSync87, mkdirSync as mkdirSync49, readFileSync as readFileSync81, renameSync as renameSync22, writeFileSync as writeFileSync34 } from "node:fs";
115040
+ import { existsSync as existsSync88, mkdirSync as mkdirSync49, readFileSync as readFileSync81, renameSync as renameSync22, writeFileSync as writeFileSync34 } from "node:fs";
114071
115041
  import { dirname as dirname38 } from "node:path";
114072
115042
  var SERVICE_PATH = "/etc/systemd/system/switchroom-self-heal.service";
114073
115043
  var TIMER_PATH = "/etc/systemd/system/switchroom-self-heal.timer";
@@ -114114,7 +115084,7 @@ function renderTimer() {
114114
115084
  `;
114115
115085
  }
114116
115086
  function writeUnitIdempotent(path7, content) {
114117
- if (existsSync87(path7)) {
115087
+ if (existsSync88(path7)) {
114118
115088
  try {
114119
115089
  if (readFileSync81(path7, "utf8") === content)
114120
115090
  return "unchanged";
@@ -114145,7 +115115,7 @@ function resolveOperatorUser(env2) {
114145
115115
  }
114146
115116
  function installSelfHealTimer(deps = {}) {
114147
115117
  const env2 = deps.env ?? process.env;
114148
- const systemdBooted = deps.systemdBooted ?? (() => existsSync87(SYSTEMD_MARKER));
115118
+ const systemdBooted = deps.systemdBooted ?? (() => existsSync88(SYSTEMD_MARKER));
114149
115119
  const geteuid = deps.geteuid ?? (() => typeof process.geteuid === "function" ? process.geteuid() : undefined);
114150
115120
  const homeForUser = deps.homeForUser ?? homeFromPasswd;
114151
115121
  const servicePath = deps.servicePath ?? SERVICE_PATH;
@@ -114230,7 +115200,7 @@ var PACKAGED_INSTALL_KINDS = new Set([
114230
115200
  function runningFromSwitchroomCheckout(scriptPath) {
114231
115201
  let dir = dirname39(scriptPath);
114232
115202
  for (let i2 = 0;i2 < 12; i2++) {
114233
- if (existsSync88(join92(dir, ".git"))) {
115203
+ if (existsSync89(join92(dir, ".git"))) {
114234
115204
  try {
114235
115205
  const pkg = JSON.parse(readFileSync82(join92(dir, "package.json"), "utf-8"));
114236
115206
  if (pkg.name === "switchroom")
@@ -114299,7 +115269,7 @@ function planUpdate(opts) {
114299
115269
  bundleDir: import.meta.dirname,
114300
115270
  execPath: process.execPath,
114301
115271
  scriptPath,
114302
- inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync88("/.dockerenv")
115272
+ inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync89("/.dockerenv")
114303
115273
  });
114304
115274
  if (detection.kind !== "static-binary") {
114305
115275
  say(source_default.gray(` host CLI not self-updated: ${detection.reason}
@@ -114382,7 +115352,7 @@ function planUpdate(opts) {
114382
115352
  steps.push({
114383
115353
  name: "pull-images",
114384
115354
  description: "Pull broker / kernel / agent images from GHCR",
114385
- skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync88(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
115355
+ skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync89(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
114386
115356
  run: () => {
114387
115357
  const r = runner("docker", [
114388
115358
  "compose",
@@ -114617,7 +115587,7 @@ function planUpdate(opts) {
114617
115587
  bundleDir: import.meta.dirname,
114618
115588
  execPath: process.execPath,
114619
115589
  scriptPath,
114620
- inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync88("/.dockerenv")
115590
+ inContainer: process.env.SWITCHROOM_HOSTD_CONTEXT === "1" || existsSync89("/.dockerenv")
114621
115591
  });
114622
115592
  const detail = `no shipped skills/ payload found for this install ` + `(${detection.kind}; ${describeShippedAssetSearch(resolution)})`;
114623
115593
  if (PACKAGED_INSTALL_KINDS.has(detection.kind)) {
@@ -114657,10 +115627,10 @@ function planUpdate(opts) {
114657
115627
  if (opts.syncBundledSkillsFn)
114658
115628
  return;
114659
115629
  const dest = join92(homedir48(), ".switchroom", "skills", "_bundled");
114660
- if (!existsSync88(dest)) {
115630
+ if (!existsSync89(dest)) {
114661
115631
  return;
114662
115632
  }
114663
- const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync88(join92(dest, key)));
115633
+ const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync89(join92(dest, key)));
114664
115634
  if (missing.length > 0) {
114665
115635
  throw new Error(`verify-bundled-skills: builtin default skill(s) missing from the pool after sync: ` + `${missing.join(", ")}. These ship in the CLI package and must exist in ${dest}. ` + `This is a broken sync or a packaging regression \u2014 the pool is not converged.`);
114666
115636
  }
@@ -114775,7 +115745,7 @@ function defaultStatusProbe(composePath) {
114775
115745
  let dir = dirname39(scriptPath);
114776
115746
  for (let i2 = 0;i2 < 8; i2++) {
114777
115747
  const pkgPath = join92(dir, "package.json");
114778
- if (existsSync88(pkgPath)) {
115748
+ if (existsSync89(pkgPath)) {
114779
115749
  try {
114780
115750
  const pkg = JSON.parse(readFileSync82(pkgPath, "utf-8"));
114781
115751
  if (typeof pkg.version === "string")
@@ -114798,7 +115768,7 @@ function defaultStatusProbe(composePath) {
114798
115768
  warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
114799
115769
  }
114800
115770
  const services = [];
114801
- if (!existsSync88(composePath)) {
115771
+ if (!existsSync89(composePath)) {
114802
115772
  warnings.push(`compose file not found at ${composePath}; service status unknown`);
114803
115773
  return { cliVersion, cliBuiltAt, services, warnings };
114804
115774
  }
@@ -115066,7 +116036,7 @@ import { homedir as homedir50 } from "node:os";
115066
116036
 
115067
116037
  // src/cli/rollout-pin-journal.ts
115068
116038
  import {
115069
- existsSync as existsSync89,
116039
+ existsSync as existsSync90,
115070
116040
  readFileSync as readFileSync83,
115071
116041
  writeFileSync as writeFileSync35,
115072
116042
  renameSync as renameSync23,
@@ -115130,7 +116100,7 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
115130
116100
  try {
115131
116101
  raw = readFileSync83(p, "utf8");
115132
116102
  } catch (e) {
115133
- if (existsSync89(p)) {
116103
+ if (existsSync90(p)) {
115134
116104
  warn(`\u26a0\ufe0f rollout pin journal: ${p} exists but could not be read ` + `(${e.message}). A provisional \`release.pin\` may be ` + `uncommitted \u2014 verify it host-side before the next reconcile.
115135
116105
  `);
115136
116106
  }
@@ -115202,7 +116172,7 @@ function commitPinPersist(configPath) {
115202
116172
  unlinkSync17(p);
115203
116173
  return null;
115204
116174
  } catch (e) {
115205
- if (!existsSync89(p))
116175
+ if (!existsSync90(p))
115206
116176
  return null;
115207
116177
  return `rollout pin journal: FAILED to clear ${p} after a SUCCESSFUL roll ` + `(${e.message}). Delete it host-side \u2014 while it exists, ` + `recovery may revert a proven \`release.pin\`.`;
115208
116178
  }
@@ -116130,7 +117100,7 @@ init_helpers();
116130
117100
  init_lifecycle();
116131
117101
  init_resolve_version();
116132
117102
  import { execSync as execSync3 } from "node:child_process";
116133
- import { existsSync as existsSync90, readFileSync as readFileSync85 } from "node:fs";
117103
+ import { existsSync as existsSync91, readFileSync as readFileSync85 } from "node:fs";
116134
117104
  import { dirname as dirname41, join as join94 } from "node:path";
116135
117105
  function getClaudeCodeVersion() {
116136
117106
  try {
@@ -116182,10 +117152,10 @@ function locateSwitchroomInstallDir() {
116182
117152
  let dir = import.meta.dirname;
116183
117153
  for (let i2 = 0;i2 < 10 && dir && dir !== "/"; i2++) {
116184
117154
  const pkgPath = join94(dir, "package.json");
116185
- if (existsSync90(pkgPath)) {
117155
+ if (existsSync91(pkgPath)) {
116186
117156
  try {
116187
117157
  const pkg = JSON.parse(readFileSync85(pkgPath, "utf-8"));
116188
- if (pkg.name === "switchroom" && existsSync90(join94(dir, ".git"))) {
117158
+ if (pkg.name === "switchroom" && existsSync91(join94(dir, ".git"))) {
116189
117159
  return dir;
116190
117160
  }
116191
117161
  } catch {}
@@ -116366,7 +117336,7 @@ init_merge();
116366
117336
 
116367
117337
  // src/agents/session-retention.ts
116368
117338
  import {
116369
- existsSync as existsSync91,
117339
+ existsSync as existsSync92,
116370
117340
  readdirSync as readdirSync31,
116371
117341
  statSync as statSync52,
116372
117342
  unlinkSync as unlinkSync18
@@ -116377,7 +117347,7 @@ var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
116377
117347
  var MIN_KEEP = 2;
116378
117348
  function collectSessionJsonl(claudeConfigDir) {
116379
117349
  const projects = join95(claudeConfigDir, "projects");
116380
- if (!existsSync91(projects))
117350
+ if (!existsSync92(projects))
116381
117351
  return [];
116382
117352
  const found = [];
116383
117353
  const walk2 = (dir) => {
@@ -116509,7 +117479,7 @@ function registerHandoffCommand(program3) {
116509
117479
  // src/issues/store.ts
116510
117480
  import {
116511
117481
  closeSync as closeSync16,
116512
- existsSync as existsSync92,
117482
+ existsSync as existsSync93,
116513
117483
  mkdirSync as mkdirSync52,
116514
117484
  openSync as openSync16,
116515
117485
  readdirSync as readdirSync32,
@@ -116548,7 +117518,7 @@ var ISSUES_FILE = "issues.jsonl";
116548
117518
  var ISSUES_LOCK = "issues.lock";
116549
117519
  function readAll(stateDir) {
116550
117520
  const path7 = join96(stateDir, ISSUES_FILE);
116551
- if (!existsSync92(path7))
117521
+ if (!existsSync93(path7))
116552
117522
  return [];
116553
117523
  let raw;
116554
117524
  try {
@@ -116625,7 +117595,7 @@ function record(stateDir, input, nowFn = Date.now) {
116625
117595
  });
116626
117596
  }
116627
117597
  function resolve51(stateDir, fingerprint, nowFn = Date.now) {
116628
- if (!existsSync92(join96(stateDir, ISSUES_FILE)))
117598
+ if (!existsSync93(join96(stateDir, ISSUES_FILE)))
116629
117599
  return 0;
116630
117600
  return withLock(stateDir, () => {
116631
117601
  const all = readAll(stateDir);
@@ -116643,7 +117613,7 @@ function resolve51(stateDir, fingerprint, nowFn = Date.now) {
116643
117613
  });
116644
117614
  }
116645
117615
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
116646
- if (!existsSync92(join96(stateDir, ISSUES_FILE)))
117616
+ if (!existsSync93(join96(stateDir, ISSUES_FILE)))
116647
117617
  return 0;
116648
117618
  return withLock(stateDir, () => {
116649
117619
  const all = readAll(stateDir);
@@ -116661,7 +117631,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
116661
117631
  });
116662
117632
  }
116663
117633
  function prune(stateDir, opts = {}) {
116664
- if (!existsSync92(join96(stateDir, ISSUES_FILE)))
117634
+ if (!existsSync93(join96(stateDir, ISSUES_FILE)))
116665
117635
  return 0;
116666
117636
  return withLock(stateDir, () => {
116667
117637
  const all = readAll(stateDir);
@@ -117011,14 +117981,14 @@ function relTime(deltaMs) {
117011
117981
 
117012
117982
  // src/cli/deps.ts
117013
117983
  init_source();
117014
- import { existsSync as existsSync95 } from "node:fs";
117984
+ import { existsSync as existsSync96 } from "node:fs";
117015
117985
  import { homedir as homedir53 } from "node:os";
117016
117986
  import { join as join99, resolve as resolve52 } from "node:path";
117017
117987
 
117018
117988
  // src/deps/python.ts
117019
117989
  import { createHash as createHash18 } from "node:crypto";
117020
117990
  import {
117021
- existsSync as existsSync93,
117991
+ existsSync as existsSync94,
117022
117992
  mkdirSync as mkdirSync53,
117023
117993
  readFileSync as readFileSync87,
117024
117994
  rmSync as rmSync16,
@@ -117026,7 +117996,7 @@ import {
117026
117996
  } from "node:fs";
117027
117997
  import { dirname as dirname42, join as join97 } from "node:path";
117028
117998
  import { homedir as homedir51 } from "node:os";
117029
- import { execFileSync as execFileSync27 } from "node:child_process";
117999
+ import { execFileSync as execFileSync28 } from "node:child_process";
117030
118000
 
117031
118001
  class PythonEnvError extends Error {
117032
118002
  stderr;
@@ -117046,7 +118016,7 @@ function ensurePythonEnv(opts) {
117046
118016
  const { skillName, requirementsPath, force = false } = opts;
117047
118017
  const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
117048
118018
  const hostPython = opts.pythonBin ?? "python3";
117049
- if (!existsSync93(requirementsPath)) {
118019
+ if (!existsSync94(requirementsPath)) {
117050
118020
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
117051
118021
  }
117052
118022
  const venvDir = join97(cacheRoot, skillName);
@@ -117055,7 +118025,7 @@ function ensurePythonEnv(opts) {
117055
118025
  const pythonBin = join97(binDir, "python");
117056
118026
  const pipBin = join97(binDir, "pip");
117057
118027
  const targetHash = hashFile(requirementsPath);
117058
- if (!force && existsSync93(stampPath) && existsSync93(pythonBin)) {
118028
+ if (!force && existsSync94(stampPath) && existsSync94(pythonBin)) {
117059
118029
  const existingHash = readFileSync87(stampPath, "utf8").trim();
117060
118030
  if (existingHash === targetHash) {
117061
118031
  return {
@@ -117068,12 +118038,12 @@ function ensurePythonEnv(opts) {
117068
118038
  };
117069
118039
  }
117070
118040
  }
117071
- if (existsSync93(venvDir)) {
118041
+ if (existsSync94(venvDir)) {
117072
118042
  rmSync16(venvDir, { recursive: true, force: true });
117073
118043
  }
117074
118044
  mkdirSync53(dirname42(venvDir), { recursive: true });
117075
118045
  try {
117076
- execFileSync27(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
118046
+ execFileSync28(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
117077
118047
  } catch (err) {
117078
118048
  const e = err;
117079
118049
  throw new PythonEnvError(`Failed to create venv for skill "${skillName}" with ${hostPython}: ${e.message}`, e.stderr?.toString());
@@ -117085,7 +118055,7 @@ function ensurePythonEnv(opts) {
117085
118055
  delete childEnv.PIP_TARGET;
117086
118056
  delete childEnv.PIP_PREFIX;
117087
118057
  delete childEnv.PYTHONUSERBASE;
117088
- execFileSync27(pipBin, ["install", "--disable-pip-version-check", "-r", requirementsPath], { stdio: "pipe", env: childEnv });
118058
+ execFileSync28(pipBin, ["install", "--disable-pip-version-check", "-r", requirementsPath], { stdio: "pipe", env: childEnv });
117089
118059
  } catch (err) {
117090
118060
  const e = err;
117091
118061
  throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
@@ -117106,7 +118076,7 @@ function ensurePythonEnv(opts) {
117106
118076
  import { createHash as createHash19 } from "node:crypto";
117107
118077
  import {
117108
118078
  copyFileSync as copyFileSync13,
117109
- existsSync as existsSync94,
118079
+ existsSync as existsSync95,
117110
118080
  mkdirSync as mkdirSync54,
117111
118081
  readFileSync as readFileSync88,
117112
118082
  rmSync as rmSync17,
@@ -117114,7 +118084,7 @@ import {
117114
118084
  } from "node:fs";
117115
118085
  import { dirname as dirname43, join as join98 } from "node:path";
117116
118086
  import { homedir as homedir52 } from "node:os";
117117
- import { execFileSync as execFileSync28 } from "node:child_process";
118087
+ import { execFileSync as execFileSync29 } from "node:child_process";
117118
118088
 
117119
118089
  class NodeEnvError extends Error {
117120
118090
  stderr;
@@ -117146,7 +118116,7 @@ function hashDepInputs(packageJsonPath) {
117146
118116
  hasher.update(readFileSync88(packageJsonPath));
117147
118117
  for (const lockName of ALL_LOCKFILES) {
117148
118118
  const lockPath = join98(sourceDir, lockName);
117149
- if (existsSync94(lockPath)) {
118119
+ if (existsSync95(lockPath)) {
117150
118120
  hasher.update(`
117151
118121
  `);
117152
118122
  hasher.update(lockName);
@@ -117161,7 +118131,7 @@ function ensureNodeEnv(opts) {
117161
118131
  const { skillName, packageJsonPath, force = false } = opts;
117162
118132
  const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
117163
118133
  const installer = opts.installer ?? "bun";
117164
- if (!existsSync94(packageJsonPath)) {
118134
+ if (!existsSync95(packageJsonPath)) {
117165
118135
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
117166
118136
  }
117167
118137
  const sourceDir = dirname43(packageJsonPath);
@@ -117170,7 +118140,7 @@ function ensureNodeEnv(opts) {
117170
118140
  const nodeModulesDir = join98(envDir, "node_modules");
117171
118141
  const binDir = join98(nodeModulesDir, ".bin");
117172
118142
  const targetHash = hashDepInputs(packageJsonPath);
117173
- if (!force && existsSync94(stampPath) && existsSync94(nodeModulesDir)) {
118143
+ if (!force && existsSync95(stampPath) && existsSync95(nodeModulesDir)) {
117174
118144
  const existingHash = readFileSync88(stampPath, "utf8").trim();
117175
118145
  if (existingHash === targetHash) {
117176
118146
  return {
@@ -117182,7 +118152,7 @@ function ensureNodeEnv(opts) {
117182
118152
  };
117183
118153
  }
117184
118154
  }
117185
- if (existsSync94(envDir)) {
118155
+ if (existsSync95(envDir)) {
117186
118156
  rmSync17(envDir, { recursive: true, force: true });
117187
118157
  }
117188
118158
  mkdirSync54(envDir, { recursive: true });
@@ -117190,7 +118160,7 @@ function ensureNodeEnv(opts) {
117190
118160
  let copiedLockfile = false;
117191
118161
  for (const lockName of LOCKFILES_FOR[installer]) {
117192
118162
  const lockPath = join98(sourceDir, lockName);
117193
- if (existsSync94(lockPath)) {
118163
+ if (existsSync95(lockPath)) {
117194
118164
  copyFileSync13(lockPath, join98(envDir, lockName));
117195
118165
  copiedLockfile = true;
117196
118166
  }
@@ -117198,10 +118168,10 @@ function ensureNodeEnv(opts) {
117198
118168
  try {
117199
118169
  if (installer === "bun") {
117200
118170
  const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
117201
- execFileSync28("bun", args, { cwd: envDir, stdio: "pipe" });
118171
+ execFileSync29("bun", args, { cwd: envDir, stdio: "pipe" });
117202
118172
  } else {
117203
118173
  const args = copiedLockfile ? ["ci"] : ["install"];
117204
- execFileSync28("npm", args, { cwd: envDir, stdio: "pipe" });
118174
+ execFileSync29("npm", args, { cwd: envDir, stdio: "pipe" });
117205
118175
  }
117206
118176
  } catch (err) {
117207
118177
  const e = err;
@@ -117226,22 +118196,22 @@ function registerDepsCommand(program3) {
117226
118196
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
117227
118197
  deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
117228
118198
  const skillsRoot = builtinSkillsRoot();
117229
- if (!existsSync95(skillsRoot)) {
118199
+ if (!existsSync96(skillsRoot)) {
117230
118200
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
117231
118201
  process.exit(1);
117232
118202
  }
117233
118203
  const skillDir = join99(skillsRoot, skill);
117234
- if (!existsSync95(skillDir)) {
118204
+ if (!existsSync96(skillDir)) {
117235
118205
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
117236
118206
  process.exit(1);
117237
118207
  }
117238
118208
  const requirementsPath = join99(skillDir, "requirements.txt");
117239
118209
  const packageJsonPath = join99(skillDir, "package.json");
117240
- const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync95(requirementsPath));
117241
- const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync95(packageJsonPath));
118210
+ const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync96(requirementsPath));
118211
+ const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync96(packageJsonPath));
117242
118212
  let did = 0;
117243
118213
  if (wantPython) {
117244
- if (!existsSync95(requirementsPath)) {
118214
+ if (!existsSync96(requirementsPath)) {
117245
118215
  console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
117246
118216
  process.exit(1);
117247
118217
  }
@@ -117265,7 +118235,7 @@ function registerDepsCommand(program3) {
117265
118235
  }
117266
118236
  }
117267
118237
  if (wantNode) {
117268
- if (!existsSync95(packageJsonPath)) {
118238
+ if (!existsSync96(packageJsonPath)) {
117269
118239
  console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
117270
118240
  process.exit(1);
117271
118241
  }
@@ -117298,7 +118268,7 @@ function registerDepsCommand(program3) {
117298
118268
  // src/cli/workspace.ts
117299
118269
  init_helpers();
117300
118270
  init_loader();
117301
- import { existsSync as existsSync96 } from "node:fs";
118271
+ import { existsSync as existsSync97 } from "node:fs";
117302
118272
  import { resolve as resolve53, sep as sep5 } from "node:path";
117303
118273
  import { spawnSync as spawnSync23 } from "node:child_process";
117304
118274
 
@@ -118075,7 +119045,7 @@ function registerWorkspaceCommand(program3) {
118075
119045
  if (!dir)
118076
119046
  return;
118077
119047
  const gitDir = resolve53(dir, ".git");
118078
- if (!existsSync96(gitDir)) {
119048
+ if (!existsSync97(gitDir)) {
118079
119049
  process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
118080
119050
  `);
118081
119051
  return;
@@ -118129,7 +119099,7 @@ function registerWorkspaceCommand(program3) {
118129
119099
  if (!dir)
118130
119100
  return;
118131
119101
  const gitDir = resolve53(dir, ".git");
118132
- if (!existsSync96(gitDir)) {
119102
+ if (!existsSync97(gitDir)) {
118133
119103
  process.stdout.write(`Workspace is not a git repository.
118134
119104
  `);
118135
119105
  return;
@@ -118154,7 +119124,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
118154
119124
  const agentsDir = resolveAgentsDir(config);
118155
119125
  const agentDir = resolve53(agentsDir, agentName);
118156
119126
  const dir = resolveAgentWorkspaceDir(agentDir);
118157
- if (!existsSync96(dir)) {
119127
+ if (!existsSync97(dir)) {
118158
119128
  process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
118159
119129
  `);
118160
119130
  return;
@@ -118190,7 +119160,7 @@ function safeParseInt(value, fallback) {
118190
119160
  init_helpers();
118191
119161
  init_loader();
118192
119162
  init_merge();
118193
- import { copyFileSync as copyFileSync14, existsSync as existsSync97, readFileSync as readFileSync89, writeFileSync as writeFileSync39 } from "node:fs";
119163
+ import { copyFileSync as copyFileSync14, existsSync as existsSync98, readFileSync as readFileSync89, writeFileSync as writeFileSync39 } from "node:fs";
118194
119164
  import { join as join100, resolve as resolve54 } from "node:path";
118195
119165
  init_scaffold();
118196
119166
  init_profiles();
@@ -118208,7 +119178,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
118208
119178
  const agentsDir = resolveAgentsDir(config);
118209
119179
  const agentDir = resolve54(agentsDir, agentName);
118210
119180
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
118211
- if (!existsSync97(workspaceDir)) {
119181
+ if (!existsSync98(workspaceDir)) {
118212
119182
  console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
118213
119183
  process.exit(1);
118214
119184
  }
@@ -118234,7 +119204,7 @@ function registerSoulCommand(program3) {
118234
119204
  const t = resolveSoulTargetOrExit(program3, agentName);
118235
119205
  if (!t)
118236
119206
  return;
118237
- if (!existsSync97(t.soulPath)) {
119207
+ if (!existsSync98(t.soulPath)) {
118238
119208
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
118239
119209
  process.exit(1);
118240
119210
  }
@@ -118249,7 +119219,7 @@ function registerSoulCommand(program3) {
118249
119219
  console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
118250
119220
  process.exit(1);
118251
119221
  }
118252
- const exists = existsSync97(t.soulPath);
119222
+ const exists = existsSync98(t.soulPath);
118253
119223
  if (exists && !opts.yes) {
118254
119224
  if (!isInteractive()) {
118255
119225
  console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
@@ -118264,7 +119234,7 @@ function registerSoulCommand(program3) {
118264
119234
  let backupPath;
118265
119235
  if (exists) {
118266
119236
  backupPath = `${t.soulPath}.bak`;
118267
- if (existsSync97(backupPath)) {
119237
+ if (existsSync98(backupPath)) {
118268
119238
  backupPath = `${t.soulPath}.bak.${Date.now()}`;
118269
119239
  }
118270
119240
  copyFileSync14(t.soulPath, backupPath);
@@ -118283,7 +119253,7 @@ function registerSoulCommand(program3) {
118283
119253
  // src/cli/debug.ts
118284
119254
  init_helpers();
118285
119255
  init_loader();
118286
- import { existsSync as existsSync98, readFileSync as readFileSync90, readdirSync as readdirSync33, statSync as statSync54 } from "node:fs";
119256
+ import { existsSync as existsSync99, readFileSync as readFileSync90, readdirSync as readdirSync33, statSync as statSync54 } from "node:fs";
118287
119257
  import { resolve as resolve55, join as join101 } from "node:path";
118288
119258
  import { createHash as createHash20 } from "node:crypto";
118289
119259
  init_merge();
@@ -118296,7 +119266,7 @@ function estimateTokens(bytes) {
118296
119266
  }
118297
119267
  function readMcpServerNames(agentDir) {
118298
119268
  const mcpPath = join101(agentDir, ".mcp.json");
118299
- if (!existsSync98(mcpPath))
119269
+ if (!existsSync99(mcpPath))
118300
119270
  return [];
118301
119271
  try {
118302
119272
  const parsed = JSON.parse(readFileSync90(mcpPath, "utf-8"));
@@ -118310,7 +119280,7 @@ function sha2562(content) {
118310
119280
  }
118311
119281
  function findLatestTranscriptJsonl(claudeConfigDir) {
118312
119282
  const projectsDir = join101(claudeConfigDir, "projects");
118313
- if (!existsSync98(projectsDir))
119283
+ if (!existsSync99(projectsDir))
118314
119284
  return;
118315
119285
  try {
118316
119286
  const entries = readdirSync33(projectsDir, { withFileTypes: true });
@@ -118320,7 +119290,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
118320
119290
  continue;
118321
119291
  const projectPath = join101(projectsDir, entry.name);
118322
119292
  const transcriptPath = join101(projectPath, "transcript.jsonl");
118323
- if (!existsSync98(transcriptPath))
119293
+ if (!existsSync99(transcriptPath))
118324
119294
  continue;
118325
119295
  const stat3 = statSync54(transcriptPath);
118326
119296
  if (!latest || stat3.mtimeMs > latest.mtime) {
@@ -118383,7 +119353,7 @@ function registerDebugCommand(program3) {
118383
119353
  }
118384
119354
  const agentsDir = resolveAgentsDir(config);
118385
119355
  const agentDir = resolve55(agentsDir, agentName);
118386
- if (!existsSync98(agentDir)) {
119356
+ if (!existsSync99(agentDir)) {
118387
119357
  console.error(`Agent directory not found: ${agentDir}`);
118388
119358
  process.exit(1);
118389
119359
  }
@@ -118438,7 +119408,7 @@ function registerDebugCommand(program3) {
118438
119408
  }
118439
119409
  console.log(`=== Append System Prompt (per-session) ===
118440
119410
  `);
118441
- const handoffContent = existsSync98(handoffPath) ? readFileSync90(handoffPath, "utf-8") : "";
119411
+ const handoffContent = existsSync99(handoffPath) ? readFileSync90(handoffPath, "utf-8") : "";
118442
119412
  if (handoffContent.trim().length > 0) {
118443
119413
  console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
118444
119414
  console.log(handoffContent);
@@ -118449,7 +119419,7 @@ function registerDebugCommand(program3) {
118449
119419
  }
118450
119420
  console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
118451
119421
  `);
118452
- const claudeMdContent = existsSync98(claudeMdPath) ? readFileSync90(claudeMdPath, "utf-8") : "";
119422
+ const claudeMdContent = existsSync99(claudeMdPath) ? readFileSync90(claudeMdPath, "utf-8") : "";
118453
119423
  if (claudeMdContent.trim().length > 0) {
118454
119424
  console.log(`(${formatBytes(claudeMdContent.length)})`);
118455
119425
  console.log(claudeMdContent);
@@ -118460,7 +119430,7 @@ function registerDebugCommand(program3) {
118460
119430
  }
118461
119431
  console.log(`=== Persona (SOUL.md) ===
118462
119432
  `);
118463
- const soulMdContent = existsSync98(soulMdPath) ? readFileSync90(soulMdPath, "utf-8") : existsSync98(workspaceSoulMdPath) ? readFileSync90(workspaceSoulMdPath, "utf-8") : "";
119433
+ const soulMdContent = existsSync99(soulMdPath) ? readFileSync90(soulMdPath, "utf-8") : existsSync99(workspaceSoulMdPath) ? readFileSync90(workspaceSoulMdPath, "utf-8") : "";
118464
119434
  if (soulMdContent.trim().length > 0) {
118465
119435
  console.log(`(${formatBytes(soulMdContent.length)})`);
118466
119436
  console.log(soulMdContent);
@@ -118524,8 +119494,8 @@ function registerDebugCommand(program3) {
118524
119494
  const fleetDir = join101(agentsDir, "..", "fleet");
118525
119495
  const fleetInvPath = join101(fleetDir, "switchroom-invariants.md");
118526
119496
  const fleetClaudePath = join101(fleetDir, "CLAUDE.md");
118527
- const fleetInvBytes = existsSync98(fleetInvPath) ? readFileSync90(fleetInvPath, "utf-8").length : 0;
118528
- const fleetClaudeBytes = existsSync98(fleetClaudePath) ? readFileSync90(fleetClaudePath, "utf-8").length : 0;
119497
+ const fleetInvBytes = existsSync99(fleetInvPath) ? readFileSync90(fleetInvPath, "utf-8").length : 0;
119498
+ const fleetClaudeBytes = existsSync99(fleetClaudePath) ? readFileSync90(fleetClaudePath, "utf-8").length : 0;
118529
119499
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
118530
119500
  const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
118531
119501
  console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
@@ -118557,8 +119527,8 @@ function registerDebugCommand(program3) {
118557
119527
  init_source();
118558
119528
 
118559
119529
  // src/worktree/claim.ts
118560
- import { execFileSync as execFileSync29 } from "node:child_process";
118561
- import { closeSync as closeSync17, mkdirSync as mkdirSync56, openSync as openSync17, existsSync as existsSync100, unlinkSync as unlinkSync21 } from "node:fs";
119530
+ import { execFileSync as execFileSync30 } from "node:child_process";
119531
+ import { closeSync as closeSync17, mkdirSync as mkdirSync56, openSync as openSync17, existsSync as existsSync101, unlinkSync as unlinkSync21 } from "node:fs";
118562
119532
  import { join as join103, resolve as resolve57 } from "node:path";
118563
119533
  import { homedir as homedir55 } from "node:os";
118564
119534
  import { randomBytes as randomBytes16 } from "node:crypto";
@@ -118570,7 +119540,7 @@ import {
118570
119540
  readFileSync as readFileSync91,
118571
119541
  readdirSync as readdirSync34,
118572
119542
  unlinkSync as unlinkSync20,
118573
- existsSync as existsSync99,
119543
+ existsSync as existsSync100,
118574
119544
  renameSync as renameSync25
118575
119545
  } from "node:fs";
118576
119546
  import { join as join102, resolve as resolve56 } from "node:path";
@@ -118684,7 +119654,7 @@ function expandHome(p) {
118684
119654
  }
118685
119655
  async function claimWorktree(input, codeRepos) {
118686
119656
  const repoPath = resolveRepoPath(input.repo, codeRepos);
118687
- if (!existsSync100(repoPath)) {
119657
+ if (!existsSync101(repoPath)) {
118688
119658
  throw new Error(`Repository path does not exist: ${repoPath}`);
118689
119659
  }
118690
119660
  let concurrencyCap = DEFAULT_CONCURRENCY;
@@ -118726,7 +119696,7 @@ async function claimWorktree(input, codeRepos) {
118726
119696
  releaseLock();
118727
119697
  }
118728
119698
  try {
118729
- execFileSync29("git", ["worktree", "add", "-b", branch, worktreePath], {
119699
+ execFileSync30("git", ["worktree", "add", "-b", branch, worktreePath], {
118730
119700
  cwd: repoPath,
118731
119701
  stdio: "pipe"
118732
119702
  });
@@ -118739,8 +119709,8 @@ async function claimWorktree(input, codeRepos) {
118739
119709
  }
118740
119710
 
118741
119711
  // src/worktree/release.ts
118742
- import { execFileSync as execFileSync30 } from "node:child_process";
118743
- import { existsSync as existsSync101 } from "node:fs";
119712
+ import { execFileSync as execFileSync31 } from "node:child_process";
119713
+ import { existsSync as existsSync102 } from "node:fs";
118744
119714
  function releaseWorktree(input) {
118745
119715
  const { id } = input;
118746
119716
  const record2 = readRecord(id);
@@ -118748,9 +119718,9 @@ function releaseWorktree(input) {
118748
119718
  return { released: true };
118749
119719
  }
118750
119720
  let gitSuccess = true;
118751
- if (existsSync101(record2.path)) {
119721
+ if (existsSync102(record2.path)) {
118752
119722
  try {
118753
- execFileSync30("git", ["worktree", "remove", "--force", record2.path], {
119723
+ execFileSync31("git", ["worktree", "remove", "--force", record2.path], {
118754
119724
  cwd: record2.repo,
118755
119725
  stdio: "pipe"
118756
119726
  });
@@ -118786,8 +119756,8 @@ function listWorktrees() {
118786
119756
  }
118787
119757
 
118788
119758
  // src/worktree/reaper.ts
118789
- import { execFileSync as execFileSync31 } from "node:child_process";
118790
- import { existsSync as existsSync102 } from "node:fs";
119759
+ import { execFileSync as execFileSync32 } from "node:child_process";
119760
+ import { existsSync as existsSync103 } from "node:fs";
118791
119761
  var STALE_THRESHOLD_MS = 10 * 60 * 1000;
118792
119762
  function reapSkipReasonText(action) {
118793
119763
  switch (action) {
@@ -118804,7 +119774,7 @@ function reapSkipReasonText(action) {
118804
119774
  function probePathInUse(path10) {
118805
119775
  let probeRan = false;
118806
119776
  try {
118807
- execFileSync31("fuser", [path10], { stdio: "pipe" });
119777
+ execFileSync32("fuser", [path10], { stdio: "pipe" });
118808
119778
  return "in-use";
118809
119779
  } catch (err) {
118810
119780
  if (err.code !== "ENOENT") {
@@ -118812,7 +119782,7 @@ function probePathInUse(path10) {
118812
119782
  }
118813
119783
  }
118814
119784
  try {
118815
- const out = execFileSync31("lsof", ["-t", path10], {
119785
+ const out = execFileSync32("lsof", ["-t", path10], {
118816
119786
  stdio: ["ignore", "pipe", "ignore"]
118817
119787
  }).toString().trim();
118818
119788
  probeRan = true;
@@ -118827,7 +119797,7 @@ function probePathInUse(path10) {
118827
119797
  }
118828
119798
  function hasUncommittedChanges(_repoPath, worktreePath) {
118829
119799
  try {
118830
- const out = execFileSync31("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" }).toString();
119800
+ const out = execFileSync32("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" }).toString();
118831
119801
  return out.trim().length > 0;
118832
119802
  } catch {
118833
119803
  return true;
@@ -118835,7 +119805,7 @@ function hasUncommittedChanges(_repoPath, worktreePath) {
118835
119805
  }
118836
119806
  function defaultRemoveWorktree(repoPath, worktreePath) {
118837
119807
  try {
118838
- execFileSync31("git", ["worktree", "remove", "--force", worktreePath], {
119808
+ execFileSync32("git", ["worktree", "remove", "--force", worktreePath], {
118839
119809
  cwd: repoPath,
118840
119810
  stdio: "pipe"
118841
119811
  });
@@ -118848,7 +119818,7 @@ function planReaper(nowMs, deps = {}) {
118848
119818
  const plan = [];
118849
119819
  for (const record2 of listRecords()) {
118850
119820
  const heartbeatAge = now2 - new Date(record2.heartbeatAt).getTime();
118851
- const worktreeExists = existsSync102(record2.path);
119821
+ const worktreeExists = existsSync103(record2.path);
118852
119822
  if (!worktreeExists) {
118853
119823
  plan.push({
118854
119824
  record: record2,
@@ -118927,9 +119897,9 @@ function runReaper(nowMs, deps = {}) {
118927
119897
  }
118928
119898
 
118929
119899
  // src/worktree/gc.ts
118930
- import { execFileSync as execFileSync32 } from "node:child_process";
119900
+ import { execFileSync as execFileSync33 } from "node:child_process";
118931
119901
  import {
118932
- existsSync as existsSync103,
119902
+ existsSync as existsSync104,
118933
119903
  readFileSync as readFileSync92,
118934
119904
  readdirSync as readdirSync35,
118935
119905
  statSync as statSync55,
@@ -119054,7 +120024,7 @@ function classifyTaskTree(i2) {
119054
120024
  return "skip-unmerged";
119055
120025
  return "reap";
119056
120026
  }
119057
- var defaultExec3 = (file, args, cwd) => execFileSync32(file, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).toString();
120027
+ var defaultExec3 = (file, args, cwd) => execFileSync33(file, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).toString();
119058
120028
  function defaultPrSignal(repo, branch, exec) {
119059
120029
  try {
119060
120030
  const out = exec("gh", [
@@ -119108,7 +120078,7 @@ function trashRoot() {
119108
120078
  return resolve58(process.env.SWITCHROOM_WORKTREE_TRASH ?? join104(homedir56(), ".switchroom", "worktree-gc-trash"));
119109
120079
  }
119110
120080
  function planGc(roots, deps = {}, taskTreeRoots = []) {
119111
- const exists = deps.existsSync ?? existsSync103;
120081
+ const exists = deps.existsSync ?? existsSync104;
119112
120082
  const readDir = deps.readDir ?? ((p) => readdirSync35(p));
119113
120083
  const readFile4 = deps.readFile ?? ((p) => readFileSync92(p, "utf8"));
119114
120084
  const stat3 = deps.stat ?? ((p) => statSync55(p));
@@ -119427,7 +120397,7 @@ function selectPurgeTargets(entries, olderThanDays) {
119427
120397
  return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
119428
120398
  }
119429
120399
  function listTrashEntries(nowMs, deps = {}) {
119430
- const exists = deps.existsSync ?? existsSync103;
120400
+ const exists = deps.existsSync ?? existsSync104;
119431
120401
  const readDir = deps.readDir ?? ((p) => readdirSync35(p));
119432
120402
  const root = trashRoot();
119433
120403
  if (!exists(root))
@@ -119480,7 +120450,7 @@ function defaultTaskTreeRoots() {
119480
120450
  for (const name of names.sort()) {
119481
120451
  for (const sub of ["home/work", "home/workspace"]) {
119482
120452
  const r = join104(agentsDir, name, sub);
119483
- if (existsSync103(r))
120453
+ if (existsSync104(r))
119484
120454
  roots.push(r);
119485
120455
  }
119486
120456
  }
@@ -120491,7 +121461,7 @@ function registerM365McpLauncherCommand(program3) {
120491
121461
  // src/cli/notion-mcp-launcher.ts
120492
121462
  init_scaffold_integration();
120493
121463
  import { spawn as spawn7 } from "node:child_process";
120494
- import { existsSync as existsSync104, mkdirSync as mkdirSync60, writeFileSync as writeFileSync43 } from "node:fs";
121464
+ import { existsSync as existsSync105, mkdirSync as mkdirSync60, writeFileSync as writeFileSync43 } from "node:fs";
120495
121465
  import { dirname as dirname45 } from "node:path";
120496
121466
  var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
120497
121467
  var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
@@ -120503,7 +121473,7 @@ function buildNotionMcpArgs(opts) {
120503
121473
  function defaultWriteHeartbeat(path10, contents) {
120504
121474
  try {
120505
121475
  const dir = dirname45(path10);
120506
- if (!existsSync104(dir))
121476
+ if (!existsSync105(dir))
120507
121477
  mkdirSync60(dir, { recursive: true });
120508
121478
  writeFileSync43(path10, contents);
120509
121479
  } catch {}
@@ -120619,6 +121589,92 @@ import { mkdirSync as mkdirSync61, readFileSync as readFileSync93, renameSync as
120619
121589
  import { tmpdir as tmpdir5 } from "node:os";
120620
121590
  import { join as join107 } from "node:path";
120621
121591
  import { createInterface as createInterface7 } from "node:readline";
121592
+
121593
+ // src/memory/hindsight-knowledge-admin.ts
121594
+ var KNOWLEDGE_SEARCH_LIMIT_MIN = 1;
121595
+ var KNOWLEDGE_SEARCH_LIMIT_MAX = 50;
121596
+ var KNOWLEDGE_SEARCH_LIMIT_DEFAULT = 10;
121597
+ function clampKnowledgeSearchLimit(limit) {
121598
+ if (limit === undefined || !Number.isFinite(limit)) {
121599
+ return KNOWLEDGE_SEARCH_LIMIT_DEFAULT;
121600
+ }
121601
+ return Math.min(KNOWLEDGE_SEARCH_LIMIT_MAX, Math.max(KNOWLEDGE_SEARCH_LIMIT_MIN, Math.trunc(limit)));
121602
+ }
121603
+ var KNOWLEDGE_ADMIN_TIMEOUT_MS = 15000;
121604
+ var KNOWLEDGE_PAGE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
121605
+
121606
+ class KnowledgeAdmin {
121607
+ opts;
121608
+ constructor(opts) {
121609
+ this.opts = opts;
121610
+ }
121611
+ get fetchImpl() {
121612
+ return this.opts.fetchImpl ?? fetch;
121613
+ }
121614
+ knowledgeBasePath() {
121615
+ const base = this.opts.apiBaseUrl.replace(/\/+$/, "");
121616
+ return `${base}/v1/default/banks/${encodeURIComponent(this.opts.bankId)}/knowledge-base`;
121617
+ }
121618
+ async get(url) {
121619
+ const ctl = new AbortController;
121620
+ const timer = setTimeout(() => ctl.abort(), this.opts.timeoutMs ?? KNOWLEDGE_ADMIN_TIMEOUT_MS);
121621
+ try {
121622
+ return await this.fetchImpl(url, {
121623
+ method: "GET",
121624
+ headers: {
121625
+ accept: "application/json",
121626
+ ...this.opts.bankId ? { "X-Bank-Id": this.opts.bankId } : {}
121627
+ },
121628
+ signal: ctl.signal
121629
+ });
121630
+ } finally {
121631
+ clearTimeout(timer);
121632
+ }
121633
+ }
121634
+ async search(args) {
121635
+ const params = new URLSearchParams({
121636
+ q: args.query,
121637
+ limit: String(clampKnowledgeSearchLimit(args.limit))
121638
+ });
121639
+ const res = await this.get(`${this.knowledgeBasePath()}/search?${params}`);
121640
+ if (!res.ok) {
121641
+ throw new Error(`searching knowledge pages in bank '${this.opts.bankId}' failed: HTTP ${res.status}`);
121642
+ }
121643
+ const parsed = await res.json();
121644
+ const results = Array.isArray(parsed.results) ? parsed.results : [];
121645
+ return {
121646
+ results,
121647
+ total: typeof parsed.total === "number" ? parsed.total : results.length
121648
+ };
121649
+ }
121650
+ async getPage(args) {
121651
+ if (!KNOWLEDGE_PAGE_ID_PATTERN.test(args.page_id)) {
121652
+ throw new Error(`'${args.page_id}' is not a knowledge page id. Ids look like ` + `'kp-<hex>' (letters, digits, '-' and '_' only) and come from ` + `search_knowledge_pages or get_knowledge_tree.`);
121653
+ }
121654
+ const res = await this.get(`${this.knowledgeBasePath()}/pages/${encodeURIComponent(args.page_id)}`);
121655
+ if (res.status === 404) {
121656
+ throw new Error(`no knowledge page '${args.page_id}' in bank '${this.opts.bankId}'. ` + `Page ids come from search_knowledge_pages or get_knowledge_tree.`);
121657
+ }
121658
+ if (!res.ok) {
121659
+ throw new Error(`reading knowledge page '${args.page_id}' in bank ` + `'${this.opts.bankId}' failed: HTTP ${res.status}`);
121660
+ }
121661
+ const page = await res.json();
121662
+ if (typeof page.markdown !== "string") {
121663
+ throw new Error(`knowledge page '${args.page_id}' in bank '${this.opts.bankId}' came ` + `back with no markdown body \u2014 the response had no string 'markdown' ` + `field, so there is nothing to read.`);
121664
+ }
121665
+ return page;
121666
+ }
121667
+ async tree() {
121668
+ const res = await this.get(`${this.knowledgeBasePath()}/tree`);
121669
+ if (!res.ok) {
121670
+ throw new Error(`listing the knowledge tree in bank '${this.opts.bankId}' failed: HTTP ${res.status}`);
121671
+ }
121672
+ const parsed = await res.json();
121673
+ return { roots: Array.isArray(parsed.roots) ? parsed.roots : [] };
121674
+ }
121675
+ }
121676
+
121677
+ // src/cli/hindsight-mcp-shim.ts
120622
121678
  init_hindsight();
120623
121679
  var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
120624
121680
  "2025-06-18",
@@ -120687,9 +121743,80 @@ var SYNTHESIZED_TOOL_TABLE = {
120687
121743
  description: "Exact name of the directive to reactivate."
120688
121744
  }
120689
121745
  }
121746
+ },
121747
+ search_knowledge_pages: {
121748
+ description: "Search YOUR OWN knowledge pages \u2014 the curated, continuously-refreshed " + "summaries of what this bank knows \u2014 with hybrid full-text + semantic " + "search. Reach for this before re-deriving a standing answer from raw " + "recall: a page is already synthesized, where recall returns fragments. " + "Returns ranked hits with a relevance snippet and a page id; read the " + "whole page with get_knowledge_page. Read-only, and only ever your own " + "memory bank. An empty knowledge base returns no results, not an error.",
121749
+ required: ["query"],
121750
+ props: {
121751
+ query: {
121752
+ type: "string",
121753
+ description: "What to look for, in natural language."
121754
+ },
121755
+ limit: {
121756
+ type: "integer",
121757
+ minimum: KNOWLEDGE_SEARCH_LIMIT_MIN,
121758
+ maximum: KNOWLEDGE_SEARCH_LIMIT_MAX,
121759
+ description: `Maximum hits to return (${KNOWLEDGE_SEARCH_LIMIT_MIN}-` + `${KNOWLEDGE_SEARCH_LIMIT_MAX}, default ${KNOWLEDGE_SEARCH_LIMIT_DEFAULT}).`
121760
+ }
121761
+ }
121762
+ },
121763
+ get_knowledge_page: {
121764
+ description: "Read one of YOUR OWN knowledge pages in full, by the page id returned " + "by search_knowledge_pages or get_knowledge_tree. Returns the complete " + "markdown document (YAML frontmatter + synthesized body). Prefer this " + "over rebuilding the same understanding from scratch. Read-only: it " + "returns the page as it currently stands and never triggers a refresh.",
121765
+ required: ["page_id"],
121766
+ props: {
121767
+ page_id: {
121768
+ type: "string",
121769
+ description: "Id of the page to read, from search_knowledge_pages or " + "get_knowledge_tree."
121770
+ }
121771
+ }
121772
+ },
121773
+ get_knowledge_tree: {
121774
+ description: "List YOUR OWN knowledge base as a folder/page tree \u2014 every page's id, " + "name, source query, tags and staleness flag. Call this to see what " + "this bank already knows before reading anything else; is_stale=true " + "means the page MAY be behind newer memories, not that it is wrong. " + "Read-only, and only ever your own memory bank.",
121775
+ required: [],
121776
+ props: {}
120690
121777
  }
120691
121778
  };
120692
121779
  var SYNTHESIZED_TOOL_NAMES = Object.keys(SYNTHESIZED_TOOL_TABLE);
121780
+ var INTEGER_STRING_PATTERN = /^-?(?:0|[1-9]\d*)$/;
121781
+ function coerceSynthesizedArg(toolName, key, propSchema, value) {
121782
+ const schema = propSchema ?? {};
121783
+ if (schema.type !== "integer") {
121784
+ if (typeof value !== "string" || value.length === 0) {
121785
+ return {
121786
+ ok: false,
121787
+ text: `${toolName}: '${key}' must be a non-empty string.`
121788
+ };
121789
+ }
121790
+ return { ok: true, value };
121791
+ }
121792
+ const min = schema.minimum;
121793
+ const max = schema.maximum;
121794
+ const range = min !== undefined && max !== undefined ? ` between ${min} and ${max}` : "";
121795
+ let n;
121796
+ if (typeof value === "number") {
121797
+ n = value;
121798
+ } else if (typeof value === "string" && INTEGER_STRING_PATTERN.test(value)) {
121799
+ n = Number(value);
121800
+ } else {
121801
+ return {
121802
+ ok: false,
121803
+ text: `${toolName}: '${key}' must be an integer${range}.`
121804
+ };
121805
+ }
121806
+ if (!Number.isSafeInteger(n)) {
121807
+ return {
121808
+ ok: false,
121809
+ text: `${toolName}: '${key}' must be an integer${range}.`
121810
+ };
121811
+ }
121812
+ if (min !== undefined && n < min || max !== undefined && n > max) {
121813
+ return {
121814
+ ok: false,
121815
+ text: `${toolName}: '${key}' must be an integer${range} \u2014 got ${n}. ` + `Re-issue the call with a value in range.`
121816
+ };
121817
+ }
121818
+ return { ok: true, value: n };
121819
+ }
120693
121820
  function buildSynthesizedToolsList() {
120694
121821
  return Object.entries(SYNTHESIZED_TOOL_TABLE).map(([name, { description, required, props }]) => ({
120695
121822
  name,
@@ -120768,6 +121895,52 @@ var DEFAULT_RECALL_MAX_TOKENS = 1024;
120768
121895
  var DEFAULT_REFLECT_MAX_TOKENS = 1024;
120769
121896
  var DEFAULT_RECALL_BUDGET = "low";
120770
121897
  var DEFAULT_REFLECT_BUDGET = "mid";
121898
+ var KNOWLEDGE_RESPONSE_MAX_CHARS = 16384;
121899
+ var KNOWLEDGE_TREE_MARKER_RESERVE = 256;
121900
+ function capKnowledgeResponse(text) {
121901
+ if (text.length <= KNOWLEDGE_RESPONSE_MAX_CHARS)
121902
+ return text;
121903
+ const omitted = text.length - KNOWLEDGE_RESPONSE_MAX_CHARS;
121904
+ return text.slice(0, KNOWLEDGE_RESPONSE_MAX_CHARS) + `
121905
+
121906
+ \u2026truncated at ${KNOWLEDGE_RESPONSE_MAX_CHARS} chars \u2014 ${omitted} ` + `chars omitted. This is a PARTIAL read; narrow it with ` + `search_knowledge_pages rather than treating the above as the whole.`;
121907
+ }
121908
+ function countKnowledgeNodes(nodes) {
121909
+ let n = 0;
121910
+ for (const node of nodes)
121911
+ n += 1 + countKnowledgeNodes(node.children ?? []);
121912
+ return n;
121913
+ }
121914
+ function takeKnowledgeNodes(nodes, budget) {
121915
+ const out = [];
121916
+ for (const node of nodes) {
121917
+ if (budget.left <= 0)
121918
+ break;
121919
+ budget.left -= 1;
121920
+ out.push(node.children === undefined ? node : { ...node, children: takeKnowledgeNodes(node.children, budget) });
121921
+ }
121922
+ return out;
121923
+ }
121924
+ function renderKnowledgeTree(roots) {
121925
+ const total = countKnowledgeNodes(roots);
121926
+ const whole = JSON.stringify(roots);
121927
+ if (whole.length <= KNOWLEDGE_RESPONSE_MAX_CHARS)
121928
+ return whole;
121929
+ const charBudget = KNOWLEDGE_RESPONSE_MAX_CHARS - KNOWLEDGE_TREE_MARKER_RESERVE;
121930
+ let allowed = total;
121931
+ let kept = takeKnowledgeNodes(roots, { left: allowed });
121932
+ let json = JSON.stringify(kept);
121933
+ while (json.length > charBudget && allowed > 0) {
121934
+ allowed = Math.max(0, Math.min(allowed - 1, Math.floor(allowed * (charBudget / json.length))));
121935
+ kept = takeKnowledgeNodes(roots, { left: allowed });
121936
+ json = JSON.stringify(kept);
121937
+ }
121938
+ const shown = countKnowledgeNodes(kept);
121939
+ if (shown === total)
121940
+ return json;
121941
+ return json + `
121942
+ \u2026${total - shown} of ${total} nodes omitted \u2014 the knowledge tree was ` + `truncated to fit the response budget. This is a PARTIAL listing; use ` + `search_knowledge_pages to find a specific page.`;
121943
+ }
120771
121944
  var VALID_BUDGETS = new Set(["low", "mid", "high"]);
120772
121945
  function resolveClampMaxTokens(envVal, fallback) {
120773
121946
  if (envVal === undefined || envVal === "")
@@ -121102,6 +122275,17 @@ class HindsightShim {
121102
122275
  }
121103
122276
  return this.directiveAdminCache;
121104
122277
  }
122278
+ knowledgeAdminCache = null;
122279
+ get knowledgeAdmin() {
122280
+ if (!this.knowledgeAdminCache) {
122281
+ this.knowledgeAdminCache = new KnowledgeAdmin({
122282
+ apiBaseUrl: this.opts.apiBaseUrl ?? this.opts.url.replace(/\/mcp\/?$/, ""),
122283
+ bankId: this.opts.bankId,
122284
+ ...this.opts.fetchImpl ? { fetchImpl: this.opts.fetchImpl } : {}
122285
+ });
122286
+ }
122287
+ return this.knowledgeAdminCache;
122288
+ }
121105
122289
  async synthesizedCall(name, rawArgs) {
121106
122290
  const fail4 = (text) => ({
121107
122291
  content: [{ type: "text", text }],
@@ -121114,26 +122298,23 @@ class HindsightShim {
121114
122298
  if (unknown.length > 0) {
121115
122299
  return fail4(`${name} does not accept ${unknown.map((u) => `'${u}'`).join(", ")}. ` + `Accepted arguments: ${[...allowed].join(", ")}. ` + (unknown.includes("bank_id") ? "This tool always operates on your own memory bank; there is no " + "way to target another agent's bank through it." : ""));
121116
122300
  }
122301
+ const clean3 = {};
121117
122302
  for (const key of Object.keys(args)) {
121118
- if (typeof args[key] !== "string" || args[key].length === 0) {
121119
- return fail4(`${name}: '${key}' must be a non-empty string.`);
121120
- }
122303
+ const coerced = coerceSynthesizedArg(name, key, spec.props[key], args[key]);
122304
+ if (!coerced.ok)
122305
+ return fail4(coerced.text);
122306
+ clean3[key] = coerced.value;
121121
122307
  }
121122
122308
  for (const req of spec.required) {
121123
- if (args[req] === undefined) {
122309
+ if (clean3[req] === undefined) {
121124
122310
  return fail4(`${name} requires '${req}'.`);
121125
122311
  }
121126
122312
  }
121127
122313
  if (!this.opts.bankId) {
121128
- return fail4(`${name} is unavailable: this agent has no HINDSIGHT_BANK_ID, so ` + "there is no bank to pin the change to.");
122314
+ return fail4(`${name} is unavailable: this agent has no HINDSIGHT_BANK_ID, so ` + "there is no bank to pin it to.");
121129
122315
  }
121130
122316
  try {
121131
- const text = name === "deactivate_directive" ? await this.directiveAdmin.deactivate({
121132
- name: args.name,
121133
- ...typeof args.superseded_by === "string" ? { supersededBy: args.superseded_by } : {}
121134
- }) : await this.directiveAdmin.reactivate({
121135
- name: args.name
121136
- });
122317
+ const text = await this.runSynthesized(name, clean3);
121137
122318
  return { content: [{ type: "text", text }], isError: false };
121138
122319
  } catch (err) {
121139
122320
  if (err instanceof DirectivePairInconsistentError) {
@@ -121142,6 +122323,42 @@ class HindsightShim {
121142
122323
  return fail4(`${name} failed: ${String(err)}`);
121143
122324
  }
121144
122325
  }
122326
+ async runSynthesized(name, args) {
122327
+ switch (name) {
122328
+ case "deactivate_directive":
122329
+ return this.directiveAdmin.deactivate({
122330
+ name: args.name,
122331
+ ...typeof args.superseded_by === "string" ? { supersededBy: args.superseded_by } : {}
122332
+ });
122333
+ case "reactivate_directive":
122334
+ return this.directiveAdmin.reactivate({ name: args.name });
122335
+ case "search_knowledge_pages": {
122336
+ const res = await this.knowledgeAdmin.search({
122337
+ query: args.query,
122338
+ ...typeof args.limit === "number" ? { limit: args.limit } : {}
122339
+ });
122340
+ if (res.results.length === 0) {
122341
+ return "No knowledge pages matched that query in your memory bank. " + "The bank may have no pages yet \u2014 get_knowledge_tree lists what " + "exists.";
122342
+ }
122343
+ return capKnowledgeResponse(JSON.stringify({ total: res.total, results: res.results }, null, 2));
122344
+ }
122345
+ case "get_knowledge_page": {
122346
+ const page = await this.knowledgeAdmin.getPage({
122347
+ page_id: args.page_id
122348
+ });
122349
+ return capKnowledgeResponse(page.markdown);
122350
+ }
122351
+ case "get_knowledge_tree": {
122352
+ const tree = await this.knowledgeAdmin.tree();
122353
+ if (tree.roots.length === 0) {
122354
+ return "Your knowledge base has no pages yet. Pages are synthesized from " + "mental models \u2014 propose one to start it.";
122355
+ }
122356
+ return renderKnowledgeTree(tree.roots);
122357
+ }
122358
+ default:
122359
+ throw new Error(`${name} is advertised as a synthesized tool but has no dispatch arm`);
122360
+ }
122361
+ }
121145
122362
  async toolsCall(params) {
121146
122363
  const called = params;
121147
122364
  if (called?.name && SYNTHESIZED_TOOL_NAMES.includes(called.name)) {
@@ -121921,7 +123138,7 @@ function runRedactStdin() {
121921
123138
  }
121922
123139
 
121923
123140
  // src/cli/status-ask.ts
121924
- import { readFileSync as readFileSync95, existsSync as existsSync105, readdirSync as readdirSync37 } from "node:fs";
123141
+ import { readFileSync as readFileSync95, existsSync as existsSync106, readdirSync as readdirSync37 } from "node:fs";
121925
123142
  import { join as join108 } from "node:path";
121926
123143
  import { homedir as homedir57 } from "node:os";
121927
123144
 
@@ -122244,7 +123461,7 @@ function runReport(opts) {
122244
123461
  function resolveSources(explicitPath) {
122245
123462
  if (explicitPath != null && explicitPath.trim() !== "") {
122246
123463
  const trimmed = explicitPath.trim();
122247
- if (!existsSync105(trimmed)) {
123464
+ if (!existsSync106(trimmed)) {
122248
123465
  process.stderr.write(`status-ask report: ${trimmed}: file not found
122249
123466
  `);
122250
123467
  process.exit(1);
@@ -122260,7 +123477,7 @@ function resolveSources(explicitPath) {
122260
123477
  } catch {
122261
123478
  agentsDir = join108(homedir57(), ".switchroom", "agents");
122262
123479
  }
122263
- if (!existsSync105(agentsDir))
123480
+ if (!existsSync106(agentsDir))
122264
123481
  return [];
122265
123482
  const sources = [];
122266
123483
  let entries;
@@ -122271,7 +123488,7 @@ function resolveSources(explicitPath) {
122271
123488
  }
122272
123489
  for (const name of entries) {
122273
123490
  const path10 = join108(agentsDir, name, "runtime-metrics.jsonl");
122274
- if (existsSync105(path10)) {
123491
+ if (existsSync106(path10)) {
122275
123492
  sources.push({ path: path10, agent: name });
122276
123493
  }
122277
123494
  }
@@ -122302,7 +123519,7 @@ init_paths();
122302
123519
  import {
122303
123520
  chownSync as chownSync12,
122304
123521
  closeSync as closeSync18,
122305
- existsSync as existsSync106,
123522
+ existsSync as existsSync107,
122306
123523
  fsyncSync as fsyncSync8,
122307
123524
  mkdirSync as mkdirSync62,
122308
123525
  openSync as openSync18,
@@ -122432,7 +123649,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
122432
123649
  const paths = overlayPathsFor(agent, opts);
122433
123650
  return withAgentLock(paths, () => {
122434
123651
  const finalPath = join109(paths.skillsDir, `${slug}.yaml`);
122435
- if (!existsSync106(finalPath))
123652
+ if (!existsSync107(finalPath))
122436
123653
  return false;
122437
123654
  unlinkSync22(finalPath);
122438
123655
  return true;
@@ -122440,7 +123657,7 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
122440
123657
  }
122441
123658
  function listSkillsOverlayEntries(agent, opts = {}) {
122442
123659
  const paths = overlayPathsFor(agent, opts);
122443
- if (!existsSync106(paths.skillsDir))
123660
+ if (!existsSync107(paths.skillsDir))
122444
123661
  return [];
122445
123662
  const out = [];
122446
123663
  for (const name of readdirSync38(paths.skillsDir)) {
@@ -122459,7 +123676,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
122459
123676
  const paths = overlayPathsFor(agent, opts);
122460
123677
  return withAgentLock(paths, () => {
122461
123678
  const finalPath = join109(paths.scheduleDir, `${slug}.yaml`);
122462
- if (!existsSync106(finalPath))
123679
+ if (!existsSync107(finalPath))
122463
123680
  return false;
122464
123681
  unlinkSync22(finalPath);
122465
123682
  return true;
@@ -122467,7 +123684,7 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
122467
123684
  }
122468
123685
  function listOverlayEntries(agent, opts = {}) {
122469
123686
  const paths = overlayPathsFor(agent, opts);
122470
- if (!existsSync106(paths.scheduleDir))
123687
+ if (!existsSync107(paths.scheduleDir))
122471
123688
  return [];
122472
123689
  const out = [];
122473
123690
  for (const name of readdirSync38(paths.scheduleDir)) {
@@ -122695,7 +123912,7 @@ function reconcileAgentCronOnly(agent) {
122695
123912
  // src/cli/agent-config-pending.ts
122696
123913
  import {
122697
123914
  closeSync as closeSync19,
122698
- existsSync as existsSync107,
123915
+ existsSync as existsSync108,
122699
123916
  fsyncSync as fsyncSync9,
122700
123917
  mkdirSync as mkdirSync63,
122701
123918
  openSync as openSync19,
@@ -122752,7 +123969,7 @@ function stagePendingScheduleEntry(opts) {
122752
123969
  }
122753
123970
  function listPendingScheduleEntries(agent, opts = {}) {
122754
123971
  const dir = pendingDir(agent, opts);
122755
- if (!existsSync107(dir))
123972
+ if (!existsSync108(dir))
122756
123973
  return [];
122757
123974
  const out = [];
122758
123975
  for (const name of readdirSync39(dir).sort()) {
@@ -122761,7 +123978,7 @@ function listPendingScheduleEntries(agent, opts = {}) {
122761
123978
  const stageId = name.slice(0, -".meta.json".length);
122762
123979
  const metaPath = join110(dir, name);
122763
123980
  const yamlPath = join110(dir, `${stageId}.yaml`);
122764
- if (!existsSync107(yamlPath))
123981
+ if (!existsSync108(yamlPath))
122765
123982
  continue;
122766
123983
  try {
122767
123984
  const meta = JSON.parse(readFileSync97(metaPath, "utf-8"));
@@ -122780,7 +123997,7 @@ function commitPendingScheduleEntry(opts) {
122780
123997
  const slug = match.meta.entry.name ?? match.stageId;
122781
123998
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
122782
123999
  const finalPath = join110(paths.scheduleDir, `${slug}.yaml`);
122783
- if (existsSync107(finalPath)) {
124000
+ if (existsSync108(finalPath)) {
122784
124001
  return { committed: false, reason: "slug_collision" };
122785
124002
  }
122786
124003
  renameSync29(match.yamlPath, finalPath);
@@ -122802,8 +124019,8 @@ function denyPendingScheduleEntry(opts) {
122802
124019
  }
122803
124020
 
122804
124021
  // src/cli/agent-config-write.ts
122805
- import { existsSync as existsSync108, readFileSync as readFileSync98 } from "node:fs";
122806
- import { execFileSync as execFileSync33 } from "node:child_process";
124022
+ import { existsSync as existsSync109, readFileSync as readFileSync98 } from "node:fs";
124023
+ import { execFileSync as execFileSync34 } from "node:child_process";
122807
124024
 
122808
124025
  // src/scheduler/schedule-report.ts
122809
124026
  var TIER_WEIGHT = { poll: 0, action: 0, cheap: 1, main: 5 };
@@ -123192,7 +124409,7 @@ function scheduleRemove(opts) {
123192
124409
  }
123193
124410
  let priorContent = null;
123194
124411
  try {
123195
- if (existsSync108(match.path))
124412
+ if (existsSync109(match.path))
123196
124413
  priorContent = readFileSync98(match.path, "utf-8");
123197
124414
  } catch {}
123198
124415
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
@@ -123396,10 +124613,10 @@ function registerAgentConfigWriteCommands(program3) {
123396
124613
  }
123397
124614
  let blob;
123398
124615
  if (opts.jsonl) {
123399
- blob = existsSync108(opts.jsonl) ? readFileSync98(opts.jsonl, "utf-8") : "";
124616
+ blob = existsSync109(opts.jsonl) ? readFileSync98(opts.jsonl, "utf-8") : "";
123400
124617
  } else {
123401
124618
  try {
123402
- blob = execFileSync33("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
124619
+ blob = execFileSync34("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
123403
124620
  encoding: "utf-8",
123404
124621
  stdio: ["ignore", "pipe", "ignore"]
123405
124622
  });
@@ -123428,7 +124645,7 @@ function registerAgentConfigWriteCommands(program3) {
123428
124645
 
123429
124646
  // src/cli/agent-config-skill-write.ts
123430
124647
  var import_yaml24 = __toESM(require_dist(), 1);
123431
- import { existsSync as existsSync109 } from "node:fs";
124648
+ import { existsSync as existsSync110 } from "node:fs";
123432
124649
  init_reconcile_default_skills();
123433
124650
  init_agent_config();
123434
124651
  var import_yaml25 = __toESM(require_dist(), 1);
@@ -123508,7 +124725,7 @@ function skillInstall(opts) {
123508
124725
  }
123509
124726
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
123510
124727
  const skillPath = join111(poolDir, skillName);
123511
- if (!existsSync109(skillPath)) {
124728
+ if (!existsSync110(skillPath)) {
123512
124729
  return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
123513
124730
  }
123514
124731
  const yamlText = import_yaml24.stringify({ skills: [skillName] });
@@ -123678,7 +124895,7 @@ init_source();
123678
124895
  import {
123679
124896
  chmodSync as chmodSync18,
123680
124897
  closeSync as closeSync20,
123681
- existsSync as existsSync110,
124898
+ existsSync as existsSync111,
123682
124899
  lstatSync as lstatSync14,
123683
124900
  mkdirSync as mkdirSync64,
123684
124901
  mkdtempSync as mkdtempSync5,
@@ -123887,7 +125104,7 @@ function validatePayload(name, files) {
123887
125104
  function diffSummary(currentDir, files) {
123888
125105
  const lines = [];
123889
125106
  const currentFiles = {};
123890
- if (existsSync110(currentDir)) {
125107
+ if (existsSync111(currentDir)) {
123891
125108
  const walk2 = (sub) => {
123892
125109
  for (const ent of readdirSync40(sub, { withFileTypes: true })) {
123893
125110
  const full = join112(sub, ent.name);
@@ -123923,7 +125140,7 @@ function diffSummary(currentDir, files) {
123923
125140
  `);
123924
125141
  }
123925
125142
  function writePayload(poolDir, name, files) {
123926
- if (!existsSync110(poolDir)) {
125143
+ if (!existsSync111(poolDir)) {
123927
125144
  mkdirSync64(poolDir, { recursive: true, mode: 493 });
123928
125145
  }
123929
125146
  const target = join112(poolDir, name);
@@ -123973,9 +125190,9 @@ function writePayload(poolDir, name, files) {
123973
125190
  try {
123974
125191
  rmSync20(staging, { recursive: true, force: true });
123975
125192
  } catch {}
123976
- if (oldRename && existsSync110(oldRename)) {
125193
+ if (oldRename && existsSync111(oldRename)) {
123977
125194
  try {
123978
- if (existsSync110(target)) {
125195
+ if (existsSync111(target)) {
123979
125196
  rmSync20(target, { recursive: true, force: true });
123980
125197
  }
123981
125198
  renameSync30(oldRename, target);
@@ -123996,7 +125213,7 @@ function registerSkillCommand(program3) {
123996
125213
  files = loadFromStdin();
123997
125214
  } else {
123998
125215
  const fromPath = resolve60(opts.from);
123999
- if (!existsSync110(fromPath)) {
125216
+ if (!existsSync111(fromPath)) {
124000
125217
  fail4(`--from path does not exist: ${opts.from}`);
124001
125218
  }
124002
125219
  const st = statSync58(fromPath);
@@ -124057,7 +125274,7 @@ init_source();
124057
125274
  import {
124058
125275
  chmodSync as chmodSync19,
124059
125276
  closeSync as closeSync21,
124060
- existsSync as existsSync111,
125277
+ existsSync as existsSync112,
124061
125278
  lstatSync as lstatSync15,
124062
125279
  mkdirSync as mkdirSync65,
124063
125280
  mkdtempSync as mkdtempSync6,
@@ -124080,14 +125297,14 @@ var PERSONAL_SKILLS_SUBPATH2 = "personal-skills";
124080
125297
  function resolveConfigSkillsDir(agent) {
124081
125298
  const override = process.env.SWITCHROOM_CONFIG_DIR;
124082
125299
  const candidate = override ? resolve61(override) : join113(homedir59(), ".switchroom-config");
124083
- if (!existsSync111(candidate))
125300
+ if (!existsSync112(candidate))
124084
125301
  return null;
124085
125302
  return join113(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH2);
124086
125303
  }
124087
125304
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
124088
125305
  function sweepMirrorPriors(configSkillsRoot) {
124089
125306
  try {
124090
- if (!existsSync111(configSkillsRoot))
125307
+ if (!existsSync112(configSkillsRoot))
124091
125308
  return;
124092
125309
  const now2 = Date.now();
124093
125310
  for (const ent of readdirSync41(configSkillsRoot)) {
@@ -124123,7 +125340,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
124123
125340
  }
124124
125341
  if (liveSkillDir === null) {
124125
125342
  sweepMirrorPriors(configSkillsRoot);
124126
- if (existsSync111(dest)) {
125343
+ if (existsSync112(dest)) {
124127
125344
  const trash = join113(configSkillsRoot, `.${name}-trash-${Date.now()}`);
124128
125345
  renameSync31(dest, trash);
124129
125346
  }
@@ -124148,7 +125365,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
124148
125365
  };
124149
125366
  walk2(liveSkillDir, staging);
124150
125367
  chmodSync19(staging, 493);
124151
- if (existsSync111(dest)) {
125368
+ if (existsSync112(dest)) {
124152
125369
  const prior = join113(configSkillsRoot, `.${name}-prior-${Date.now()}`);
124153
125370
  renameSync31(dest, prior);
124154
125371
  }
@@ -124189,7 +125406,7 @@ function trashDir(agentsRoot, agent) {
124189
125406
  }
124190
125407
  function countPersonalSkills(agentsRoot, agent) {
124191
125408
  const skillsDir = join113(agentsRoot, agent, ".claude", "skills");
124192
- if (!existsSync111(skillsDir))
125409
+ if (!existsSync112(skillsDir))
124193
125410
  return 0;
124194
125411
  let n = 0;
124195
125412
  for (const ent of readdirSync41(skillsDir, { withFileTypes: true })) {
@@ -124298,7 +125515,7 @@ function behavioralValidate(files) {
124298
125515
  }
124299
125516
  function sweepTrash(agentsRoot, agent) {
124300
125517
  const trash = trashDir(agentsRoot, agent);
124301
- if (!existsSync111(trash))
125518
+ if (!existsSync112(trash))
124302
125519
  return;
124303
125520
  const now2 = Date.now();
124304
125521
  for (const ent of readdirSync41(trash, { withFileTypes: true })) {
@@ -124361,9 +125578,9 @@ function writePersonalSkill(targetDir, files) {
124361
125578
  try {
124362
125579
  rmSync21(staging, { recursive: true, force: true });
124363
125580
  } catch {}
124364
- if (oldRename && existsSync111(oldRename)) {
125581
+ if (oldRename && existsSync112(oldRename)) {
124365
125582
  try {
124366
- if (existsSync111(targetDir)) {
125583
+ if (existsSync112(targetDir)) {
124367
125584
  rmSync21(targetDir, { recursive: true, force: true });
124368
125585
  }
124369
125586
  renameSync31(oldRename, targetDir);
@@ -124429,7 +125646,7 @@ function loadFiles(opts) {
124429
125646
  return loadFromStdin2();
124430
125647
  }
124431
125648
  const p = resolve61(opts.from);
124432
- if (!existsSync111(p)) {
125649
+ if (!existsSync112(p)) {
124433
125650
  fail5(`--from path does not exist: ${opts.from}`);
124434
125651
  }
124435
125652
  const st = statSync59(p);
@@ -124491,7 +125708,7 @@ function resolveCloneSource(source, opts) {
124491
125708
  const slug = m[2];
124492
125709
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
124493
125710
  const dir = join113(root, slug);
124494
- if (!existsSync111(dir)) {
125711
+ if (!existsSync112(dir)) {
124495
125712
  fail5(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
124496
125713
  }
124497
125714
  const st = lstatSync15(dir);
@@ -124638,7 +125855,7 @@ function listPersonalAction(opts) {
124638
125855
  sweepTrash(agentsRoot, agent);
124639
125856
  const skillsDir = join113(agentsRoot, agent, ".claude", "skills");
124640
125857
  const personal = [];
124641
- if (existsSync111(skillsDir)) {
125858
+ if (existsSync112(skillsDir)) {
124642
125859
  for (const ent of readdirSync41(skillsDir, { withFileTypes: true })) {
124643
125860
  if (!ent.isDirectory())
124644
125861
  continue;
@@ -124824,10 +126041,10 @@ function registerSelfImproveProposeSkillCommand(program3) {
124824
126041
  import { createConnection as createConnection5 } from "node:net";
124825
126042
  import { homedir as homedir61 } from "node:os";
124826
126043
  import { join as join118, resolve as resolve62, sep as sep6 } from "node:path";
124827
- import { existsSync as existsSync116, readFileSync as readFileSync106, realpathSync as realpathSync10 } from "node:fs";
126044
+ import { existsSync as existsSync117, readFileSync as readFileSync106, realpathSync as realpathSync10 } from "node:fs";
124828
126045
 
124829
126046
  // src/self-improve/apply-guard.ts
124830
- import { existsSync as existsSync114, lstatSync as lstatSync16, readFileSync as readFileSync104, statSync as statSync61 } from "node:fs";
126047
+ import { existsSync as existsSync115, lstatSync as lstatSync16, readFileSync as readFileSync104, statSync as statSync61 } from "node:fs";
124831
126048
 
124832
126049
  // src/self-improve/config.ts
124833
126050
  function intEnv(name, def) {
@@ -124848,7 +126065,7 @@ function resolveSelfImproveConfig() {
124848
126065
  }
124849
126066
 
124850
126067
  // src/self-improve/eval-gate.ts
124851
- import { existsSync as existsSync112, readFileSync as readFileSync102, readdirSync as readdirSync42, statSync as statSync60 } from "node:fs";
126068
+ import { existsSync as existsSync113, readFileSync as readFileSync102, readdirSync as readdirSync42, statSync as statSync60 } from "node:fs";
124852
126069
  import { join as join115 } from "node:path";
124853
126070
  import { spawnSync as spawnSync26 } from "node:child_process";
124854
126071
  function evalsJsonPath(skillDir) {
@@ -124856,7 +126073,7 @@ function evalsJsonPath(skillDir) {
124856
126073
  }
124857
126074
  function skillHasEvals(skillDir) {
124858
126075
  const p = evalsJsonPath(skillDir);
124859
- if (!existsSync112(p))
126076
+ if (!existsSync113(p))
124860
126077
  return false;
124861
126078
  try {
124862
126079
  const parsed = JSON.parse(readFileSync102(p, "utf-8"));
@@ -124867,7 +126084,7 @@ function skillHasEvals(skillDir) {
124867
126084
  }
124868
126085
  function aggregateBenchmark(benchmarkDir, repoRoot, pythonBin = process.env.SWITCHROOM_PYTHON ?? "python3") {
124869
126086
  const script = join115(repoRoot, "skills", "skill-creator", "scripts", "aggregate_benchmark.py");
124870
- if (!existsSync112(script)) {
126087
+ if (!existsSync113(script)) {
124871
126088
  return { ok: false, error: `aggregate_benchmark.py not found at ${script}` };
124872
126089
  }
124873
126090
  const r = spawnSync26(pythonBin, [script, benchmarkDir], {
@@ -124899,7 +126116,7 @@ function isBenchmarkDir(dir) {
124899
126116
  if (hasEvalDirs(dir))
124900
126117
  return true;
124901
126118
  const runs = join115(dir, "runs");
124902
- return existsSync112(runs) && hasEvalDirs(runs);
126119
+ return existsSync113(runs) && hasEvalDirs(runs);
124903
126120
  }
124904
126121
  function resolveBenchmarkDir(baseDir, explicit) {
124905
126122
  if (explicit != null && explicit.length > 0) {
@@ -124978,7 +126195,7 @@ function floatEnv(name, def) {
124978
126195
  import {
124979
126196
  closeSync as closeSync22,
124980
126197
  copyFileSync as copyFileSync15,
124981
- existsSync as existsSync113,
126198
+ existsSync as existsSync114,
124982
126199
  mkdirSync as mkdirSync66,
124983
126200
  openSync as openSync22,
124984
126201
  readFileSync as readFileSync103,
@@ -125125,7 +126342,7 @@ function caseFingerprint(prompt) {
125125
126342
  }
125126
126343
  function readEvalsDoc(skillDir) {
125127
126344
  const p = evalsJsonPath(skillDir);
125128
- if (!existsSync113(p))
126345
+ if (!existsSync114(p))
125129
126346
  return null;
125130
126347
  try {
125131
126348
  const parsed = JSON.parse(readFileSync103(p, "utf-8"));
@@ -125149,7 +126366,7 @@ function appendEvalCase(skillDir, slug, input) {
125149
126366
  return { ok: false, reason: parsed.error };
125150
126367
  const ec = parsed.case;
125151
126368
  const existingDoc = readEvalsDoc(skillDir);
125152
- if (existingDoc === null && existsSync113(evalsJsonPath(skillDir))) {
126369
+ if (existingDoc === null && existsSync114(evalsJsonPath(skillDir))) {
125153
126370
  return {
125154
126371
  ok: false,
125155
126372
  reason: "evals.json exists but is unparseable or malformed \u2014 refusing to " + "overwrite it (fix or remove the file first)"
@@ -125181,7 +126398,7 @@ function heldOutPath(stateDir) {
125181
126398
  }
125182
126399
  function heldOutHasFingerprint(stateDir, slug, fp) {
125183
126400
  const path10 = heldOutPath(stateDir);
125184
- if (!existsSync113(path10))
126401
+ if (!existsSync114(path10))
125185
126402
  return false;
125186
126403
  let text;
125187
126404
  try {
@@ -125233,7 +126450,7 @@ function baselineEvalsPath(stateDir, slug) {
125233
126450
  }
125234
126451
  function evalsSha256(skillDir) {
125235
126452
  const p = evalsJsonPath(skillDir);
125236
- if (!existsSync113(p))
126453
+ if (!existsSync114(p))
125237
126454
  return null;
125238
126455
  try {
125239
126456
  return createHash21("sha256").update(readFileSync103(p)).digest("hex");
@@ -125243,7 +126460,7 @@ function evalsSha256(skillDir) {
125243
126460
  }
125244
126461
  function readManifest(stateDir) {
125245
126462
  const p = manifestPath(stateDir);
125246
- if (!existsSync113(p))
126463
+ if (!existsSync114(p))
125247
126464
  return {};
125248
126465
  try {
125249
126466
  const parsed = JSON.parse(readFileSync103(p, "utf-8"));
@@ -125285,7 +126502,7 @@ function ownsSkill(skillDir) {
125285
126502
  // src/self-improve/eval-case-proposals.ts
125286
126503
  import {
125287
126504
  closeSync as closeSync23,
125288
- existsSync as existsSync115,
126505
+ existsSync as existsSync116,
125289
126506
  mkdirSync as mkdirSync67,
125290
126507
  openSync as openSync23,
125291
126508
  readFileSync as readFileSync105,
@@ -125301,7 +126518,7 @@ function isRecord2(v) {
125301
126518
  }
125302
126519
  function readEvalCaseProposals(stateDir) {
125303
126520
  const p = proposalsPath(stateDir);
125304
- if (!existsSync115(p))
126521
+ if (!existsSync116(p))
125305
126522
  return [];
125306
126523
  let raw;
125307
126524
  try {
@@ -125498,7 +126715,7 @@ function registerApply(parent) {
125498
126715
  }));
125499
126716
  process.exit(0);
125500
126717
  }
125501
- if (!existsSync116(proposal.skill_dir)) {
126718
+ if (!existsSync117(proposal.skill_dir)) {
125502
126719
  fail7(`skill dir gone: ${proposal.skill_dir}`);
125503
126720
  }
125504
126721
  const { dir, resolvedDir } = authorizeApplyTarget(proposal.skill_dir, proposal.skill_slug);
@@ -125611,7 +126828,7 @@ function registerSelfImproveBenchCommand(program3) {
125611
126828
  init_esm();
125612
126829
  init_helpers();
125613
126830
  var import_yaml26 = __toESM(require_dist(), 1);
125614
- import { existsSync as existsSync117, readdirSync as readdirSync44, readFileSync as readFileSync107, statSync as statSync62 } from "node:fs";
126831
+ import { existsSync as existsSync118, readdirSync as readdirSync44, readFileSync as readFileSync107, statSync as statSync62 } from "node:fs";
125615
126832
  import { homedir as homedir63 } from "node:os";
125616
126833
  import { join as join120, resolve as resolve63 } from "node:path";
125617
126834
  var PERSONAL_PREFIX2 = "personal-";
@@ -125628,7 +126845,7 @@ function defaultBundledRoot2() {
125628
126845
  }
125629
126846
  function readSkillFrontmatter(skillDir) {
125630
126847
  const mdPath = join120(skillDir, "SKILL.md");
125631
- if (!existsSync117(mdPath))
126848
+ if (!existsSync118(mdPath))
125632
126849
  return null;
125633
126850
  let content;
125634
126851
  try {
@@ -125672,7 +126889,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
125672
126889
  if (!AGENT_NAME_RE3.test(agent))
125673
126890
  return [];
125674
126891
  const skillsDir = join120(agentsRoot, agent, ".claude/skills");
125675
- if (!existsSync117(skillsDir))
126892
+ if (!existsSync118(skillsDir))
125676
126893
  return [];
125677
126894
  const out = [];
125678
126895
  let entries;
@@ -125710,7 +126927,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
125710
126927
  return out;
125711
126928
  }
125712
126929
  function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
125713
- if (!existsSync117(sharedRoot))
126930
+ if (!existsSync118(sharedRoot))
125714
126931
  return [];
125715
126932
  const out = [];
125716
126933
  let entries;
@@ -125748,7 +126965,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
125748
126965
  return out;
125749
126966
  }
125750
126967
  function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
125751
- if (!existsSync117(bundledRoot))
126968
+ if (!existsSync118(bundledRoot))
125752
126969
  return [];
125753
126970
  const out = [];
125754
126971
  let entries;
@@ -125907,7 +127124,7 @@ init_helpers();
125907
127124
  init_docker_socket();
125908
127125
  init_operator_uid();
125909
127126
  import {
125910
- existsSync as existsSync119,
127127
+ existsSync as existsSync120,
125911
127128
  mkdirSync as mkdirSync68,
125912
127129
  readdirSync as readdirSync45,
125913
127130
  writeFileSync as writeFileSync49,
@@ -126290,7 +127507,7 @@ function resolveHostdSkillsTarget(hostHome) {
126290
127507
  console.warn(`switchroom hostd install: ~/.switchroom/skills is a symlink whose target ` + `does not resolve (dangling) \u2014 skipping the skills bind mount. Bundled ` + `skills will be unavailable to rollout/update until the symlink is fixed.`);
126291
127508
  return;
126292
127509
  }
126293
- if (!existsSync119(target)) {
127510
+ if (!existsSync120(target)) {
126294
127511
  console.warn(`switchroom hostd install: ~/.switchroom/skills resolves to "${target}", ` + `which does not exist \u2014 skipping the skills bind mount. Bundled skills ` + `will be unavailable to rollout/update until the symlink target exists.`);
126295
127512
  return;
126296
127513
  }
@@ -126304,7 +127521,7 @@ function hostdComposePath() {
126304
127521
  }
126305
127522
  function backupExistingCompose() {
126306
127523
  const p = hostdComposePath();
126307
- if (!existsSync119(p))
127524
+ if (!existsSync120(p))
126308
127525
  return null;
126309
127526
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
126310
127527
  const bak = `${p}.bak-${ts}`;
@@ -126403,7 +127620,7 @@ function doStatus() {
126403
127620
  const composeYml = hostdComposePath();
126404
127621
  console.log(source_default.bold("switchroom-hostd"));
126405
127622
  console.log("");
126406
- if (!existsSync119(composeYml)) {
127623
+ if (!existsSync120(composeYml)) {
126407
127624
  console.log(source_default.yellow(" compose: not installed"));
126408
127625
  console.log(source_default.dim(" run `switchroom hostd install` to set up."));
126409
127626
  return;
@@ -126424,14 +127641,14 @@ function doStatus() {
126424
127641
  } else {
126425
127642
  console.log(source_default.green(` container: ${ps.stdout.trim()}`));
126426
127643
  }
126427
- if (existsSync119(dir)) {
127644
+ if (existsSync120(dir)) {
126428
127645
  const entries = [];
126429
127646
  try {
126430
127647
  for (const name of readdirSync45(dir)) {
126431
127648
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
126432
127649
  continue;
126433
127650
  const sockPath = join121(dir, name, "sock");
126434
- if (existsSync119(sockPath)) {
127651
+ if (existsSync120(sockPath)) {
126435
127652
  const st = statSync63(sockPath);
126436
127653
  if ((st.mode & 61440) === 49152) {
126437
127654
  entries.push(`${name} \u2192 ${sockPath}`);
@@ -126450,7 +127667,7 @@ function doStatus() {
126450
127667
  }
126451
127668
  function doUninstall() {
126452
127669
  const composeYml = hostdComposePath();
126453
- if (!existsSync119(composeYml)) {
127670
+ if (!existsSync120(composeYml)) {
126454
127671
  console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
126455
127672
  return;
126456
127673
  }
@@ -126474,7 +127691,7 @@ function registerHostdCommand(program3) {
126474
127691
  hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
126475
127692
  hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
126476
127693
  const logPath = opts.path ?? defaultAuditLogPath2();
126477
- if (!existsSync119(logPath)) {
127694
+ if (!existsSync120(logPath)) {
126478
127695
  console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
126479
127696
  The log is created when hostd handles its first privileged-verb request.`));
126480
127697
  return;
@@ -126523,7 +127740,7 @@ init_source();
126523
127740
  init_helpers();
126524
127741
  init_operator_uid();
126525
127742
  init_compose();
126526
- import { chownSync as chownSync13, existsSync as existsSync120, mkdirSync as mkdirSync69, writeFileSync as writeFileSync50, copyFileSync as copyFileSync17 } from "node:fs";
127743
+ import { chownSync as chownSync13, existsSync as existsSync121, mkdirSync as mkdirSync69, writeFileSync as writeFileSync50, copyFileSync as copyFileSync17 } from "node:fs";
126527
127744
  import { homedir as homedir65 } from "node:os";
126528
127745
  import { join as join122 } from "node:path";
126529
127746
  import { spawnSync as spawnSync30 } from "node:child_process";
@@ -126634,7 +127851,7 @@ function webdComposePath() {
126634
127851
  }
126635
127852
  function backupExistingCompose2() {
126636
127853
  const p = webdComposePath();
126637
- if (!existsSync120(p))
127854
+ if (!existsSync121(p))
126638
127855
  return null;
126639
127856
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
126640
127857
  const bak = `${p}.bak-${ts}`;
@@ -126742,7 +127959,7 @@ function doStatus2() {
126742
127959
  const composeYml = webdComposePath();
126743
127960
  console.log(source_default.bold("switchroom-web"));
126744
127961
  console.log("");
126745
- if (!existsSync120(composeYml)) {
127962
+ if (!existsSync121(composeYml)) {
126746
127963
  console.log(source_default.yellow(" compose: not installed"));
126747
127964
  console.log(source_default.dim(" run `switchroom webd install` to set up."));
126748
127965
  return;
@@ -126766,7 +127983,7 @@ function doStatus2() {
126766
127983
  }
126767
127984
  function doUninstall2() {
126768
127985
  const composeYml = webdComposePath();
126769
- if (!existsSync120(composeYml)) {
127986
+ if (!existsSync121(composeYml)) {
126770
127987
  console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
126771
127988
  return;
126772
127989
  }
@@ -127252,9 +128469,9 @@ function printDeepDiveBrief(targets) {
127252
128469
 
127253
128470
  // src/cli/hindsight-watch.ts
127254
128471
  init_source();
127255
- import { existsSync as existsSync125, readdirSync as readdirSync50 } from "node:fs";
128472
+ import { existsSync as existsSync126, readdirSync as readdirSync50 } from "node:fs";
127256
128473
  import { homedir as homedir69 } from "node:os";
127257
- import { join as join125, resolve as resolve66 } from "node:path";
128474
+ import { join as join126, resolve as resolve66 } from "node:path";
127258
128475
 
127259
128476
  // src/host-control/config-degraded.ts
127260
128477
  import { connect as connect3 } from "node:net";
@@ -127451,16 +128668,16 @@ function readLlmSignals(series) {
127451
128668
  }
127452
128669
 
127453
128670
  // src/hindsight-watch/recall-log.ts
127454
- import { closeSync as closeSync24, existsSync as existsSync124, openSync as openSync24, readdirSync as readdirSync48, readSync as readSync6, statSync as statSync64 } from "node:fs";
127455
- import { join as join124 } from "node:path";
128671
+ import { closeSync as closeSync24, existsSync as existsSync125, openSync as openSync24, readdirSync as readdirSync48, readSync as readSync6, statSync as statSync64 } from "node:fs";
128672
+ import { join as join125 } from "node:path";
127456
128673
  var RECALL_WINDOW_ROWS = 200;
127457
128674
  var RECALL_MAX_TAIL_BYTES = 1024 * 1024;
127458
128675
  var RECALL_MAX_ROW_AGE_MS = 24 * 60 * 60 * 1000;
127459
128676
  function recallLogPath2(agentDir) {
127460
- return join124(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
128677
+ return join125(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
127461
128678
  }
127462
128679
  function readRecallLogTail2(path10, windowRows = RECALL_WINDOW_ROWS) {
127463
- if (!existsSync124(path10))
128680
+ if (!existsSync125(path10))
127464
128681
  return [];
127465
128682
  let text;
127466
128683
  let truncatedHead = false;
@@ -127605,7 +128822,7 @@ function probeRecallLogs(agentsDir, now2, windowRows = RECALL_WINDOW_ROWS, maxAg
127605
128822
  const all = [];
127606
128823
  let agents = 0;
127607
128824
  for (const name of names) {
127608
- const rows = withinWindow(readRecallLogTail2(recallLogPath2(join124(agentsDir, name)), windowRows), now2, maxAgeMs);
128825
+ const rows = withinWindow(readRecallLogTail2(recallLogPath2(join125(agentsDir, name)), windowRows), now2, maxAgeMs);
127609
128826
  if (rows.length > 0)
127610
128827
  agents++;
127611
128828
  all.push(...rows);
@@ -128663,7 +129880,7 @@ function registerHindsightWatchCommand(program3) {
128663
129880
  process.exitCode = runInstallCron(opts.cronUser);
128664
129881
  return;
128665
129882
  }
128666
- const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join125(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir69(), ".switchroom", "agents");
129883
+ const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join126(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir69(), ".switchroom", "agents");
128667
129884
  const statePath = opts.state ?? defaultStatePath2();
128668
129885
  const log = (m) => {
128669
129886
  process.stderr.write(`${m}
@@ -128779,7 +129996,7 @@ function collectRelayCandidates(agentsDir) {
128779
129996
  const candidates = [];
128780
129997
  for (const name of readdirSync50(agentsDir).sort()) {
128781
129998
  const sock = resolve66(agentsDir, name, "telegram", "gateway.sock");
128782
- if (existsSync125(sock))
129999
+ if (existsSync126(sock))
128783
130000
  candidates.push({ agent: name, sock });
128784
130001
  }
128785
130002
  return orderRelayCandidates(candidates);
@@ -128802,7 +130019,7 @@ async function notifyOperator(agentsDir, text, log) {
128802
130019
  // src/cli/hindsight-bench.ts
128803
130020
  init_source();
128804
130021
  import { mkdirSync as mkdirSync71, readFileSync as readFileSync113, writeFileSync as writeFileSync52 } from "node:fs";
128805
- import { dirname as dirname51, resolve as resolve67 } from "node:path";
130022
+ import { dirname as dirname52, resolve as resolve67 } from "node:path";
128806
130023
 
128807
130024
  // src/hindsight-bench/anonymise.ts
128808
130025
  function pseudonym(n, width) {
@@ -129930,7 +131147,7 @@ function readResult(path10) {
129930
131147
  }
129931
131148
  function writeOut(path10, body) {
129932
131149
  const abs = resolve67(path10);
129933
- mkdirSync71(dirname51(abs), { recursive: true });
131150
+ mkdirSync71(dirname52(abs), { recursive: true });
129934
131151
  writeFileSync52(abs, body);
129935
131152
  process.stderr.write(`${source_default.green("\u2713")} wrote ${abs}
129936
131153
  `);
@@ -130135,13 +131352,13 @@ async function runMeasureMode(opts) {
130135
131352
 
130136
131353
  // src/cli/openrouter-watch.ts
130137
131354
  init_source();
130138
- import { existsSync as existsSync127, readdirSync as readdirSync51 } from "node:fs";
131355
+ import { existsSync as existsSync128, readdirSync as readdirSync51 } from "node:fs";
130139
131356
  import { homedir as homedir71 } from "node:os";
130140
- import { join as join126, resolve as resolve69 } from "node:path";
131357
+ import { join as join127, resolve as resolve69 } from "node:path";
130141
131358
 
130142
131359
  // src/openrouter/install-cron.ts
130143
- import { existsSync as existsSync126, mkdirSync as mkdirSync72, readFileSync as readFileSync114, renameSync as renameSync33, writeFileSync as writeFileSync53 } from "node:fs";
130144
- import { dirname as dirname52 } from "node:path";
131360
+ import { existsSync as existsSync127, mkdirSync as mkdirSync72, readFileSync as readFileSync114, renameSync as renameSync33, writeFileSync as writeFileSync53 } from "node:fs";
131361
+ import { dirname as dirname53 } from "node:path";
130145
131362
  var CRON_PATH3 = "/etc/cron.d/openrouter-watch";
130146
131363
  var CRON_SCHEDULE2 = "17 * * * *";
130147
131364
  var CRON_LOG_PATH3 = "/var/log/openrouter-watch.log";
@@ -130158,14 +131375,14 @@ function renderCron3(opts) {
130158
131375
  function installCron3(opts) {
130159
131376
  const path10 = opts.path ?? CRON_PATH3;
130160
131377
  const content = renderCron3(opts);
130161
- if (existsSync126(path10)) {
131378
+ if (existsSync127(path10)) {
130162
131379
  try {
130163
131380
  if (readFileSync114(path10, "utf8") === content) {
130164
131381
  return { status: "unchanged", path: path10, content };
130165
131382
  }
130166
131383
  } catch {}
130167
131384
  }
130168
- mkdirSync72(dirname52(path10), { recursive: true });
131385
+ mkdirSync72(dirname53(path10), { recursive: true });
130169
131386
  const tmp = `${path10}.${process.pid}.tmp`;
130170
131387
  writeFileSync53(tmp, content, { mode: 420 });
130171
131388
  renameSync33(tmp, path10);
@@ -130174,7 +131391,7 @@ function installCron3(opts) {
130174
131391
  // src/openrouter/state.ts
130175
131392
  import { mkdirSync as mkdirSync73, readFileSync as readFileSync115, renameSync as renameSync34, writeFileSync as writeFileSync54 } from "node:fs";
130176
131393
  import { homedir as homedir70 } from "node:os";
130177
- import { dirname as dirname53, resolve as resolve68 } from "node:path";
131394
+ import { dirname as dirname54, resolve as resolve68 } from "node:path";
130178
131395
  function defaultStatePath3(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir70()) {
130179
131396
  return resolve68(home2, ".switchroom", "openrouter-watch", "state.json");
130180
131397
  }
@@ -130201,7 +131418,7 @@ function loadState2(path10) {
130201
131418
  return out;
130202
131419
  }
130203
131420
  function saveState2(path10, state) {
130204
- mkdirSync73(dirname53(path10), { recursive: true, mode: 448 });
131421
+ mkdirSync73(dirname54(path10), { recursive: true, mode: 448 });
130205
131422
  const tmp = `${path10}.${process.pid}.tmp`;
130206
131423
  const payload = { v: 1, ...state };
130207
131424
  writeFileSync54(tmp, JSON.stringify(payload, null, 2) + `
@@ -130292,7 +131509,7 @@ function registerOpenRouterWatchCommand(program3) {
130292
131509
  process.exitCode = runInstallCron2(opts.cronUser);
130293
131510
  return;
130294
131511
  }
130295
- const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join126(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir71(), ".switchroom", "agents");
131512
+ const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join127(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir71(), ".switchroom", "agents");
130296
131513
  const statePath = opts.state ?? defaultStatePath3();
130297
131514
  const log = (m) => {
130298
131515
  process.stderr.write(`${m}
@@ -130391,7 +131608,7 @@ async function notifyOperator2(agentsDir, text, log) {
130391
131608
  try {
130392
131609
  for (const name of readdirSync51(agentsDir).sort()) {
130393
131610
  const sock = resolve69(agentsDir, name, "telegram", "gateway.sock");
130394
- if (existsSync127(sock))
131611
+ if (existsSync128(sock))
130395
131612
  candidates.push({ agent: name, sock });
130396
131613
  }
130397
131614
  } catch (e) {
@@ -130412,7 +131629,7 @@ init_sync();
130412
131629
  init_install_cron2();
130413
131630
  init_flock();
130414
131631
  import { homedir as homedir72 } from "node:os";
130415
- import { join as join127 } from "node:path";
131632
+ import { join as join128 } from "node:path";
130416
131633
  function switchroomHome() {
130417
131634
  return process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir72();
130418
131635
  }
@@ -130424,9 +131641,9 @@ function resolveSyncOptions(configPath, opts) {
130424
131641
  cfg = loaded.config_repo;
130425
131642
  } catch {}
130426
131643
  const home2 = switchroomHome();
130427
- const repoPath = opts.path ? resolvePath(opts.path) : cfg.path ? resolvePath(cfg.path) : join127(home2, ".switchroom-config");
130428
- const livePath = process.env.SWITCHROOM_CONFIG ?? join127(home2, ".switchroom", "switchroom.yaml");
130429
- const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join127(home2, ".switchroom", "agents");
131644
+ const repoPath = opts.path ? resolvePath(opts.path) : cfg.path ? resolvePath(cfg.path) : join128(home2, ".switchroom-config");
131645
+ const livePath = process.env.SWITCHROOM_CONFIG ?? join128(home2, ".switchroom", "switchroom.yaml");
131646
+ const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join128(home2, ".switchroom", "agents");
130430
131647
  const push = opts.push === false ? false : cfg.push ?? true;
130431
131648
  const remote = opts.remote ?? cfg.remote ?? "origin";
130432
131649
  const requirePrivate = opts.requirePrivate ?? cfg.require_private ?? true;