switchroom 0.21.13 → 0.21.14

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.21.13", COMMIT_SHA = "4a70ee58", COMMIT_DATE = "2026-08-15T09:11:57Z";
2123
+ var VERSION = "0.21.14", COMMIT_SHA = "2d43063c", COMMIT_DATE = "2026-08-16T08:34:12Z";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -16025,6 +16025,23 @@ function loadHostCapabilities() {
16025
16025
  warnDegradedHostCapabilities(read);
16026
16026
  return read.caps;
16027
16027
  }
16028
+ function isDefaultedVoiceEngine(r) {
16029
+ return r.reason !== "verdict";
16030
+ }
16031
+ function resolveVoiceEngine() {
16032
+ const read = readHostCapabilities();
16033
+ warnDegradedHostCapabilities(read);
16034
+ if (read.status === "ok" && read.caps) {
16035
+ return {
16036
+ engine: read.caps.voice.engine,
16037
+ reason: "verdict",
16038
+ path: read.path,
16039
+ detail: ""
16040
+ };
16041
+ }
16042
+ const reason = read.status === "absent" ? "no-verdict" : read.status === "unreadable" ? "unreadable" : "malformed";
16043
+ return { engine: "cloud", reason, path: read.path, detail: read.detail };
16044
+ }
16028
16045
  var HOST_CAPABILITIES_VERSION = 1, _warnedReads;
16029
16046
  var init_host_capabilities = __esm(() => {
16030
16047
  init_paths();
@@ -33772,7 +33789,7 @@ function generateCompose(opts) {
33772
33789
  const precreateHostDirs = opts.precreateHostDirs ?? (opts.homeDir !== undefined || opts.probeHomeDir !== undefined);
33773
33790
  const switchroomConfigPath = resolveConfigMountSource(opts.switchroomConfigPath, homePrefix);
33774
33791
  const bundledSkillsPoolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
33775
- const voiceEngine = opts.voiceEngine ?? loadHostCapabilities()?.voice.engine ?? "cloud";
33792
+ const voiceEngine = opts.voiceEngine ?? resolveVoiceEngine().engine;
33776
33793
  let resolvedAnalyticsId = null;
33777
33794
  if (probeHome !== "") {
33778
33795
  const idPath = join18(probeHome, ".switchroom", "analytics-id");
@@ -34549,11 +34566,118 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
34549
34566
  var CONTAINER_OPERATOR_HOME = "/host-home", SWITCHROOM_HOME_MARKER = "switchroom.yaml";
34550
34567
  var init_operator_uid = () => {};
34551
34568
 
34569
+ // src/agents/compose-env.ts
34570
+ import { existsSync as existsSync25 } from "node:fs";
34571
+ import { dirname as dirname10 } from "node:path";
34572
+ function composeEnvPath(composePath) {
34573
+ return dirname10(composePath) + "/.env";
34574
+ }
34575
+ function composeEnvFileArgs(composePath) {
34576
+ const envPath = composeEnvPath(composePath);
34577
+ return existsSync25(envPath) ? ["--env-file", envPath] : [];
34578
+ }
34579
+ var init_compose_env = () => {};
34580
+
34581
+ // src/agents/singleton-reconcile.ts
34582
+ import { execFileSync as execFileSync9 } from "node:child_process";
34583
+ import { readFileSync as readFileSync19 } from "node:fs";
34584
+ function resolveSingletonServices(voiceEngine) {
34585
+ const engine = voiceEngine ?? resolveVoiceEngine().engine;
34586
+ return engine === "local" ? [...CORE_SINGLETON_SERVICES, VOICE_SIDECAR_SERVICE] : [...CORE_SINGLETON_SERVICES];
34587
+ }
34588
+ function singletonContainerName(svc) {
34589
+ return svc.startsWith("switchroom-") ? svc : `switchroom-${svc}`;
34590
+ }
34591
+ function readPinnedSingletonImages(composeText, services = SINGLETON_SERVICES) {
34592
+ let doc;
34593
+ try {
34594
+ doc = import_yaml3.parse(composeText);
34595
+ } catch {
34596
+ return {};
34597
+ }
34598
+ const out = {};
34599
+ for (const svc of services) {
34600
+ const img = doc?.services?.[svc]?.image;
34601
+ if (typeof img === "string" && img.length > 0)
34602
+ out[svc] = img;
34603
+ }
34604
+ return out;
34605
+ }
34606
+ function defaultInspectImage(dockerBin, container) {
34607
+ try {
34608
+ const out = execFileSync9(dockerBin, ["inspect", "-f", "{{.Config.Image}}", container], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000 }).trim();
34609
+ return out.length > 0 ? out : null;
34610
+ } catch {
34611
+ return null;
34612
+ }
34613
+ }
34614
+ function detectSingletonDrift(deps) {
34615
+ const dockerBin = deps.dockerBin ?? "docker";
34616
+ const readCompose = deps.readCompose ?? ((p) => readFileSync19(p, "utf-8"));
34617
+ const inspectImage = deps.inspectImage ?? ((c) => defaultInspectImage(dockerBin, c));
34618
+ const services = resolveSingletonServices(deps.voiceEngine);
34619
+ let pinned = {};
34620
+ try {
34621
+ pinned = readPinnedSingletonImages(readCompose(deps.composeFile), services);
34622
+ } catch {
34623
+ pinned = {};
34624
+ }
34625
+ return services.map((service) => {
34626
+ const container = singletonContainerName(service);
34627
+ const running = inspectImage(container);
34628
+ const pin = pinned[service] ?? null;
34629
+ const needsRecreate = pin !== null && running !== pin;
34630
+ return { service, container, running, pinned: pin, needsRecreate };
34631
+ });
34632
+ }
34633
+ function defaultRecreate(dockerBin, project, composeFile, service) {
34634
+ execFileSync9(dockerBin, ["compose", "-p", project, "-f", composeFile, ...composeEnvFileArgs(composeFile), "up", "-d", "--no-deps", service], { stdio: ["ignore", "pipe", "pipe"], timeout: 120000 });
34635
+ }
34636
+ function reconcileSingletons(deps) {
34637
+ const dockerBin = deps.dockerBin ?? "docker";
34638
+ const project = deps.project ?? "switchroom";
34639
+ const log = deps.log ?? ((m) => process.stderr.write(m + `
34640
+ `));
34641
+ const recreate = deps.recreate ?? ((svc) => defaultRecreate(dockerBin, project, deps.composeFile, svc));
34642
+ const drift = detectSingletonDrift(deps);
34643
+ const recreated = [];
34644
+ const failed = [];
34645
+ for (const d of drift) {
34646
+ if (!d.needsRecreate)
34647
+ continue;
34648
+ log(`singleton-reconcile: ${d.service} drift ${d.running ?? "<absent>"} \u2192 ${d.pinned} \u2014 recreating`);
34649
+ try {
34650
+ recreate(d.service);
34651
+ recreated.push(d.service);
34652
+ } catch (err) {
34653
+ const msg = err instanceof Error ? err.message : String(err);
34654
+ log(`singleton-reconcile: ${d.service} recreate FAILED: ${msg}`);
34655
+ failed.push({ service: d.service, error: msg });
34656
+ }
34657
+ }
34658
+ return { drift, recreated, failed };
34659
+ }
34660
+ var import_yaml3, CORE_SINGLETON_SERVICES, VOICE_SIDECAR_SERVICE = "voice-sidecar", SINGLETON_SERVICES;
34661
+ var init_singleton_reconcile = __esm(() => {
34662
+ init_host_capabilities();
34663
+ init_compose_env();
34664
+ import_yaml3 = __toESM(require_dist(), 1);
34665
+ CORE_SINGLETON_SERVICES = [
34666
+ "vault-broker",
34667
+ "approval-kernel",
34668
+ "switchroom-auth-broker"
34669
+ ];
34670
+ SINGLETON_SERVICES = [
34671
+ ...CORE_SINGLETON_SERVICES,
34672
+ VOICE_SIDECAR_SERVICE
34673
+ ];
34674
+ });
34675
+
34552
34676
  // src/cli/write-compose.ts
34553
34677
  import { chownSync as chownSync3 } from "node:fs";
34554
34678
  import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
34555
34679
  import { homedir as homedir10 } from "node:os";
34556
- import { basename as basename4, dirname as dirname10, join as join20 } from "node:path";
34680
+ import { basename as basename4, dirname as dirname11, join as join20 } from "node:path";
34557
34681
  function agentHadLiteLLMRouting(composeContent, agentName) {
34558
34682
  const lines = composeContent.split(`
34559
34683
  `);
@@ -34665,6 +34789,46 @@ function resolveHostHomeForCompose() {
34665
34789
  }
34666
34790
  return homedir10();
34667
34791
  }
34792
+ function parseComposeServiceNames(compose) {
34793
+ const lines = compose.split(`
34794
+ `);
34795
+ const names = [];
34796
+ let inServices = false;
34797
+ for (const line of lines) {
34798
+ if (/^services:\s*$/.test(line)) {
34799
+ inServices = true;
34800
+ continue;
34801
+ }
34802
+ if (inServices && /^\S/.test(line))
34803
+ break;
34804
+ if (!inServices)
34805
+ continue;
34806
+ const m = /^ {2}([A-Za-z0-9_.-]+):\s*$/.exec(line);
34807
+ if (m)
34808
+ names.push(m[1]);
34809
+ }
34810
+ return names;
34811
+ }
34812
+ function voiceSidecarDropMessage(r) {
34813
+ const why = r.reason === "no-verdict" ? `no verdict file at ${r.path}` : `the verdict at ${r.path} is ${r.reason} \u2014 ${r.detail}`;
34814
+ return `voice-sidecar is being REMOVED because ${why}, so the voice engine ` + "defaulted to `cloud`. This host is not known to lack a GPU \u2014 switchroom " + "could not tell. Re-run `switchroom setup` to re-probe before applying, or confirm the removal is intended.";
34815
+ }
34816
+ function detectVoiceSidecarDrop(previous, content, resolution) {
34817
+ if (!isDefaultedVoiceEngine(resolution))
34818
+ return null;
34819
+ if (previous === null)
34820
+ return null;
34821
+ if (!parseComposeServiceNames(previous).includes(VOICE_SIDECAR_SERVICE))
34822
+ return null;
34823
+ if (parseComposeServiceNames(content).includes(VOICE_SIDECAR_SERVICE))
34824
+ return null;
34825
+ return {
34826
+ reason: resolution.reason,
34827
+ path: resolution.path,
34828
+ detail: resolution.detail,
34829
+ message: voiceSidecarDropMessage(resolution)
34830
+ };
34831
+ }
34668
34832
  async function computeComposeContent(opts) {
34669
34833
  const release = resolveRelease({ override: opts.releaseOverride, root: opts.config.release });
34670
34834
  const imageTag = resolveImageTag(release);
@@ -34677,10 +34841,12 @@ async function computeComposeContent(opts) {
34677
34841
  previous = null;
34678
34842
  }
34679
34843
  const litellmConfirmedAgents = await resolveLiteLLMConfirmedAgents(opts.config, previous);
34844
+ const voiceResolution = resolveVoiceEngine();
34680
34845
  const content = generateCompose({
34681
34846
  config: opts.config,
34682
34847
  imageTag,
34683
34848
  litellmConfirmedAgents,
34849
+ voiceEngine: voiceResolution.engine,
34684
34850
  buildMode: opts.buildMode ?? "pull",
34685
34851
  buildContext: opts.buildContext,
34686
34852
  homeDir: resolveHostHomeForCompose(),
@@ -34690,7 +34856,13 @@ async function computeComposeContent(opts) {
34690
34856
  dockerSocketPath: resolveDockerSocketPath()
34691
34857
  });
34692
34858
  const previousImageTag = previous ? AGENT_IMAGE_TAG_RE.exec(previous)?.[1] ?? null : null;
34693
- return { content, imageTag, previous, previousImageTag };
34859
+ const voiceSidecarDrop = detectVoiceSidecarDrop(previous, content, voiceResolution);
34860
+ if (voiceSidecarDrop && !_warnedVoiceDrop) {
34861
+ _warnedVoiceDrop = true;
34862
+ process.stderr.write(`[switchroom] WARNING: ${voiceSidecarDrop.message}
34863
+ `);
34864
+ }
34865
+ return { content, imageTag, previous, previousImageTag, voiceSidecarDrop };
34694
34866
  }
34695
34867
  function composeBackupPath(composePath) {
34696
34868
  return composePath + BACKUP_SUFFIX;
@@ -34721,7 +34893,7 @@ function shouldBackupCompose(previous, next, existingBackupTag) {
34721
34893
  async function writeComposeFile(opts) {
34722
34894
  const { content, imageTag, previous, previousImageTag } = await computeComposeContent(opts);
34723
34895
  const operatorUid = resolveOperatorUid();
34724
- await mkdir(dirname10(opts.composePath), { recursive: true });
34896
+ await mkdir(dirname11(opts.composePath), { recursive: true });
34725
34897
  if (shouldBackupCompose(previous, content, await readBackupImageTag(opts.composePath))) {
34726
34898
  try {
34727
34899
  await copyFile(opts.composePath, composeBackupPath(opts.composePath));
@@ -34743,18 +34915,20 @@ async function writeComposeFile(opts) {
34743
34915
  previousImageTag
34744
34916
  };
34745
34917
  }
34746
- var AGENT_IMAGE_TAG_RE, CONTAINER_CONFIG_PREFIX = "/state/config/", BACKUP_SUFFIX = ".bak";
34918
+ var AGENT_IMAGE_TAG_RE, CONTAINER_CONFIG_PREFIX = "/state/config/", _warnedVoiceDrop = false, BACKUP_SUFFIX = ".bak";
34747
34919
  var init_write_compose = __esm(() => {
34748
34920
  init_compose();
34749
34921
  init_docker_socket();
34750
34922
  init_agent_config();
34751
34923
  init_operator_uid();
34752
34924
  init_merge();
34925
+ init_singleton_reconcile();
34926
+ init_host_capabilities();
34753
34927
  AGENT_IMAGE_TAG_RE = /image:\s*\S*switchroom-agent:(\S+)/;
34754
34928
  });
34755
34929
 
34756
34930
  // src/agents/tmux.ts
34757
- import { execFileSync as execFileSync9 } from "node:child_process";
34931
+ import { execFileSync as execFileSync10 } from "node:child_process";
34758
34932
  function sendAgentInterrupt(opts) {
34759
34933
  const { agentName } = opts;
34760
34934
  const attempts = typeof opts.attempts === "number" && opts.attempts > 0 ? opts.attempts : 1;
@@ -34764,7 +34938,7 @@ function sendAgentInterrupt(opts) {
34764
34938
  let lastError = null;
34765
34939
  for (let i = 0;i < attempts; i++) {
34766
34940
  try {
34767
- execFileSync9("tmux", args, {
34941
+ execFileSync10("tmux", args, {
34768
34942
  timeout: 3000,
34769
34943
  stdio: ["ignore", "pipe", "pipe"]
34770
34944
  });
@@ -34789,23 +34963,11 @@ var init_tmux = __esm(() => {
34789
34963
  MAX_BYTES = 10 * 1024 * 1024;
34790
34964
  });
34791
34965
 
34792
- // src/agents/compose-env.ts
34793
- import { existsSync as existsSync25 } from "node:fs";
34794
- import { dirname as dirname11 } from "node:path";
34795
- function composeEnvPath(composePath) {
34796
- return dirname11(composePath) + "/.env";
34797
- }
34798
- function composeEnvFileArgs(composePath) {
34799
- const envPath = composeEnvPath(composePath);
34800
- return existsSync25(envPath) ? ["--env-file", envPath] : [];
34801
- }
34802
- var init_compose_env = () => {};
34803
-
34804
34966
  // src/agents/docker-fleet.ts
34805
34967
  import { resolve as resolve17 } from "node:path";
34806
34968
  import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync8 } from "node:fs";
34807
34969
  import { homedir as homedir11 } from "node:os";
34808
- import { execFileSync as execFileSync10 } from "node:child_process";
34970
+ import { execFileSync as execFileSync11 } from "node:child_process";
34809
34971
  function resolveSwitchroomHome(explicit) {
34810
34972
  if (explicit && explicit.length > 0)
34811
34973
  return explicit;
@@ -34837,7 +34999,7 @@ function bringUpAgentService(opts) {
34837
34999
  const dockerBin = opts.dockerBin ?? "docker";
34838
35000
  const stdio = opts.stdio ?? "inherit";
34839
35001
  for (const svc of ["vault-broker", "approval-kernel", "switchroom-auth-broker"]) {
34840
- execFileSync10(dockerBin, [
35002
+ execFileSync11(dockerBin, [
34841
35003
  "compose",
34842
35004
  "-f",
34843
35005
  composePath,
@@ -34849,7 +35011,7 @@ function bringUpAgentService(opts) {
34849
35011
  svc
34850
35012
  ], { stdio });
34851
35013
  }
34852
- execFileSync10(dockerBin, [
35014
+ execFileSync11(dockerBin, [
34853
35015
  "compose",
34854
35016
  "-f",
34855
35017
  composePath,
@@ -34868,101 +35030,6 @@ var init_docker_fleet = __esm(() => {
34868
35030
  init_loader();
34869
35031
  });
34870
35032
 
34871
- // src/agents/singleton-reconcile.ts
34872
- import { execFileSync as execFileSync11 } from "node:child_process";
34873
- import { readFileSync as readFileSync19 } from "node:fs";
34874
- function resolveSingletonServices(voiceEngine) {
34875
- const engine = voiceEngine ?? loadHostCapabilities()?.voice.engine ?? "cloud";
34876
- return engine === "local" ? [...CORE_SINGLETON_SERVICES, VOICE_SIDECAR_SERVICE] : [...CORE_SINGLETON_SERVICES];
34877
- }
34878
- function singletonContainerName(svc) {
34879
- return svc.startsWith("switchroom-") ? svc : `switchroom-${svc}`;
34880
- }
34881
- function readPinnedSingletonImages(composeText, services = SINGLETON_SERVICES) {
34882
- let doc;
34883
- try {
34884
- doc = import_yaml3.parse(composeText);
34885
- } catch {
34886
- return {};
34887
- }
34888
- const out = {};
34889
- for (const svc of services) {
34890
- const img = doc?.services?.[svc]?.image;
34891
- if (typeof img === "string" && img.length > 0)
34892
- out[svc] = img;
34893
- }
34894
- return out;
34895
- }
34896
- function defaultInspectImage(dockerBin, container) {
34897
- try {
34898
- const out = execFileSync11(dockerBin, ["inspect", "-f", "{{.Config.Image}}", container], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000 }).trim();
34899
- return out.length > 0 ? out : null;
34900
- } catch {
34901
- return null;
34902
- }
34903
- }
34904
- function detectSingletonDrift(deps) {
34905
- const dockerBin = deps.dockerBin ?? "docker";
34906
- const readCompose = deps.readCompose ?? ((p) => readFileSync19(p, "utf-8"));
34907
- const inspectImage = deps.inspectImage ?? ((c) => defaultInspectImage(dockerBin, c));
34908
- const services = resolveSingletonServices(deps.voiceEngine);
34909
- let pinned = {};
34910
- try {
34911
- pinned = readPinnedSingletonImages(readCompose(deps.composeFile), services);
34912
- } catch {
34913
- pinned = {};
34914
- }
34915
- return services.map((service) => {
34916
- const container = singletonContainerName(service);
34917
- const running = inspectImage(container);
34918
- const pin = pinned[service] ?? null;
34919
- const needsRecreate = pin !== null && running !== pin;
34920
- return { service, container, running, pinned: pin, needsRecreate };
34921
- });
34922
- }
34923
- function defaultRecreate(dockerBin, project, composeFile, service) {
34924
- execFileSync11(dockerBin, ["compose", "-p", project, "-f", composeFile, ...composeEnvFileArgs(composeFile), "up", "-d", "--no-deps", service], { stdio: ["ignore", "pipe", "pipe"], timeout: 120000 });
34925
- }
34926
- function reconcileSingletons(deps) {
34927
- const dockerBin = deps.dockerBin ?? "docker";
34928
- const project = deps.project ?? "switchroom";
34929
- const log = deps.log ?? ((m) => process.stderr.write(m + `
34930
- `));
34931
- const recreate = deps.recreate ?? ((svc) => defaultRecreate(dockerBin, project, deps.composeFile, svc));
34932
- const drift = detectSingletonDrift(deps);
34933
- const recreated = [];
34934
- const failed = [];
34935
- for (const d of drift) {
34936
- if (!d.needsRecreate)
34937
- continue;
34938
- log(`singleton-reconcile: ${d.service} drift ${d.running ?? "<absent>"} \u2192 ${d.pinned} \u2014 recreating`);
34939
- try {
34940
- recreate(d.service);
34941
- recreated.push(d.service);
34942
- } catch (err) {
34943
- const msg = err instanceof Error ? err.message : String(err);
34944
- log(`singleton-reconcile: ${d.service} recreate FAILED: ${msg}`);
34945
- failed.push({ service: d.service, error: msg });
34946
- }
34947
- }
34948
- return { drift, recreated, failed };
34949
- }
34950
- var import_yaml3, CORE_SINGLETON_SERVICES, VOICE_SIDECAR_SERVICE = "voice-sidecar", SINGLETON_SERVICES;
34951
- var init_singleton_reconcile = __esm(() => {
34952
- init_host_capabilities();
34953
- init_compose_env();
34954
- import_yaml3 = __toESM(require_dist(), 1);
34955
- CORE_SINGLETON_SERVICES = [
34956
- "vault-broker",
34957
- "approval-kernel",
34958
- "switchroom-auth-broker"
34959
- ];
34960
- SINGLETON_SERVICES = [
34961
- ...CORE_SINGLETON_SERVICES,
34962
- VOICE_SIDECAR_SERVICE
34963
- ];
34964
- });
34965
-
34966
35033
  // src/agents/lifecycle.ts
34967
35034
  import { execFileSync as execFileSync12, spawn, spawnSync as spawnSync2 } from "node:child_process";
34968
35035
  import { existsSync as existsSync26, mkdirSync as mkdirSync18, writeFileSync as writeFileSync9, renameSync as renameSync7, readFileSync as readFileSync20 } from "node:fs";
@@ -48724,8 +48791,213 @@ var init_doctor_memory = __esm(() => {
48724
48791
  MIN_HINDSIGHT_SHM_BYTES = 1024 * 1024 * 1024;
48725
48792
  });
48726
48793
 
48794
+ // src/memory/hindsight-shim-contract.ts
48795
+ function openApiHasRoute(spec, route) {
48796
+ const methods = spec.paths?.[route.path];
48797
+ if (!methods || typeof methods !== "object")
48798
+ return false;
48799
+ return Object.prototype.hasOwnProperty.call(methods, route.method.toLowerCase());
48800
+ }
48801
+ function missingRoutesForTool(spec, toolName) {
48802
+ const required = SYNTHESIZED_TOOL_ROUTES[toolName];
48803
+ if (!required)
48804
+ return [];
48805
+ return required.filter((r) => !openApiHasRoute(spec, r));
48806
+ }
48807
+ async function fetchHindsightOpenApi(apiBaseUrl, opts = {}) {
48808
+ const doFetch = opts.fetchImpl ?? fetch;
48809
+ const base = apiBaseUrl.replace(/\/+$/, "");
48810
+ const controller = new AbortController;
48811
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? OPENAPI_FETCH_TIMEOUT_MS);
48812
+ try {
48813
+ const res = await doFetch(`${base}/openapi.json`, { signal: controller.signal });
48814
+ if (!res.ok)
48815
+ return null;
48816
+ const body = await res.json();
48817
+ if (!body || typeof body !== "object" || typeof body.paths !== "object") {
48818
+ return null;
48819
+ }
48820
+ return body;
48821
+ } catch {
48822
+ return null;
48823
+ } finally {
48824
+ clearTimeout(timer);
48825
+ }
48826
+ }
48827
+
48828
+ class ShimContractPin {
48829
+ apiBaseUrl;
48830
+ opts;
48831
+ cached = null;
48832
+ cachedAt = null;
48833
+ failedAt = null;
48834
+ constructor(apiBaseUrl, opts = {}) {
48835
+ this.apiBaseUrl = apiBaseUrl;
48836
+ this.opts = opts;
48837
+ }
48838
+ now() {
48839
+ return this.opts.now ? this.opts.now() : Date.now();
48840
+ }
48841
+ async spec() {
48842
+ const positiveCacheMs = this.opts.positiveCacheMs ?? 0;
48843
+ if (this.cached) {
48844
+ const stale = positiveCacheMs > 0 && this.cachedAt !== null && this.now() - this.cachedAt >= positiveCacheMs;
48845
+ if (!stale)
48846
+ return this.cached;
48847
+ this.cached = null;
48848
+ this.cachedAt = null;
48849
+ }
48850
+ const negativeCacheMs = this.opts.negativeCacheMs ?? 0;
48851
+ if (negativeCacheMs > 0 && this.failedAt !== null && this.now() - this.failedAt < negativeCacheMs) {
48852
+ return null;
48853
+ }
48854
+ const fetched = await fetchHindsightOpenApi(this.apiBaseUrl, this.opts);
48855
+ if (fetched) {
48856
+ this.cached = fetched;
48857
+ this.cachedAt = this.now();
48858
+ this.failedAt = null;
48859
+ } else if (negativeCacheMs > 0) {
48860
+ this.failedAt = this.now();
48861
+ }
48862
+ return fetched;
48863
+ }
48864
+ async preflight(toolName) {
48865
+ const spec = await this.spec();
48866
+ if (!spec)
48867
+ return { ok: true };
48868
+ const missing = missingRoutesForTool(spec, toolName);
48869
+ if (missing.length === 0)
48870
+ return { ok: true };
48871
+ const engineVersion = spec.info?.version ?? "unknown";
48872
+ const routeList = missing.map((r) => `${r.method.toUpperCase()} ${r.path}`).join(", ");
48873
+ return {
48874
+ ok: false,
48875
+ text: `${toolName} is unavailable: the live Hindsight engine (api_version ` + `${engineVersion}) no longer exposes the REST route(s) this tool is ` + `synthesized over: ${routeList}. This is not a silent empty result \u2014 ` + `the shim confirmed the route is gone and is refusing the call rather ` + `than guessing. If the engine renamed/moved the route, this tool's ` + `implementation needs updating; if the engine now ships this ` + `capability as a real MCP tool, the synthesis should be retired (see ` + `the retirement seam in withSynthesizedTools, hindsight-mcp-shim.ts). ` + `\`switchroom doctor\`'s hindsight shim contract rows carry the same ` + `finding fleet-wide.`
48876
+ };
48877
+ }
48878
+ }
48879
+ var OPENAPI_FETCH_TIMEOUT_MS = 3000, SYNTHESIZED_TOOL_ROUTES, SYNTHESIZED_ROUTE_TOOL_NAMES;
48880
+ var init_hindsight_shim_contract = __esm(() => {
48881
+ SYNTHESIZED_TOOL_ROUTES = {
48882
+ deactivate_directive: [
48883
+ { path: "/v1/default/banks/{bank_id}/directives", method: "get" },
48884
+ {
48885
+ path: "/v1/default/banks/{bank_id}/directives/{directive_id}",
48886
+ method: "patch"
48887
+ }
48888
+ ],
48889
+ reactivate_directive: [
48890
+ { path: "/v1/default/banks/{bank_id}/directives", method: "get" },
48891
+ {
48892
+ path: "/v1/default/banks/{bank_id}/directives/{directive_id}",
48893
+ method: "patch"
48894
+ }
48895
+ ],
48896
+ search_knowledge_pages: [
48897
+ {
48898
+ path: "/v1/default/banks/{bank_id}/knowledge-base/search",
48899
+ method: "get"
48900
+ }
48901
+ ],
48902
+ get_knowledge_page: [
48903
+ {
48904
+ path: "/v1/default/banks/{bank_id}/knowledge-base/pages/{page_id}",
48905
+ method: "get"
48906
+ }
48907
+ ],
48908
+ get_knowledge_tree: [
48909
+ {
48910
+ path: "/v1/default/banks/{bank_id}/knowledge-base/tree",
48911
+ method: "get"
48912
+ }
48913
+ ]
48914
+ };
48915
+ SYNTHESIZED_ROUTE_TOOL_NAMES = Object.keys(SYNTHESIZED_TOOL_ROUTES);
48916
+ });
48917
+
48918
+ // src/cli/doctor-hindsight-shim-contract.ts
48919
+ function classifySynthesizedToolRoute(spec, toolName) {
48920
+ const missing = missingRoutesForTool(spec, toolName);
48921
+ if (missing.length === 0)
48922
+ return null;
48923
+ const routeList = missing.map((r) => `${r.method.toUpperCase()} ${r.path}`).join(", ");
48924
+ return {
48925
+ name: `${SHIM_CONTRACT_CHECK_PREFIX}: ${toolName}`,
48926
+ status: "fail",
48927
+ detail: `the live engine's /openapi.json no longer declares [${routeList}] \u2014 ` + `the REST route(s) '${toolName}' is synthesized over. Every call to ` + `this tool will now be loud-rejected by the shim's contract preflight ` + `(ShimContractPin.preflight in hindsight-mcp-shim.ts) rather than ` + `silently returning nothing, but the tool is effectively dead until ` + `this is fixed.`,
48928
+ fix: "If the engine renamed/moved the route, update SYNTHESIZED_TOOL_ROUTES " + "(src/memory/hindsight-shim-contract.ts) and the corresponding " + "DirectiveAdmin/KnowledgeAdmin call to match. If the engine now " + "registers this capability as a real MCP tool, retire the synthesis " + "instead \u2014 see the retirement seam in withSynthesizedTools " + "(src/cli/hindsight-mcp-shim.ts)."
48929
+ };
48930
+ }
48931
+ function classifyShimContractVersion(spec) {
48932
+ const name = `${SHIM_CONTRACT_CHECK_PREFIX}: version`;
48933
+ const live = spec.info?.version;
48934
+ if (typeof live !== "string" || live.length === 0) {
48935
+ return {
48936
+ name,
48937
+ status: "warn",
48938
+ detail: "/openapi.json has no info.version \u2014 cannot confirm the shim's " + "REST route contract is pinned to a known engine version."
48939
+ };
48940
+ }
48941
+ const cmp = compareApiVersion(live, HINDSIGHT_MIN_API_VERSION);
48942
+ if (cmp === 0) {
48943
+ return {
48944
+ name,
48945
+ status: "ok",
48946
+ detail: `/openapi.json info.version ${live} matches the pinned ${HINDSIGHT_MIN_API_VERSION}`
48947
+ };
48948
+ }
48949
+ if (cmp < 0) {
48950
+ return {
48951
+ name,
48952
+ status: "fail",
48953
+ detail: `/openapi.json info.version ${live} is OLDER than the pinned ` + `${HINDSIGHT_MIN_API_VERSION} \u2014 the REST route contract the five ` + `synthesized tools depend on may not exist on this server.`,
48954
+ fix: "Same remediation as the `hindsight version` check: update the " + "pinned image, or deliberately re-pin HINDSIGHT_MIN_API_VERSION to " + "this older version."
48955
+ };
48956
+ }
48957
+ return {
48958
+ name,
48959
+ status: "warn",
48960
+ detail: `/openapi.json info.version ${live} is NEWER than the pinned ` + `${HINDSIGHT_MIN_API_VERSION} \u2014 the engine may have grown a native ` + `MCP tool that makes one of the five synthesized tools obsolete; ` + `re-check the retirement seam (withSynthesizedTools, ` + `hindsight-mcp-shim.ts).`
48961
+ };
48962
+ }
48963
+ function classifyHindsightShimContract(spec) {
48964
+ const results = [classifyShimContractVersion(spec)];
48965
+ const routeRows = SYNTHESIZED_ROUTE_TOOL_NAMES.map((tool) => classifySynthesizedToolRoute(spec, tool)).filter((r) => r !== null);
48966
+ if (routeRows.length === 0) {
48967
+ results.push({
48968
+ name: `${SHIM_CONTRACT_CHECK_PREFIX}: routes`,
48969
+ status: "ok",
48970
+ detail: `all ${SYNTHESIZED_ROUTE_TOOL_NAMES.length} synthesized tools' REST routes are present in /openapi.json`
48971
+ });
48972
+ } else {
48973
+ results.push(...routeRows);
48974
+ }
48975
+ return results;
48976
+ }
48977
+ async function runHindsightShimContractCheck(mcpUrl, opts = {}) {
48978
+ const origin = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
48979
+ const spec = await fetchHindsightOpenApi(origin, opts);
48980
+ if (!spec) {
48981
+ return [
48982
+ {
48983
+ name: `${SHIM_CONTRACT_CHECK_PREFIX}: routes`,
48984
+ status: "warn",
48985
+ detail: `the engine is reachable but /openapi.json did not return a usable ` + `spec \u2014 the shim contract guard is INACTIVE: neither this doctor ` + `check nor ShimContractPin's loud call-time preflight (` + `hindsight-mcp-shim.ts) can detect a route rename/removal under the ` + `five synthesized tools (deactivate_directive, reactivate_directive, ` + `search_knowledge_pages, get_knowledge_page, get_knowledge_tree) ` + `right now. Calls degrade to "unknown, proceed" and a route drop ` + `would surface as a bare failed REST call instead of a named refusal.`,
48986
+ fix: "Confirm /openapi.json is served at the engine's origin (FastAPI " + "serves it by default; check for `docs_url`/`openapi_url` disabled " + "in the engine's startup config, or a reverse proxy stripping the " + "route). If OpenAPI docs are deliberately disabled in production, " + "this warning is expected and the route contract is unverifiable " + "by design \u2014 the REST layer's own error handling remains the " + "backstop for the five synthesized tools."
48987
+ }
48988
+ ];
48989
+ }
48990
+ return classifyHindsightShimContract(spec);
48991
+ }
48992
+ var SHIM_CONTRACT_CHECK_PREFIX = "hindsight shim contract";
48993
+ var init_doctor_hindsight_shim_contract = __esm(() => {
48994
+ init_hindsight_repair();
48995
+ init_hindsight_shim_contract();
48996
+ init_hindsight_tools();
48997
+ });
48998
+
48727
48999
  // src/cli/doctor-recall-health.ts
48728
- import { closeSync as closeSync13, existsSync as existsSync60, openSync as openSync13, readSync as readSync3, statSync as statSync32 } from "node:fs";
49000
+ import { closeSync as closeSync13, existsSync as existsSync60, openSync as openSync13, readSync as readSync3, statSync as statSync33 } from "node:fs";
48729
49001
  import { join as join54 } from "node:path";
48730
49002
  function filterRecentRecallRows(rows, maxAgeMs = RECALL_HEALTH_WINDOW_MAX_AGE_MS, now = Date.now()) {
48731
49003
  if (!Number.isFinite(maxAgeMs) || maxAgeMs <= 0)
@@ -48821,7 +49093,7 @@ function readRecallLogTail(path7, windowRows = RECALL_HEALTH_WINDOW_ROWS) {
48821
49093
  let truncatedHead = false;
48822
49094
  let fd;
48823
49095
  try {
48824
- const size = statSync32(path7).size;
49096
+ const size = statSync33(path7).size;
48825
49097
  const start = Math.max(0, size - RECALL_HEALTH_MAX_TAIL_BYTES);
48826
49098
  truncatedHead = start > 0;
48827
49099
  const length = size - start;
@@ -49253,7 +49525,7 @@ var init_doctor_observation_scopes = __esm(() => {
49253
49525
 
49254
49526
  // src/hindsight-watch/install-cron.ts
49255
49527
  import { execFileSync as execFileSync21 } from "node:child_process";
49256
- import { chownSync as chownSync8, existsSync as existsSync61, mkdirSync as mkdirSync37, readFileSync as readFileSync51, renameSync as renameSync17, statSync as statSync33, writeFileSync as writeFileSync24 } from "node:fs";
49528
+ import { chownSync as chownSync8, existsSync as existsSync61, mkdirSync as mkdirSync37, readFileSync as readFileSync51, renameSync as renameSync17, statSync as statSync34, writeFileSync as writeFileSync24 } from "node:fs";
49257
49529
  import { dirname as dirname24 } from "node:path";
49258
49530
  function renderCron(opts) {
49259
49531
  return `# switchroom hindsight-watch \u2014 model-free memory watchdog.
@@ -49290,7 +49562,7 @@ function ensureLogFile(opts) {
49290
49562
  const isRoot = process.getuid?.() === 0;
49291
49563
  let alreadyOwned = false;
49292
49564
  try {
49293
- const st = statSync33(path7);
49565
+ const st = statSync34(path7);
49294
49566
  alreadyOwned = st.uid === uid && st.gid === gid;
49295
49567
  } catch {}
49296
49568
  if (isRoot && !alreadyOwned) {
@@ -49554,7 +49826,7 @@ var init_state2 = __esm(() => {
49554
49826
  });
49555
49827
 
49556
49828
  // src/cli/doctor-hindsight-watch.ts
49557
- import { existsSync as existsSync62, statSync as statSync34 } from "node:fs";
49829
+ import { existsSync as existsSync62, statSync as statSync35 } from "node:fs";
49558
49830
  function classifyWatchArmed(cronPresent, stateMtimeMs, now) {
49559
49831
  const name = "hindsight-watch armed";
49560
49832
  if (stateMtimeMs === null) {
@@ -49592,7 +49864,7 @@ function classifyWatchArmed(cronPresent, stateMtimeMs, now) {
49592
49864
  function checkHindsightWatchArmed(now = Date.now(), cronPath = CRON_PATH, statePath = defaultStatePath2()) {
49593
49865
  let mtime = null;
49594
49866
  try {
49595
- mtime = statSync34(statePath).mtimeMs;
49867
+ mtime = statSync35(statePath).mtimeMs;
49596
49868
  } catch {}
49597
49869
  return classifyWatchArmed(existsSync62(cronPath), mtime, now);
49598
49870
  }
@@ -50520,7 +50792,7 @@ import {
50520
50792
  readdirSync as readdirSync24,
50521
50793
  readFileSync as readFileSync53,
50522
50794
  realpathSync as realpathSync5,
50523
- statSync as statSync35,
50795
+ statSync as statSync36,
50524
50796
  unlinkSync as unlinkSync15
50525
50797
  } from "node:fs";
50526
50798
  import { join as join55, relative as relative3, sep as sep5 } from "node:path";
@@ -50575,7 +50847,7 @@ function guardedCopyFile(src, dest, allowedRoot) {
50575
50847
  }
50576
50848
  let realStat;
50577
50849
  try {
50578
- realStat = statSync35(real);
50850
+ realStat = statSync36(real);
50579
50851
  } catch {
50580
50852
  return { status: "missing" };
50581
50853
  }
@@ -50622,7 +50894,7 @@ function copyAgentWorkspace(agentName, liveWorkspace, repoWorkspace, skipped, lo
50622
50894
  }
50623
50895
  function dirExists(p) {
50624
50896
  try {
50625
- return statSync35(p).isDirectory();
50897
+ return statSync36(p).isDirectory();
50626
50898
  } catch {
50627
50899
  return false;
50628
50900
  }
@@ -50676,7 +50948,7 @@ function scanStagedForSecrets(repoPath, git, log) {
50676
50948
  const abs = join55(repoPath, relPath);
50677
50949
  let content;
50678
50950
  try {
50679
- const st = statSync35(abs);
50951
+ const st = statSync36(abs);
50680
50952
  if (!st.isFile() || st.size > SCAN_MAX_FILE_BYTES)
50681
50953
  continue;
50682
50954
  const buf = readFileSync53(abs);
@@ -50834,7 +51106,7 @@ import {
50834
51106
  readFileSync as readFileSync54,
50835
51107
  renameSync as renameSync19,
50836
51108
  rmSync as rmSync17,
50837
- statSync as statSync36,
51109
+ statSync as statSync37,
50838
51110
  writeFileSync as writeFileSync26
50839
51111
  } from "node:fs";
50840
51112
  import { dirname as dirname26 } from "node:path";
@@ -50896,7 +51168,7 @@ function ensureLogFile2(opts) {
50896
51168
  const isRoot = process.getuid?.() === 0;
50897
51169
  let alreadyOwned = false;
50898
51170
  try {
50899
- const st = statSync36(path7);
51171
+ const st = statSync37(path7);
50900
51172
  alreadyOwned = st.uid === uid && st.gid === gid;
50901
51173
  } catch {}
50902
51174
  if (isRoot && !alreadyOwned) {
@@ -50993,7 +51265,7 @@ var CRON_PATH2 = "/etc/cron.d/switchroom-config-sync", CRON_LOG_PATH2 = "/var/lo
50993
51265
  var init_install_cron2 = () => {};
50994
51266
 
50995
51267
  // src/cli/doctor-config-repo.ts
50996
- import { existsSync as existsSync65, readdirSync as readdirSync25, statSync as statSync37 } from "node:fs";
51268
+ import { existsSync as existsSync65, readdirSync as readdirSync25, statSync as statSync38 } from "node:fs";
50997
51269
  import { spawnSync as spawnSync9 } from "node:child_process";
50998
51270
  import { homedir as homedir27 } from "node:os";
50999
51271
  import { join as join56 } from "node:path";
@@ -51168,7 +51440,7 @@ function countUntrackedPersonalSkills(repoPath, git) {
51168
51440
  const rel = relPrefix ? `${relPrefix}/${ent}` : ent;
51169
51441
  let st;
51170
51442
  try {
51171
- st = statSync37(abs);
51443
+ st = statSync38(abs);
51172
51444
  } catch {
51173
51445
  continue;
51174
51446
  }
@@ -51188,7 +51460,7 @@ function countUntrackedPersonalSkills(repoPath, git) {
51188
51460
  const abs = join56(ps, ent);
51189
51461
  let st;
51190
51462
  try {
51191
- st = statSync37(abs);
51463
+ st = statSync38(abs);
51192
51464
  } catch {
51193
51465
  continue;
51194
51466
  }
@@ -51280,7 +51552,7 @@ function checkConfigRepo(config, now = Date.now()) {
51280
51552
  }
51281
51553
  function safeMtimeMs2(path7) {
51282
51554
  try {
51283
- return statSync37(path7).mtimeMs;
51555
+ return statSync38(path7).mtimeMs;
51284
51556
  } catch {
51285
51557
  return null;
51286
51558
  }
@@ -52282,7 +52554,7 @@ import {
52282
52554
  constants as fsConstants3,
52283
52555
  existsSync as existsSync67,
52284
52556
  realpathSync as realpathSync6,
52285
- statSync as statSync38
52557
+ statSync as statSync39
52286
52558
  } from "node:fs";
52287
52559
  import { userInfo, homedir as homedir29 } from "node:os";
52288
52560
  import { join as join59 } from "node:path";
@@ -52300,7 +52572,7 @@ function defaultStatVault(path7) {
52300
52572
  let uid = -1;
52301
52573
  let mode = 0;
52302
52574
  try {
52303
- const s = statSync38(real);
52575
+ const s = statSync39(real);
52304
52576
  uid = s.uid;
52305
52577
  mode = s.mode & 511;
52306
52578
  } catch {
@@ -63851,7 +64123,7 @@ import {
63851
64123
  mkdirSync as mkdirSync43,
63852
64124
  readFileSync as readFileSync63,
63853
64125
  renameSync as renameSync21,
63854
- statSync as statSync39,
64126
+ statSync as statSync40,
63855
64127
  writeFileSync as writeFileSync30
63856
64128
  } from "node:fs";
63857
64129
  import { homedir as homedir33 } from "node:os";
@@ -63869,7 +64141,7 @@ function resolveIo(io) {
63869
64141
  }),
63870
64142
  statUid: io.statUid ?? ((p) => {
63871
64143
  try {
63872
- return statSync39(p).uid;
64144
+ return statSync40(p).uid;
63873
64145
  } catch {
63874
64146
  return;
63875
64147
  }
@@ -63879,7 +64151,7 @@ function resolveIo(io) {
63879
64151
  chown: io.chown ?? ((p, uid, gid) => chownSync10(p, uid, gid)),
63880
64152
  statOwner: io.statOwner ?? ((p) => {
63881
64153
  try {
63882
- const st = statSync39(p);
64154
+ const st = statSync40(p);
63883
64155
  return { uid: st.uid, gid: st.gid };
63884
64156
  } catch {
63885
64157
  return;
@@ -65179,7 +65451,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
65179
65451
  var init_doctor_credentials_migration = () => {};
65180
65452
 
65181
65453
  // src/cli/doctor-agent-dotfile-ownership.ts
65182
- import { existsSync as existsSync72, lstatSync as lstatSync11, statSync as statSync40 } from "node:fs";
65454
+ import { existsSync as existsSync72, lstatSync as lstatSync11, statSync as statSync41 } from "node:fs";
65183
65455
  import { join as join74 } from "node:path";
65184
65456
  function agentDotfileCandidates(agentDir) {
65185
65457
  return [
@@ -65196,7 +65468,7 @@ function runAgentDotfileOwnershipChecks(config, deps = {}) {
65196
65468
  if (agents.length === 0)
65197
65469
  return results;
65198
65470
  const exists = deps.existsSync ?? existsSync72;
65199
- const scanFs = deps.scanFs ?? { lstatSync: lstatSync11, statSync: statSync40 };
65471
+ const scanFs = deps.scanFs ?? { lstatSync: lstatSync11, statSync: statSync41 };
65200
65472
  let agentsDir = deps.agentsDir;
65201
65473
  if (agentsDir === undefined) {
65202
65474
  try {
@@ -65803,7 +66075,7 @@ var init_doctor_agent_smoke = __esm(() => {
65803
66075
 
65804
66076
  // src/cli/doctor-vault-broker-durability.ts
65805
66077
  import { execFileSync as execFileSync25 } from "node:child_process";
65806
- import { existsSync as existsSync74, statSync as statSync41, readFileSync as readFileSync64 } from "node:fs";
66078
+ import { existsSync as existsSync74, statSync as statSync42, readFileSync as readFileSync64 } from "node:fs";
65807
66079
  import { homedir as homedir40 } from "node:os";
65808
66080
  import { join as join78 } from "node:path";
65809
66081
  import { createRequire as createRequire2 } from "node:module";
@@ -65831,7 +66103,7 @@ function defaultStatHost(p) {
65831
66103
  if (!existsSync74(p))
65832
66104
  return null;
65833
66105
  try {
65834
- const s = statSync41(p, { bigint: true });
66106
+ const s = statSync42(p, { bigint: true });
65835
66107
  return { ino: s.ino, size: Number(s.size) };
65836
66108
  } catch {
65837
66109
  return null;
@@ -66169,7 +66441,7 @@ function probeOrphanVaultTokens(config, opts) {
66169
66441
  }
66170
66442
  function defaultTokenMtimeMs(agent, home2) {
66171
66443
  try {
66172
- return statSync41(join78(home2, ".switchroom", "agents", agent, ".vault-token")).mtimeMs;
66444
+ return statSync42(join78(home2, ".switchroom", "agents", agent, ".vault-token")).mtimeMs;
66173
66445
  } catch {
66174
66446
  return null;
66175
66447
  }
@@ -66217,7 +66489,7 @@ function probeAutoUnlockBlob(home2) {
66217
66489
  fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
66218
66490
  };
66219
66491
  }
66220
- const sz = statSync41(blobPath).size;
66492
+ const sz = statSync42(blobPath).size;
66221
66493
  if (sz === 0) {
66222
66494
  return {
66223
66495
  name: "vault-broker: auto-unlock blob",
@@ -66383,7 +66655,7 @@ var init_doctor_timezone = __esm(() => {
66383
66655
  });
66384
66656
 
66385
66657
  // src/cli/doctor-disk.ts
66386
- import { closeSync as closeSync14, fstatSync as fstatSync4, openSync as openSync14, readSync as readSync4, statfsSync, statSync as statSync42 } from "node:fs";
66658
+ import { closeSync as closeSync14, fstatSync as fstatSync4, openSync as openSync14, readSync as readSync4, statfsSync, statSync as statSync43 } from "node:fs";
66387
66659
  import { dirname as dirname31 } from "node:path";
66388
66660
  function usageFromStatfs(s) {
66389
66661
  const bsize = s.bsize > 0 ? s.bsize : 0;
@@ -66406,7 +66678,7 @@ function fmtBytes(n) {
66406
66678
  }
66407
66679
  function nearestExistingPath(path7, exists = (p) => {
66408
66680
  try {
66409
- statSync42(p);
66681
+ statSync43(p);
66410
66682
  return true;
66411
66683
  } catch {
66412
66684
  return false;
@@ -66854,7 +67126,7 @@ import {
66854
67126
  mkdirSync as mkdirSync44,
66855
67127
  readFileSync as readFileSync67,
66856
67128
  readdirSync as readdirSync28,
66857
- statSync as statSync43
67129
+ statSync as statSync44
66858
67130
  } from "node:fs";
66859
67131
  import { dirname as dirname33, join as join81, resolve as resolve41 } from "node:path";
66860
67132
  import { createPublicKey, createPrivateKey } from "node:crypto";
@@ -66881,7 +67153,7 @@ function findInNvm(bin) {
66881
67153
  for (const v of versions) {
66882
67154
  const candidate = join81(nvmRoot, v, "bin", bin);
66883
67155
  try {
66884
- const s = statSync43(candidate);
67156
+ const s = statSync44(candidate);
66885
67157
  if (s.isFile() || s.isSymbolicLink()) {
66886
67158
  return candidate;
66887
67159
  }
@@ -67541,7 +67813,7 @@ function checkHostCapabilitiesReadable(config, read = readHostCapabilities()) {
67541
67813
  const proven = voice?.gpuPresent === true && voice?.containerToolkit === true;
67542
67814
  let mode;
67543
67815
  try {
67544
- mode = statSync43(read.path).mode & 511;
67816
+ mode = statSync44(read.path).mode & 511;
67545
67817
  } catch {
67546
67818
  mode = undefined;
67547
67819
  }
@@ -67827,6 +68099,7 @@ async function checkHindsight(config) {
67827
68099
  const versionRow = await checkHindsightVersion(url);
67828
68100
  if (versionRow)
67829
68101
  results.push(versionRow);
68102
+ results.push(...await runHindsightShimContractCheck(url));
67830
68103
  results.push(checkHindsightConsumer(config));
67831
68104
  results.push(checkHindsightEnvKeysAreManaged(config));
67832
68105
  results.push(...checkHindsightContainerHealth());
@@ -69297,6 +69570,7 @@ var init_doctor = __esm(() => {
69297
69570
  init_hindsight_perf_defaults();
69298
69571
  init_memory_key_drift();
69299
69572
  init_doctor_memory();
69573
+ init_doctor_hindsight_shim_contract();
69300
69574
  init_doctor_recall_health();
69301
69575
  init_doctor_hnsw_index();
69302
69576
  init_doctor_observation_scopes();
@@ -69374,7 +69648,7 @@ __export(exports_apply, {
69374
69648
  DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH,
69375
69649
  COMPOSE_PROJECT: () => COMPOSE_PROJECT2
69376
69650
  });
69377
- import { accessSync as accessSync3, chmodSync as chmodSync14, chownSync as chownSync11, constants as fsConstants5, existsSync as existsSync77, mkdirSync as mkdirSync45, readFileSync as readFileSync68, readdirSync as readdirSync29, renameSync as renameSync22, statSync as statSync44, writeFileSync as writeFileSync31 } from "node:fs";
69651
+ import { accessSync as accessSync3, chmodSync as chmodSync14, chownSync as chownSync11, constants as fsConstants5, existsSync as existsSync77, mkdirSync as mkdirSync45, readFileSync as readFileSync68, readdirSync as readdirSync29, renameSync as renameSync22, statSync as statSync45, writeFileSync as writeFileSync31 } from "node:fs";
69378
69652
  import { mkdir as mkdir2 } from "node:fs/promises";
69379
69653
  import { spawnSync as childSpawnSync } from "node:child_process";
69380
69654
  import readline from "node:readline";
@@ -69679,7 +69953,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
69679
69953
  try {
69680
69954
  let cfgMode = 420;
69681
69955
  try {
69682
- cfgMode = statSync44(switchroomConfigPath).mode & 511;
69956
+ cfgMode = statSync45(switchroomConfigPath).mode & 511;
69683
69957
  } catch {}
69684
69958
  writeConfigFileSync(switchroomConfigPath, configText, cfgMode);
69685
69959
  writeOut(source_default.gray(` ~ litellm: granted read-ACL on per-agent keys (updated ${switchroomConfigPath})
@@ -70291,26 +70565,6 @@ async function probeVaultProvisioning(config, agentNames, home2) {
70291
70565
  needsHindsight
70292
70566
  };
70293
70567
  }
70294
- function parseComposeServiceNames(compose) {
70295
- const lines = compose.split(`
70296
- `);
70297
- const names = [];
70298
- let inServices = false;
70299
- for (const line of lines) {
70300
- if (/^services:\s*$/.test(line)) {
70301
- inServices = true;
70302
- continue;
70303
- }
70304
- if (inServices && /^\S/.test(line))
70305
- break;
70306
- if (!inServices)
70307
- continue;
70308
- const m = /^ {2}([A-Za-z0-9_.-]+):\s*$/.exec(line);
70309
- if (m)
70310
- names.push(m[1]);
70311
- }
70312
- return names;
70313
- }
70314
70568
  async function runApplyDryRun(config, options, deps = {}, switchroomConfigPath) {
70315
70569
  const writeOut = deps.writeOut ?? ((s) => process.stdout.write(s));
70316
70570
  const writeErr = deps.writeErr ?? ((s) => process.stderr.write(s));
@@ -70398,6 +70652,9 @@ Dry-run: validating switchroom config (no changes will be written)...
70398
70652
  if (removedServices.length > 0) {
70399
70653
  info.push(`compose: services removed: ${removedServices.join(", ")}.`);
70400
70654
  }
70655
+ if (computed.voiceSidecarDrop) {
70656
+ warnings.push(`compose: ${computed.voiceSidecarDrop.message}`);
70657
+ }
70401
70658
  if (previousImageTag !== null && !composeChanged) {
70402
70659
  info.push(`compose: no change (would be byte-identical to the current file).`);
70403
70660
  }
@@ -70708,7 +70965,7 @@ var init_apply = __esm(() => {
70708
70965
  });
70709
70966
 
70710
70967
  // src/host-control/audit-reader.ts
70711
- import { closeSync as closeSync15, existsSync as existsSync81, openSync as openSync15, readSync as readSync5, statSync as statSync48 } from "node:fs";
70968
+ import { closeSync as closeSync15, existsSync as existsSync81, openSync as openSync15, readSync as readSync5, statSync as statSync49 } from "node:fs";
70712
70969
  import { homedir as homedir43 } from "node:os";
70713
70970
  import { join as join87 } from "node:path";
70714
70971
  function defaultAuditLogPath2(home2 = homedir43()) {
@@ -70735,7 +70992,7 @@ function tailBytes(path7, n, strict = false) {
70735
70992
  let fd;
70736
70993
  let size;
70737
70994
  try {
70738
- size = statSync48(path7).size;
70995
+ size = statSync49(path7).size;
70739
70996
  fd = openSync15(path7, "r");
70740
70997
  } catch (err) {
70741
70998
  if (strict && err.code !== "ENOENT")
@@ -84273,7 +84530,12 @@ var init_server3 = __esm(() => {
84273
84530
  required: ["cron_expr", "prompt"],
84274
84531
  properties: {
84275
84532
  cron_expr: { type: "string" },
84276
- prompt: { type: "string", minLength: 1, maxLength: 4000 },
84533
+ prompt: {
84534
+ type: "string",
84535
+ minLength: 1,
84536
+ maxLength: 4000,
84537
+ description: "The inbound turn YOU receive when this cron fires \u2014 not a message " + "sent to the user. Phrase it from your future-self's perspective " + `("Time for the daily digest \u2014 pull yesterday's GitHub activity and ` + 'DM the summary to chat 12345"), not as a request to you ("please ' + 'send the digest"). Include any ids/context that future turn will ' + "need, since a Tier-1 fire has no conversation context."
84538
+ },
84277
84539
  secrets: { type: "array", items: { type: "string" } },
84278
84540
  name: { type: "string", pattern: "^[a-z0-9-]{1,40}$" },
84279
84541
  model: {
@@ -85098,6 +85360,9 @@ function classifyOrphanedDbTick(lines, alarmIdx) {
85098
85360
  }
85099
85361
  return verdict ?? "unrecovered";
85100
85362
  }
85363
+ function isFloodRejection(line) {
85364
+ return FLOOD_REJECTION_RE.test(line.split(" desc=")[0] ?? line);
85365
+ }
85101
85366
  function lineInstantMs(line) {
85102
85367
  const iso = extractTs(line);
85103
85368
  if (iso === null)
@@ -85116,7 +85381,7 @@ function classifyReplyDeliveryEpisode(lines, failIdx) {
85116
85381
  const failMs = lineInstantMs(failLine);
85117
85382
  if (failMs === null)
85118
85383
  return "unrecovered";
85119
- let ladderEvidence = FLOOD_REJECTION_RE.test(failLine);
85384
+ let ladderEvidence = isFloodRejection(failLine);
85120
85385
  for (let j = failIdx + 1;j < lines.length; j++) {
85121
85386
  const line = lines[j];
85122
85387
  if (!line)
@@ -85124,7 +85389,9 @@ function classifyReplyDeliveryEpisode(lines, failIdx) {
85124
85389
  const ms = lineInstantMs(line);
85125
85390
  if (ms !== null && ms - failMs > REPLY_DELIVERY_RECOVERY_WINDOW_MS)
85126
85391
  break;
85127
- if (RECOVERY_LADDER_MARKER_RE.test(line))
85392
+ if (GATEWAY_FLOOD_LADDER_RE.test(line))
85393
+ ladderEvidence = true;
85394
+ else if (FUSE_SEND_DEFERRAL_RE.exec(line)?.[1] === chat)
85128
85395
  ladderEvidence = true;
85129
85396
  if (!TG_POST_RICH_LINE_RE.test(line))
85130
85397
  continue;
@@ -85136,7 +85403,7 @@ function classifyReplyDeliveryEpisode(lines, failIdx) {
85136
85403
  if (ms === null)
85137
85404
  continue;
85138
85405
  if (TG_POST_STATUS_RE.exec(line)?.[1] !== "ok") {
85139
- if (FLOOD_REJECTION_RE.test(line))
85406
+ if (isFloodRejection(line))
85140
85407
  ladderEvidence = true;
85141
85408
  continue;
85142
85409
  }
@@ -85319,7 +85586,7 @@ function scanAgent(agent, turnsText, gatewayText, opts = {}) {
85319
85586
  escalate
85320
85587
  };
85321
85588
  }
85322
- var HANG_MS = 360000, HANG_MAXTOOLS = 2, SILENT_NOOP_FLOOR_TS = 1783900800, GATEWAY_SIGNATURES, GATEWAY_SIGNAL_MEMBERS, GATEWAY_SIGNAL_NAMES, ORPHANED_DB_RECOVERED_RE, ORPHANED_DB_SWEEP_LINE_RE, ORPHANED_DB_ALARM_TARGET_RE, ORPHANED_DB_ALARM_COUNT_RE, ORPHANED_DB_LANE_VETO_RE, TG_POST_RICH_LINE_RE, TG_POST_STATUS_RE, TG_POST_CHAT_RE, TG_POST_THREAD_RE, RECOVERY_LADDER_MARKER_RE, FLOOD_REJECTION_RE, REPLY_DELIVERY_IMMEDIATE_RESEND_MS = 2000, REPLY_DELIVERY_RECOVERY_WINDOW_MS, ISO_TS_RE;
85589
+ var HANG_MS = 360000, HANG_MAXTOOLS = 2, SILENT_NOOP_FLOOR_TS = 1783900800, GATEWAY_SIGNATURES, GATEWAY_SIGNAL_MEMBERS, GATEWAY_SIGNAL_NAMES, ORPHANED_DB_RECOVERED_RE, ORPHANED_DB_SWEEP_LINE_RE, ORPHANED_DB_ALARM_TARGET_RE, ORPHANED_DB_ALARM_COUNT_RE, ORPHANED_DB_LANE_VETO_RE, TG_POST_RICH_LINE_RE, TG_POST_STATUS_RE, TG_POST_CHAT_RE, TG_POST_THREAD_RE, GATEWAY_FLOOD_LADDER_RE, FUSE_SEND_DEFERRAL_RE, FLOOD_REJECTION_RE, REPLY_DELIVERY_IMMEDIATE_RESEND_MS = 2000, REPLY_DELIVERY_RECOVERY_WINDOW_MS, ISO_TS_RE;
85323
85590
  var init_detect = __esm(() => {
85324
85591
  init_retry_api_call();
85325
85592
  GATEWAY_SIGNATURES = {
@@ -85346,8 +85613,9 @@ var init_detect = __esm(() => {
85346
85613
  TG_POST_STATUS_RE = /\bstatus=(ok|benign|retry|err)(?![a-z])/;
85347
85614
  TG_POST_CHAT_RE = /\bchat=(-?\d+)\b/;
85348
85615
  TG_POST_THREAD_RE = /\bthread=(\S+)/;
85349
- RECOVERY_LADDER_MARKER_RE = /429 rate limited|edit-flood-fuse deferred|outbox-sweep: deferred|queued card send failed/;
85350
- FLOOD_REJECTION_RE = /\berr=telegram_429\b|Too Many Requests|retry after \d/;
85616
+ GATEWAY_FLOOD_LADDER_RE = /429 rate limited|outbox-sweep: deferred/;
85617
+ FUSE_SEND_DEFERRAL_RE = /\bedit-flood-fuse deferred method=sendRichMessage\b[^\n]*\bkey=(?:cs|cer|ce|lr|t):(-?\d+)\b/;
85618
+ FLOOD_REJECTION_RE = /\berr=telegram_429\b|\bcode=429\b/;
85351
85619
  REPLY_DELIVERY_RECOVERY_WINDOW_MS = DEFAULT_MAX_FLOOD_SLEEP_MS;
85352
85620
  ISO_TS_RE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?/;
85353
85621
  });
@@ -93400,7 +93668,7 @@ import { spawn as spawn4 } from "node:child_process";
93400
93668
  // src/vault/broker/server.ts
93401
93669
  init_compose();
93402
93670
  import * as net3 from "node:net";
93403
- import { mkdirSync as mkdirSync29, chmodSync as chmodSync9, chownSync as chownSync6, existsSync as existsSync47, readFileSync as readFileSync40, readdirSync as readdirSync20, statSync as statSync29, unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "node:fs";
93671
+ import { mkdirSync as mkdirSync29, chmodSync as chmodSync9, chownSync as chownSync6, existsSync as existsSync47, readFileSync as readFileSync40, readdirSync as readdirSync20, statSync as statSync30, unlinkSync as unlinkSync12, writeFileSync as writeFileSync18 } from "node:fs";
93404
93672
 
93405
93673
  // src/vault/broker/write-token-file.ts
93406
93674
  init_agent_uid();
@@ -93485,6 +93753,66 @@ import { dirname as dirname16, resolve as resolve32, basename as basename8 } fro
93485
93753
  import * as os4 from "node:os";
93486
93754
  import * as path5 from "node:path";
93487
93755
 
93756
+ // src/vault/broker/vault-refresh.ts
93757
+ init_vault();
93758
+ import { statSync as statSync29 } from "node:fs";
93759
+ function sameVaultStamp(a, b) {
93760
+ return a.mtimeNs === b.mtimeNs && a.size === b.size && a.ino === b.ino;
93761
+ }
93762
+ function readVaultStamp(vaultPath) {
93763
+ try {
93764
+ const st = statSync29(vaultPath, { bigint: true });
93765
+ return { mtimeNs: st.mtimeNs, size: st.size, ino: st.ino };
93766
+ } catch {
93767
+ return null;
93768
+ }
93769
+ }
93770
+ function zeroSecrets(secrets) {
93771
+ for (const [, entry] of Object.entries(secrets)) {
93772
+ try {
93773
+ if (entry.kind === "string" || entry.kind === "binary") {
93774
+ entry.value = "";
93775
+ }
93776
+ } catch {}
93777
+ }
93778
+ }
93779
+ function refreshVaultIfChanged(state, opts) {
93780
+ const { vaultPath, force = false } = opts;
93781
+ const open = opts.open ?? openVault;
93782
+ const warn = opts.warn ?? ((m) => {
93783
+ process.stderr.write(m);
93784
+ });
93785
+ const secrets = state.secrets;
93786
+ const passphrase = state.passphrase;
93787
+ if (secrets === null || passphrase === null)
93788
+ return state;
93789
+ const known = state.loadedStamp;
93790
+ if (known === null)
93791
+ return state;
93792
+ const current = readVaultStamp(vaultPath);
93793
+ if (current === null)
93794
+ return state;
93795
+ if (!force && sameVaultStamp(current, known))
93796
+ return state;
93797
+ if (!force && state.failedStamp !== null && sameVaultStamp(state.failedStamp, current)) {
93798
+ return state;
93799
+ }
93800
+ opts.beforeOpen?.(vaultPath);
93801
+ let next;
93802
+ try {
93803
+ next = open(passphrase, vaultPath);
93804
+ } catch (err) {
93805
+ const alreadyWarned = state.failedStamp !== null && sameVaultStamp(state.failedStamp, current);
93806
+ if (!alreadyWarned) {
93807
+ warn(`[vault-broker] WARNING: ${vaultPath} changed on disk but ` + `could not be re-opened (${err?.message ?? "unknown error"}) \u2014 ` + `continuing to serve the previously loaded secrets; will retry when ` + `the file changes again
93808
+ `);
93809
+ }
93810
+ return { ...state, failedStamp: current };
93811
+ }
93812
+ zeroSecrets(secrets);
93813
+ return { secrets: next, passphrase, loadedStamp: current, failedStamp: null };
93814
+ }
93815
+
93488
93816
  // src/vault/grants.ts
93489
93817
  import { createHash as createHash10, randomBytes as randomBytes9 } from "node:crypto";
93490
93818
 
@@ -95883,6 +96211,8 @@ class VaultBroker {
95883
96211
  testOpts;
95884
96212
  secrets = null;
95885
96213
  passphrase = null;
96214
+ vaultStamp = null;
96215
+ failedVaultStamp = null;
95886
96216
  config = null;
95887
96217
  startedAt = Date.now();
95888
96218
  server = null;
@@ -95977,13 +96307,53 @@ class VaultBroker {
95977
96307
  }
95978
96308
  unlockFromPassphrase(passphrase) {
95979
96309
  detectVaultLayoutDrift(this.vaultPath);
96310
+ const stamp = this._readVaultStamp();
95980
96311
  const secrets = openVault(passphrase, this.vaultPath);
95981
96312
  this.secrets = secrets;
95982
96313
  this.passphrase = passphrase;
96314
+ this.vaultStamp = stamp;
96315
+ this.failedVaultStamp = null;
95983
96316
  this._setReadinessSentinel(true);
95984
96317
  }
96318
+ _readVaultStamp() {
96319
+ return readVaultStamp(this.vaultPath);
96320
+ }
96321
+ _reloadSecretsIfVaultChanged(force = false) {
96322
+ let next;
96323
+ try {
96324
+ next = refreshVaultIfChanged({
96325
+ secrets: this.secrets,
96326
+ passphrase: this.passphrase,
96327
+ loadedStamp: this.vaultStamp,
96328
+ failedStamp: this.failedVaultStamp
96329
+ }, { vaultPath: this.vaultPath, force, beforeOpen: detectVaultLayoutDrift });
96330
+ } catch (err) {
96331
+ this._failClosedOnVaultDrift(err);
96332
+ return;
96333
+ }
96334
+ this.secrets = next.secrets;
96335
+ this.vaultStamp = next.loadedStamp;
96336
+ this.failedVaultStamp = next.failedStamp;
96337
+ }
96338
+ _failClosedOnVaultDrift(err) {
96339
+ const detail = err instanceof Error ? err.message : String(err);
96340
+ process.stderr.write(`[vault-broker] FATAL: ${detail}
96341
+ [vault-broker] Refusing to serve secrets from a vault whose on-disk ` + `layout has diverged \u2014 locking. Fix the layout and restart the broker ` + `(\`switchroom apply\`); see docs/operators/state-e-recovery.md.
96342
+ `);
96343
+ try {
96344
+ this.auditLogger.write({
96345
+ ts: new Date().toISOString(),
96346
+ op: "lock",
96347
+ caller: `pid:${process.pid}`,
96348
+ pid: process.pid,
96349
+ result: "error:vault-layout-drift"
96350
+ });
96351
+ } catch {}
96352
+ this.lock();
96353
+ }
95985
96354
  reload(config) {
95986
96355
  this.config = config;
96356
+ this._reloadSecretsIfVaultChanged(true);
95987
96357
  }
95988
96358
  _setReadinessSentinel(ready) {
95989
96359
  const p = process.env.SWITCHROOM_VAULT_BROKER_READY_PATH;
@@ -95999,15 +96369,11 @@ class VaultBroker {
95999
96369
  }
96000
96370
  lock() {
96001
96371
  if (this.secrets !== null) {
96002
- for (const [, entry] of Object.entries(this.secrets)) {
96003
- try {
96004
- if (entry.kind === "string" || entry.kind === "binary") {
96005
- entry.value = "";
96006
- }
96007
- } catch {}
96008
- }
96372
+ zeroSecrets(this.secrets);
96009
96373
  this.secrets = null;
96010
96374
  }
96375
+ this.vaultStamp = null;
96376
+ this.failedVaultStamp = null;
96011
96377
  this.passphrase = null;
96012
96378
  this._setReadinessSentinel(false);
96013
96379
  }
@@ -96327,6 +96693,7 @@ class VaultBroker {
96327
96693
  socket.write(encodeResponse(errorResponse("DENIED", "preflight_access is operator-only")));
96328
96694
  return;
96329
96695
  }
96696
+ this._reloadSecretsIfVaultChanged();
96330
96697
  if (this.secrets === null) {
96331
96698
  socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
96332
96699
  return;
@@ -96358,6 +96725,7 @@ class VaultBroker {
96358
96725
  return;
96359
96726
  }
96360
96727
  if (req.op === "list") {
96728
+ this._reloadSecretsIfVaultChanged();
96361
96729
  if (this.secrets === null) {
96362
96730
  socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
96363
96731
  return;
@@ -96433,6 +96801,7 @@ class VaultBroker {
96433
96801
  return;
96434
96802
  }
96435
96803
  if (req.op === "get") {
96804
+ this._reloadSecretsIfVaultChanged();
96436
96805
  if (this.secrets === null) {
96437
96806
  socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
96438
96807
  return;
@@ -96620,10 +96989,24 @@ class VaultBroker {
96620
96989
  return;
96621
96990
  }
96622
96991
  if (req.op === "put") {
96992
+ this._reloadSecretsIfVaultChanged();
96623
96993
  if (this.secrets === null || this.passphrase === null) {
96624
96994
  socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
96625
96995
  return;
96626
96996
  }
96997
+ if (this.failedVaultStamp !== null) {
96998
+ writeAudit({
96999
+ ts: new Date().toISOString(),
97000
+ op: "put",
97001
+ key: req.key,
97002
+ caller: auditCaller,
97003
+ pid: auditPid,
97004
+ cgroup: auditCgroup,
97005
+ result: "denied:vault-file-unreadable"
97006
+ });
97007
+ socket.write(encodeResponse(errorResponse("INTERNAL", "put refused: the vault file on disk changed and could not be decrypted with the passphrase this broker holds. Writing now would overwrite it with the broker's older in-memory state. Restore the vault file the broker was unlocked with, or restart the broker and unlock it with the current passphrase (reads keep serving the previously loaded secrets meanwhile).")));
97008
+ return;
97009
+ }
96627
97010
  let passphraseAttested = false;
96628
97011
  const requestedPostureAttest = req.attest_via_posture === true;
96629
97012
  if (requestedPostureAttest && req.passphrase !== undefined && req.passphrase !== "") {
@@ -96867,6 +97250,8 @@ class VaultBroker {
96867
97250
  return;
96868
97251
  }
96869
97252
  this.chownVaultToOperator();
97253
+ this.vaultStamp = this._readVaultStamp();
97254
+ this.failedVaultStamp = null;
96870
97255
  this.auditLogger.write({
96871
97256
  ts: new Date().toISOString(),
96872
97257
  op: "put",
@@ -97442,7 +97827,7 @@ class VaultBroker {
97442
97827
  if (!existsSync47(filePath))
97443
97828
  return false;
97444
97829
  try {
97445
- if (statSync29(filePath).size === 0)
97830
+ if (statSync30(filePath).size === 0)
97446
97831
  return false;
97447
97832
  } catch {
97448
97833
  return false;
@@ -97650,7 +98035,7 @@ init_client();
97650
98035
  init_auto_unlock();
97651
98036
  var import_yaml11 = __toESM(require_dist(), 1);
97652
98037
  import { spawnSync as spawnSync5 } from "node:child_process";
97653
- import { readFileSync as readFileSync41, statSync as statSync30 } from "node:fs";
98038
+ import { readFileSync as readFileSync41, statSync as statSync31 } from "node:fs";
97654
98039
  import { homedir as homedir21 } from "node:os";
97655
98040
  import { join as join44 } from "node:path";
97656
98041
 
@@ -97686,7 +98071,7 @@ function setVaultBrokerAutoUnlock(configPath, value) {
97686
98071
  doc.setIn(["vault", "broker", "autoUnlock"], value);
97687
98072
  let mode = 420;
97688
98073
  try {
97689
- mode = statSync30(configPath).mode & 511;
98074
+ mode = statSync31(configPath).mode & 511;
97690
98075
  } catch {}
97691
98076
  writeConfigFileSync(configPath, doc.toString(), mode);
97692
98077
  }
@@ -98572,7 +98957,7 @@ import {
98572
98957
  readdirSync as readdirSync21,
98573
98958
  readFileSync as readFileSync44,
98574
98959
  renameSync as renameSync15,
98575
- statSync as statSync31,
98960
+ statSync as statSync32,
98576
98961
  symlinkSync as symlinkSync4,
98577
98962
  unlinkSync as unlinkSync14,
98578
98963
  writeSync as writeSync7
@@ -98724,7 +99109,7 @@ function backupVault(opts) {
98724
99109
  throw new Error(`vault backup refused: '${fullPath}' already exists ` + `(sub-second collision with another backup). Retry in 1 second, ` + `or check for a concurrent backup process.`);
98725
99110
  }
98726
99111
  renameSync15(tmpPath, fullPath);
98727
- const stat = statSync31(fullPath);
99112
+ const stat = statSync32(fullPath);
98728
99113
  const sha256 = sha256OfFile(fullPath);
98729
99114
  const row = {
98730
99115
  ts: now.toISOString(),
@@ -107320,7 +107705,7 @@ function registerBuzzCommand(program3) {
107320
107705
  // src/cli/telegram.ts
107321
107706
  init_source();
107322
107707
  init_atomic();
107323
- import { existsSync as existsSync79, readFileSync as readFileSync70, statSync as statSync45 } from "node:fs";
107708
+ import { existsSync as existsSync79, readFileSync as readFileSync70, statSync as statSync46 } from "node:fs";
107324
107709
  import { join as join84 } from "node:path";
107325
107710
 
107326
107711
  // src/web/webhook-dispatch.ts
@@ -107785,7 +108170,7 @@ function emitDiffOrWrite(path7, before, after, dryRun) {
107785
108170
  }
107786
108171
  let mode = 420;
107787
108172
  try {
107788
- mode = statSync45(path7).mode & 511;
108173
+ mode = statSync46(path7).mode & 511;
107789
108174
  } catch {}
107790
108175
  writeConfigFileSync(path7, after, mode);
107791
108176
  }
@@ -108007,7 +108392,7 @@ init_source();
108007
108392
  init_helpers();
108008
108393
  init_atomic();
108009
108394
  init_telegram_yaml();
108010
- import { readFileSync as readFileSync71, statSync as statSync46 } from "node:fs";
108395
+ import { readFileSync as readFileSync71, statSync as statSync47 } from "node:fs";
108011
108396
 
108012
108397
  // src/linear/oauth-refresh.ts
108013
108398
  var LINEAR_TOKEN_ENDPOINT = "https://api.linear.app/oauth/token";
@@ -108117,7 +108502,7 @@ async function performLinearRefresh(io) {
108117
108502
  function writeConfigAtomic(path7, after) {
108118
108503
  let mode = 420;
108119
108504
  try {
108120
- mode = statSync46(path7).mode & 511;
108505
+ mode = statSync47(path7).mode & 511;
108121
108506
  } catch {}
108122
108507
  writeConfigFileSync(path7, after, mode);
108123
108508
  }
@@ -108994,7 +109379,7 @@ init_loader();
108994
109379
  init_hindsight();
108995
109380
  var import_yaml16 = __toESM(require_dist(), 1);
108996
109381
  import { spawn as spawn5 } from "node:child_process";
108997
- import { existsSync as existsSync80, readFileSync as readFileSync72, statSync as statSync47 } from "node:fs";
109382
+ import { existsSync as existsSync80, readFileSync as readFileSync72, statSync as statSync48 } from "node:fs";
108998
109383
  import { join as join85 } from "node:path";
108999
109384
  function persistMemoryConfigUrl(configPath, provider, url) {
109000
109385
  if (!existsSync80(configPath))
@@ -109017,7 +109402,7 @@ function persistMemoryConfigUrl(configPath, provider, url) {
109017
109402
  }
109018
109403
  let mode = 420;
109019
109404
  try {
109020
- mode = statSync47(configPath).mode & 511;
109405
+ mode = statSync48(configPath).mode & 511;
109021
109406
  } catch {}
109022
109407
  writeConfigFileSync(configPath, doc.toString(), mode);
109023
109408
  return true;
@@ -110083,7 +110468,7 @@ init_lifecycle();
110083
110468
  init_manager();
110084
110469
  init_hindsight2();
110085
110470
  import { spawnSync as spawnSync17 } from "node:child_process";
110086
- import { existsSync as existsSync84, readFileSync as readFileSync75, statSync as statSync50 } from "node:fs";
110471
+ import { existsSync as existsSync84, readFileSync as readFileSync75, statSync as statSync51 } from "node:fs";
110087
110472
  import { resolve as resolve44, join as join92 } from "node:path";
110088
110473
 
110089
110474
  // src/web/memory-remediation.ts
@@ -110686,7 +111071,7 @@ import {
110686
111071
  mkdirSync as mkdirSync47,
110687
111072
  openSync as openSync16,
110688
111073
  readdirSync as readdirSync31,
110689
- statSync as statSync49,
111074
+ statSync as statSync50,
110690
111075
  writeFileSync as writeFileSync33
110691
111076
  } from "node:fs";
110692
111077
  import { dirname as dirname35, join as join90, relative as relative4, sep as sep6 } from "node:path";
@@ -110715,7 +111100,7 @@ function resolveStateOwner(dir) {
110715
111100
  return cached;
110716
111101
  let owner = null;
110717
111102
  try {
110718
- const st = statSync49(dir);
111103
+ const st = statSync50(dir);
110719
111104
  owner = st.uid === 0 && st.gid === 0 ? null : { uid: st.uid, gid: st.gid };
110720
111105
  } catch {
110721
111106
  owner = null;
@@ -111088,7 +111473,7 @@ function readLastTurnAt(agentsDir, name) {
111088
111473
  function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now2 = Date.now()) {
111089
111474
  try {
111090
111475
  const f = resolve44(agentsDir, name, "telegram", ".bridge-alive");
111091
- return now2 - statSync50(f).mtimeMs <= maxAgeMs;
111476
+ return now2 - statSync51(f).mtimeMs <= maxAgeMs;
111092
111477
  } catch {
111093
111478
  return false;
111094
111479
  }
@@ -114605,10 +114990,10 @@ init_helpers();
114605
114990
  init_loader();
114606
114991
 
114607
114992
  // src/web/startup-guard.ts
114608
- import { existsSync as existsSync89, readFileSync as readFileSync81, writeFileSync as writeFileSync35, mkdirSync as mkdirSync50, statSync as statSync51, unlinkSync as unlinkSync18 } from "node:fs";
114993
+ import { existsSync as existsSync89, readFileSync as readFileSync81, writeFileSync as writeFileSync35, mkdirSync as mkdirSync50, statSync as statSync52, unlinkSync as unlinkSync18 } from "node:fs";
114609
114994
  import { dirname as dirname37 } from "node:path";
114610
114995
  function detectConfigMountFault(configPath, deps = {}) {
114611
- const stat = deps.stat ?? ((p) => statSync51(p));
114996
+ const stat = deps.stat ?? ((p) => statSync52(p));
114612
114997
  let st;
114613
114998
  try {
114614
114999
  st = stat(configPath);
@@ -114749,7 +115134,7 @@ init_embedded_examples();
114749
115134
  init_atomic();
114750
115135
  init_loader();
114751
115136
  init_scaffold();
114752
- import { existsSync as existsSync91, readFileSync as readFileSync82, mkdirSync as mkdirSync51, statSync as statSync52, writeFileSync as writeFileSync36 } from "node:fs";
115137
+ import { existsSync as existsSync91, readFileSync as readFileSync82, mkdirSync as mkdirSync51, statSync as statSync53, writeFileSync as writeFileSync36 } from "node:fs";
114753
115138
  import { execFileSync as execFileSync29 } from "node:child_process";
114754
115139
  import { resolve as resolve48, dirname as dirname38 } from "node:path";
114755
115140
  init_state();
@@ -115473,7 +115858,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath, d
115473
115858
  function writeSwitchroomYaml(configPath, text) {
115474
115859
  let mode = 420;
115475
115860
  try {
115476
- mode = statSync52(configPath).mode & 511;
115861
+ mode = statSync53(configPath).mode & 511;
115477
115862
  } catch {}
115478
115863
  writeConfigFileSync(configPath, text, mode);
115479
115864
  }
@@ -116360,7 +116745,7 @@ init_source();
116360
116745
  init_loader();
116361
116746
  init_lifecycle();
116362
116747
  init_compose_env();
116363
- import { existsSync as existsSync94, mkdirSync as mkdirSync54, readFileSync as readFileSync85, realpathSync as realpathSync8, statSync as statSync54, chownSync as chownSync12 } from "node:fs";
116748
+ import { existsSync as existsSync94, mkdirSync as mkdirSync54, readFileSync as readFileSync85, realpathSync as realpathSync8, statSync as statSync55, chownSync as chownSync12 } from "node:fs";
116364
116749
  import { spawnSync as spawnSync20 } from "node:child_process";
116365
116750
  import { join as join98, dirname as dirname41 } from "node:path";
116366
116751
  import { homedir as homedir51 } from "node:os";
@@ -116423,7 +116808,7 @@ init_operator_uid();
116423
116808
  init_atomic();
116424
116809
 
116425
116810
  // src/cli/preflight-mounts.ts
116426
- import { statSync as statSync53 } from "node:fs";
116811
+ import { statSync as statSync54 } from "node:fs";
116427
116812
  function parseHostBindSources(composeText) {
116428
116813
  const out = [];
116429
116814
  const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
@@ -116450,7 +116835,7 @@ function parseHostBindSources(composeText) {
116450
116835
  return out;
116451
116836
  }
116452
116837
  function validateBindSources(composeText, deps = {}) {
116453
- const stat = deps.stat ?? ((p) => statSync53(p));
116838
+ const stat = deps.stat ?? ((p) => statSync54(p));
116454
116839
  const sources = parseHostBindSources(composeText);
116455
116840
  const issues = [];
116456
116841
  const seen = new Set;
@@ -116812,7 +117197,7 @@ function defaultPersistPin(configPath) {
116812
117197
  const after = setReleasePinInConfig(before, pin);
116813
117198
  if (after === before)
116814
117199
  return;
116815
- writeConfigFileSync(path7, after, statSync54(path7).mode & 511);
117200
+ writeConfigFileSync(path7, after, statSync55(path7).mode & 511);
116816
117201
  try {
116817
117202
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
116818
117203
  const uid = resolveOperatorUid();
@@ -117371,7 +117756,7 @@ function defaultStatusProbe(composePath) {
117371
117756
  } catch {}
117372
117757
  if (scriptPath) {
117373
117758
  try {
117374
- cliBuiltAt = new Date(statSync54(scriptPath).mtimeMs).toISOString();
117759
+ cliBuiltAt = new Date(statSync55(scriptPath).mtimeMs).toISOString();
117375
117760
  } catch {}
117376
117761
  let dir = dirname41(scriptPath);
117377
117762
  for (let i2 = 0;i2 < 8; i2++) {
@@ -117662,7 +118047,7 @@ function registerUpdateCommand(program3) {
117662
118047
  // src/cli/rollout.ts
117663
118048
  init_helpers();
117664
118049
  import { spawnSync as spawnSync22 } from "node:child_process";
117665
- import { readFileSync as readFileSync87, chownSync as chownSync13, statSync as statSync57 } from "node:fs";
118050
+ import { readFileSync as readFileSync87, chownSync as chownSync13, statSync as statSync58 } from "node:fs";
117666
118051
  import { homedir as homedir53 } from "node:os";
117667
118052
 
117668
118053
  // src/cli/rollout-pin-journal.ts
@@ -117672,7 +118057,7 @@ import {
117672
118057
  writeFileSync as writeFileSync38,
117673
118058
  renameSync as renameSync25,
117674
118059
  unlinkSync as unlinkSync19,
117675
- statSync as statSync55,
118060
+ statSync as statSync56,
117676
118061
  mkdirSync as mkdirSync55
117677
118062
  } from "node:fs";
117678
118063
  import { homedir as homedir52 } from "node:os";
@@ -117829,7 +118214,7 @@ function rollbackPinPersist(configPath, opts = {}) {
117829
118214
  const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
117830
118215
  let mode = 384;
117831
118216
  try {
117832
- mode = statSync55(configPath).mode & 511;
118217
+ mode = statSync56(configPath).mode & 511;
117833
118218
  } catch {}
117834
118219
  if (opts.writeConfig) {
117835
118220
  opts.writeConfig(configPath, next, mode);
@@ -117879,7 +118264,7 @@ init_host_cli_stamp();
117879
118264
 
117880
118265
  // src/cli/host-cli-upgrade.ts
117881
118266
  init_self_update();
117882
- import { lchownSync, lstatSync as lstatSync14, readdirSync as readdirSync33, statSync as statSync56 } from "node:fs";
118267
+ import { lchownSync, lstatSync as lstatSync14, readdirSync as readdirSync33, statSync as statSync57 } from "node:fs";
117883
118268
  import { basename as basename10, dirname as dirname43, join as join100 } from "node:path";
117884
118269
  init_shipped_assets();
117885
118270
  var HOST_CLI_UPGRADE_SENTINEL = "SWITCHROOM_HOST_CLI_UPGRADE:";
@@ -117916,7 +118301,7 @@ function defaultIo() {
117916
118301
  return {
117917
118302
  isFile: (p) => {
117918
118303
  try {
117919
- return statSync56(p).isFile();
118304
+ return statSync57(p).isFile();
117920
118305
  } catch {
117921
118306
  return false;
117922
118307
  }
@@ -118881,7 +119266,7 @@ function createRolloutDeps(params) {
118881
119266
  if (after === before)
118882
119267
  return false;
118883
119268
  beginPinPersist(configPath, pin);
118884
- writeConfigPreservingOwnership(configPath, after, statSync57(configPath).mode & 511);
119269
+ writeConfigPreservingOwnership(configPath, after, statSync58(configPath).mode & 511);
118885
119270
  return true;
118886
119271
  },
118887
119272
  commitPin: () => {
@@ -119403,7 +119788,7 @@ init_merge();
119403
119788
  import {
119404
119789
  existsSync as existsSync97,
119405
119790
  readdirSync as readdirSync34,
119406
- statSync as statSync58,
119791
+ statSync as statSync59,
119407
119792
  unlinkSync as unlinkSync20
119408
119793
  } from "node:fs";
119409
119794
  import { join as join102 } from "node:path";
@@ -119426,7 +119811,7 @@ function collectSessionJsonl(claudeConfigDir) {
119426
119811
  const full = join102(dir, name);
119427
119812
  let st;
119428
119813
  try {
119429
- st = statSync58(full);
119814
+ st = statSync59(full);
119430
119815
  } catch {
119431
119816
  continue;
119432
119817
  }
@@ -119550,7 +119935,7 @@ import {
119550
119935
  readdirSync as readdirSync35,
119551
119936
  readFileSync as readFileSync89,
119552
119937
  renameSync as renameSync26,
119553
- statSync as statSync59,
119938
+ statSync as statSync60,
119554
119939
  unlinkSync as unlinkSync21,
119555
119940
  writeFileSync as writeFileSync39,
119556
119941
  writeSync as writeSync9
@@ -119753,7 +120138,7 @@ function sweepOrphanTmpFiles(stateDir) {
119753
120138
  continue;
119754
120139
  const tmpPath = join103(stateDir, entry);
119755
120140
  try {
119756
- const stat = statSync59(tmpPath);
120141
+ const stat = statSync60(tmpPath);
119757
120142
  if (stat.mtimeMs < cutoff) {
119758
120143
  unlinkSync21(tmpPath);
119759
120144
  }
@@ -121318,7 +121703,7 @@ function registerSoulCommand(program3) {
121318
121703
  // src/cli/debug.ts
121319
121704
  init_helpers();
121320
121705
  init_loader();
121321
- import { existsSync as existsSync104, readFileSync as readFileSync93, readdirSync as readdirSync36, statSync as statSync60 } from "node:fs";
121706
+ import { existsSync as existsSync104, readFileSync as readFileSync93, readdirSync as readdirSync36, statSync as statSync61 } from "node:fs";
121322
121707
  import { resolve as resolve57, join as join108 } from "node:path";
121323
121708
  import { createHash as createHash20 } from "node:crypto";
121324
121709
  init_merge();
@@ -121357,7 +121742,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
121357
121742
  const transcriptPath = join108(projectPath, "transcript.jsonl");
121358
121743
  if (!existsSync104(transcriptPath))
121359
121744
  continue;
121360
- const stat3 = statSync60(transcriptPath);
121745
+ const stat3 = statSync61(transcriptPath);
121361
121746
  if (!latest || stat3.mtimeMs > latest.mtime) {
121362
121747
  latest = { path: transcriptPath, mtime: stat3.mtimeMs };
121363
121748
  }
@@ -121644,7 +122029,7 @@ import { execFileSync as execFileSync32 } from "node:child_process";
121644
122029
  import { existsSync as existsSync106 } from "node:fs";
121645
122030
 
121646
122031
  // src/worktree/proc-liveness.ts
121647
- import { readdirSync as readdirSync37, readlinkSync as readlinkSync8, realpathSync as realpathSync9, statSync as statSync61, readFileSync as readFileSync94 } from "node:fs";
122032
+ import { readdirSync as readdirSync37, readlinkSync as readlinkSync8, realpathSync as realpathSync9, statSync as statSync62, readFileSync as readFileSync94 } from "node:fs";
121648
122033
  import { join as join109, sep as sep8 } from "node:path";
121649
122034
  function isPidDir(name) {
121650
122035
  return /^\d+$/.test(name);
@@ -121672,7 +122057,7 @@ function cwdIsUnderRootByInode(linkPath, rootId, maxDepth) {
121672
122057
  for (let hop = 0;hop <= maxDepth; hop++) {
121673
122058
  let st;
121674
122059
  try {
121675
- st = statSync61(linkPath + suffix);
122060
+ st = statSync62(linkPath + suffix);
121676
122061
  } catch {
121677
122062
  return false;
121678
122063
  }
@@ -121693,7 +122078,7 @@ function scanProcForHolders(path10, opts = {}) {
121693
122078
  let rootId;
121694
122079
  try {
121695
122080
  root = realpathSync9(path10);
121696
- const st = statSync61(root);
122081
+ const st = statSync62(root);
121697
122082
  rootId = { dev: st.dev, ino: st.ino };
121698
122083
  } catch {
121699
122084
  return { state: "unavailable", inaccessible: 0 };
@@ -121916,7 +122301,7 @@ import {
121916
122301
  existsSync as existsSync107,
121917
122302
  readFileSync as readFileSync95,
121918
122303
  readdirSync as readdirSync38,
121919
- statSync as statSync62,
122304
+ statSync as statSync63,
121920
122305
  renameSync as renameSync27,
121921
122306
  mkdirSync as mkdirSync59,
121922
122307
  rmSync as rmSync21
@@ -122124,7 +122509,7 @@ function defaultNewestTrackedMtimeMs(dir, exec) {
122124
122509
  let newest = 0;
122125
122510
  for (const f of files) {
122126
122511
  try {
122127
- const m = statSync62(join110(dir, f)).mtimeMs;
122512
+ const m = statSync63(join110(dir, f)).mtimeMs;
122128
122513
  if (m > newest)
122129
122514
  newest = m;
122130
122515
  } catch {}
@@ -122149,7 +122534,7 @@ function planGc(roots, deps = {}, taskTreeRoots = [], taskTreeDirs = []) {
122149
122534
  const exists = deps.existsSync ?? existsSync107;
122150
122535
  const readDir = deps.readDir ?? ((p) => readdirSync38(p));
122151
122536
  const readFile4 = deps.readFile ?? ((p) => readFileSync95(p, "utf8"));
122152
- const stat3 = deps.stat ?? ((p) => statSync62(p));
122537
+ const stat3 = deps.stat ?? ((p) => statSync63(p));
122153
122538
  const exec = deps.exec ?? execCapture;
122154
122539
  const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
122155
122540
  const stamp = deps.dateStamp ?? "undated";
@@ -122526,7 +122911,7 @@ function listTrashEntries(nowMs, deps = {}) {
122526
122911
  const p = join110(stampDir, name);
122527
122912
  let mtimeMs = nowMs;
122528
122913
  try {
122529
- mtimeMs = statSync62(p).mtimeMs;
122914
+ mtimeMs = statSync63(p).mtimeMs;
122530
122915
  } catch {}
122531
122916
  out.push({ path: p, ageDays: (nowMs - mtimeMs) / 86400000 });
122532
122917
  }
@@ -122879,7 +123264,7 @@ import {
122879
123264
  mkdirSync as mkdirSync60,
122880
123265
  mkdtempSync as mkdtempSync5,
122881
123266
  rmSync as rmSync22,
122882
- statSync as statSync63,
123267
+ statSync as statSync64,
122883
123268
  writeFileSync as writeFileSync43
122884
123269
  } from "node:fs";
122885
123270
  import { homedir as homedir58 } from "node:os";
@@ -123036,7 +123421,7 @@ function captureCheckout(opts) {
123036
123421
  };
123037
123422
  let st;
123038
123423
  try {
123039
- st = statSync63(source);
123424
+ st = statSync64(source);
123040
123425
  } catch {
123041
123426
  return { ...base, failure: "missing", error: `No such directory: ${source}` };
123042
123427
  }
@@ -123194,7 +123579,7 @@ function captureCheckout(opts) {
123194
123579
  };
123195
123580
  }
123196
123581
  opts.onBundleWritten?.(bundlePath);
123197
- const bytes = statSync63(bundlePath).size;
123582
+ const bytes = statSync64(bundlePath).size;
123198
123583
  if (bytes === 0) {
123199
123584
  return {
123200
123585
  ...base,
@@ -124623,6 +125008,7 @@ class KnowledgeAdmin {
124623
125008
  }
124624
125009
 
124625
125010
  // src/cli/hindsight-mcp-shim.ts
125011
+ init_hindsight_shim_contract();
124626
125012
  init_hindsight();
124627
125013
  var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
124628
125014
  "2025-06-18",
@@ -125234,6 +125620,17 @@ class HindsightShim {
125234
125620
  }
125235
125621
  return this.knowledgeAdminCache;
125236
125622
  }
125623
+ contractPinCache = null;
125624
+ get contractPin() {
125625
+ if (!this.contractPinCache) {
125626
+ this.contractPinCache = new ShimContractPin(this.opts.apiBaseUrl ?? this.opts.url.replace(/\/mcp\/?$/, ""), {
125627
+ ...this.opts.fetchImpl ? { fetchImpl: this.opts.fetchImpl } : {},
125628
+ negativeCacheMs: 30000,
125629
+ positiveCacheMs: 5 * 60000
125630
+ });
125631
+ }
125632
+ return this.contractPinCache;
125633
+ }
125237
125634
  async synthesizedCall(name, rawArgs) {
125238
125635
  const fail4 = (text) => ({
125239
125636
  content: [{ type: "text", text }],
@@ -125261,6 +125658,9 @@ class HindsightShim {
125261
125658
  if (!this.opts.bankId) {
125262
125659
  return fail4(`${name} is unavailable: this agent has no HINDSIGHT_BANK_ID, so ` + "there is no bank to pin it to.");
125263
125660
  }
125661
+ const preflight = await this.contractPin.preflight(name);
125662
+ if (!preflight.ok)
125663
+ return fail4(preflight.text);
125264
125664
  try {
125265
125665
  const text = await this.runSynthesized(name, clean3);
125266
125666
  return { content: [{ type: "text", text }], isError: false };
@@ -125425,7 +125825,7 @@ function registerHindsightMcpShimCommand(program3) {
125425
125825
 
125426
125826
  // src/cli/deliver-file.ts
125427
125827
  init_client2();
125428
- import { readFileSync as readFileSync97, statSync as statSync64 } from "node:fs";
125828
+ import { readFileSync as readFileSync97, statSync as statSync65 } from "node:fs";
125429
125829
  import { basename as basename15 } from "node:path";
125430
125830
 
125431
125831
  // src/delivery/onedrive.ts
@@ -125764,7 +126164,7 @@ async function defaultResolveProvider() {
125764
126164
  }
125765
126165
  async function runDeliverFile(localPath, deps = {}) {
125766
126166
  const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
125767
- const sizeOf = deps.fileSize ?? ((p) => statSync64(p).size);
126167
+ const sizeOf = deps.fileSize ?? ((p) => statSync65(p).size);
125768
126168
  const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync97(p)));
125769
126169
  const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
125770
126170
  let size;
@@ -126474,7 +126874,7 @@ import {
126474
126874
  readdirSync as readdirSync41,
126475
126875
  readFileSync as readFileSync99,
126476
126876
  renameSync as renameSync29,
126477
- statSync as statSync65,
126877
+ statSync as statSync66,
126478
126878
  unlinkSync as unlinkSync22,
126479
126879
  writeSync as writeSync10
126480
126880
  } from "node:fs";
@@ -126518,7 +126918,7 @@ function withAgentLock(paths, fn) {
126518
126918
  if (e.code !== "EEXIST")
126519
126919
  throw err;
126520
126920
  try {
126521
- const age = Date.now() - statSync65(paths.lockPath).mtimeMs;
126921
+ const age = Date.now() - statSync66(paths.lockPath).mtimeMs;
126522
126922
  if (age > 30000) {
126523
126923
  unlinkSync22(paths.lockPath);
126524
126924
  continue;
@@ -126551,7 +126951,7 @@ function alignStagedOwnerToDir(stagingPath, targetDir) {
126551
126951
  const euid = overlayWriterRuntime.geteuid();
126552
126952
  if (euid === undefined)
126553
126953
  return;
126554
- const dirStat = statSync65(targetDir);
126954
+ const dirStat = statSync66(targetDir);
126555
126955
  if (dirStat.uid === euid)
126556
126956
  return;
126557
126957
  overlayWriterRuntime.chown(stagingPath, dirStat.uid, dirStat.gid);
@@ -127853,7 +128253,7 @@ import {
127853
128253
  realpathSync as realpathSync10,
127854
128254
  renameSync as renameSync31,
127855
128255
  rmSync as rmSync24,
127856
- statSync as statSync66,
128256
+ statSync as statSync67,
127857
128257
  writeFileSync as writeFileSync49
127858
128258
  } from "node:fs";
127859
128259
  import { tmpdir as tmpdir7, homedir as homedir60 } from "node:os";
@@ -127903,7 +128303,7 @@ function isTarballPath(p) {
127903
128303
  }
127904
128304
  function loadFromDir(dir) {
127905
128305
  const abs = realpathSync10(dir);
127906
- if (!statSync66(abs).isDirectory()) {
128306
+ if (!statSync67(abs).isDirectory()) {
127907
128307
  fail4(`--from path is not a directory: ${dir}`);
127908
128308
  }
127909
128309
  const files = {};
@@ -128164,7 +128564,7 @@ function registerSkillCommand(program3) {
128164
128564
  if (!existsSync115(fromPath)) {
128165
128565
  fail4(`--from path does not exist: ${opts.from}`);
128166
128566
  }
128167
- const st = statSync66(fromPath);
128567
+ const st = statSync67(fromPath);
128168
128568
  if (st.isDirectory()) {
128169
128569
  files = loadFromDir(fromPath);
128170
128570
  } else if (isTarballPath(fromPath)) {
@@ -128231,7 +128631,7 @@ import {
128231
128631
  readdirSync as readdirSync44,
128232
128632
  renameSync as renameSync32,
128233
128633
  rmSync as rmSync25,
128234
- statSync as statSync67,
128634
+ statSync as statSync68,
128235
128635
  utimesSync,
128236
128636
  writeFileSync as writeFileSync50
128237
128637
  } from "node:fs";
@@ -128385,7 +128785,7 @@ function readStdinSync2() {
128385
128785
  }
128386
128786
  function loadFromDir2(dir) {
128387
128787
  const abs = resolve62(dir);
128388
- if (!statSync67(abs).isDirectory()) {
128788
+ if (!statSync68(abs).isDirectory()) {
128389
128789
  fail5(`--from path is not a directory: ${dir}`);
128390
128790
  }
128391
128791
  const files = {};
@@ -128471,7 +128871,7 @@ function sweepTrash(agentsRoot, agent) {
128471
128871
  continue;
128472
128872
  const entPath = join120(trash, ent.name);
128473
128873
  try {
128474
- const st = statSync67(entPath);
128874
+ const st = statSync68(entPath);
128475
128875
  if (now2 - st.mtimeMs > TRASH_TTL_MS) {
128476
128876
  rmSync25(entPath, { recursive: true, force: true });
128477
128877
  }
@@ -128597,7 +128997,7 @@ function loadFiles(opts) {
128597
128997
  if (!existsSync116(p)) {
128598
128998
  fail5(`--from path does not exist: ${opts.from}`);
128599
128999
  }
128600
- const st = statSync67(p);
129000
+ const st = statSync68(p);
128601
129001
  if (st.isDirectory()) {
128602
129002
  return loadFromDir2(p);
128603
129003
  }
@@ -128818,7 +129218,7 @@ function listPersonalAction(opts) {
128818
129218
  if (e.isFile()) {
128819
129219
  fileCount += 1;
128820
129220
  try {
128821
- totalBytes += statSync67(join120(sub, e.name)).size;
129221
+ totalBytes += statSync68(join120(sub, e.name)).size;
128822
129222
  } catch {}
128823
129223
  } else if (e.isDirectory()) {
128824
129224
  walk2(join120(sub, e.name));
@@ -128992,7 +129392,7 @@ import { join as join125, resolve as resolve63, sep as sep10 } from "node:path";
128992
129392
  import { existsSync as existsSync121, readFileSync as readFileSync109, realpathSync as realpathSync11 } from "node:fs";
128993
129393
 
128994
129394
  // src/self-improve/apply-guard.ts
128995
- import { existsSync as existsSync119, lstatSync as lstatSync17, readFileSync as readFileSync107, statSync as statSync69 } from "node:fs";
129395
+ import { existsSync as existsSync119, lstatSync as lstatSync17, readFileSync as readFileSync107, statSync as statSync70 } from "node:fs";
128996
129396
 
128997
129397
  // src/self-improve/config.ts
128998
129398
  function intEnv(name, def) {
@@ -129013,7 +129413,7 @@ function resolveSelfImproveConfig() {
129013
129413
  }
129014
129414
 
129015
129415
  // src/self-improve/eval-gate.ts
129016
- import { existsSync as existsSync117, readFileSync as readFileSync105, readdirSync as readdirSync45, statSync as statSync68 } from "node:fs";
129416
+ import { existsSync as existsSync117, readFileSync as readFileSync105, readdirSync as readdirSync45, statSync as statSync69 } from "node:fs";
129017
129417
  import { join as join122 } from "node:path";
129018
129418
  import { spawnSync as spawnSync26 } from "node:child_process";
129019
129419
  function evalsJsonPath(skillDir) {
@@ -129049,7 +129449,7 @@ function aggregateBenchmark(benchmarkDir, repoRoot, pythonBin = process.env.SWIT
129049
129449
  }
129050
129450
  function isBenchmarkDir(dir) {
129051
129451
  try {
129052
- if (!statSync68(dir).isDirectory())
129452
+ if (!statSync69(dir).isDirectory())
129053
129453
  return false;
129054
129454
  } catch {
129055
129455
  return false;
@@ -129080,7 +129480,7 @@ function resolveBenchmarkDir(baseDir, explicit) {
129080
129480
  }
129081
129481
  if (entries.length === 0)
129082
129482
  return null;
129083
- entries.sort((a, b) => statSync68(b).mtimeMs - statSync68(a).mtimeMs);
129483
+ entries.sort((a, b) => statSync69(b).mtimeMs - statSync69(a).mtimeMs);
129084
129484
  return entries[0];
129085
129485
  }
129086
129486
  var CANDIDATE_CONFIGS = ["with_skill", "candidate", "new_skill", "after"];
@@ -129779,7 +130179,7 @@ function registerSelfImproveBenchCommand(program3) {
129779
130179
  init_esm();
129780
130180
  init_helpers();
129781
130181
  var import_yaml26 = __toESM(require_dist(), 1);
129782
- import { existsSync as existsSync122, readdirSync as readdirSync47, readFileSync as readFileSync110, statSync as statSync70 } from "node:fs";
130182
+ import { existsSync as existsSync122, readdirSync as readdirSync47, readFileSync as readFileSync110, statSync as statSync71 } from "node:fs";
129783
130183
  import { homedir as homedir65 } from "node:os";
129784
130184
  import { join as join127, resolve as resolve64 } from "node:path";
129785
130185
  var PERSONAL_PREFIX2 = "personal-";
@@ -129830,7 +130230,7 @@ function readSkillFrontmatter(skillDir) {
129830
130230
  function statSkillMd(skillDir) {
129831
130231
  const mdPath = join127(skillDir, "SKILL.md");
129832
130232
  try {
129833
- const st = statSync70(mdPath);
130233
+ const st = statSync71(mdPath);
129834
130234
  return { size: st.size, mtime: st.mtime.toISOString() };
129835
130235
  } catch {
129836
130236
  return null;
@@ -129854,7 +130254,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
129854
130254
  continue;
129855
130255
  const dirPath = join127(skillsDir, ent);
129856
130256
  try {
129857
- if (!statSync70(dirPath).isDirectory())
130257
+ if (!statSync71(dirPath).isDirectory())
129858
130258
  continue;
129859
130259
  } catch {
129860
130260
  continue;
@@ -129894,7 +130294,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
129894
130294
  continue;
129895
130295
  const dirPath = join127(sharedRoot, ent);
129896
130296
  try {
129897
- if (!statSync70(dirPath).isDirectory())
130297
+ if (!statSync71(dirPath).isDirectory())
129898
130298
  continue;
129899
130299
  } catch {
129900
130300
  continue;
@@ -129930,7 +130330,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
129930
130330
  continue;
129931
130331
  const dirPath = join127(bundledRoot, ent);
129932
130332
  try {
129933
- if (!statSync70(dirPath).isDirectory())
130333
+ if (!statSync71(dirPath).isDirectory())
129934
130334
  continue;
129935
130335
  } catch {
129936
130336
  continue;
@@ -130079,7 +130479,7 @@ import {
130079
130479
  mkdirSync as mkdirSync71,
130080
130480
  readdirSync as readdirSync48,
130081
130481
  writeFileSync as writeFileSync53,
130082
- statSync as statSync71,
130482
+ statSync as statSync72,
130083
130483
  lstatSync as lstatSync18,
130084
130484
  realpathSync as realpathSync12,
130085
130485
  copyFileSync as copyFileSync16
@@ -130600,7 +131000,7 @@ function doStatus() {
130600
131000
  continue;
130601
131001
  const sockPath = join128(dir, name, "sock");
130602
131002
  if (existsSync124(sockPath)) {
130603
- const st = statSync71(sockPath);
131003
+ const st = statSync72(sockPath);
130604
131004
  if ((st.mode & 61440) === 49152) {
130605
131005
  entries.push(`${name} \u2192 ${sockPath}`);
130606
131006
  }
@@ -131501,7 +131901,7 @@ init_install_cron();
131501
131901
 
131502
131902
  // src/hindsight-watch/probe.ts
131503
131903
  import { spawnSync as spawnSync32 } from "node:child_process";
131504
- import { readFileSync as readFileSync115, readdirSync as readdirSync52, statSync as statSync73 } from "node:fs";
131904
+ import { readFileSync as readFileSync115, readdirSync as readdirSync52, statSync as statSync74 } from "node:fs";
131505
131905
  import { homedir as homedir70 } from "node:os";
131506
131906
  import { resolve as resolve66 } from "node:path";
131507
131907
 
@@ -131629,7 +132029,7 @@ function readLlmSignals(series) {
131629
132029
  }
131630
132030
 
131631
132031
  // src/hindsight-watch/recall-log.ts
131632
- import { closeSync as closeSync26, existsSync as existsSync129, openSync as openSync26, readdirSync as readdirSync51, readSync as readSync7, statSync as statSync72 } from "node:fs";
132032
+ import { closeSync as closeSync26, existsSync as existsSync129, openSync as openSync26, readdirSync as readdirSync51, readSync as readSync7, statSync as statSync73 } from "node:fs";
131633
132033
  import { join as join132 } from "node:path";
131634
132034
  var RECALL_WINDOW_ROWS = 200;
131635
132035
  var RECALL_MAX_TAIL_BYTES = 1024 * 1024;
@@ -131644,7 +132044,7 @@ function readRecallLogTail2(path10, windowRows = RECALL_WINDOW_ROWS) {
131644
132044
  let truncatedHead = false;
131645
132045
  let fd;
131646
132046
  try {
131647
- const size = statSync72(path10).size;
132047
+ const size = statSync73(path10).size;
131648
132048
  const start = Math.max(0, size - RECALL_MAX_TAIL_BYTES);
131649
132049
  truncatedHead = start > 0;
131650
132050
  const length = size - start;
@@ -131892,7 +132292,7 @@ function readDropCount(hindsightDir) {
131892
132292
  const path10 = resolve66(hindsightDir, "pending-drops.json");
131893
132293
  let parsed;
131894
132294
  try {
131895
- if (statSync73(path10).size > DROPS_LEDGER_MAX_BYTES)
132295
+ if (statSync74(path10).size > DROPS_LEDGER_MAX_BYTES)
131896
132296
  return 0;
131897
132297
  parsed = JSON.parse(readFileSync115(path10, "utf8"));
131898
132298
  } catch {