switchroom 0.19.1 → 0.19.3

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.
Files changed (80) hide show
  1. package/dist/agent-scheduler/index.js +31 -1
  2. package/dist/auth-broker/index.js +565 -48
  3. package/dist/cli/autoaccept-poll.js +31 -1
  4. package/dist/cli/drive-write-pretool.mjs +32 -2
  5. package/dist/cli/ms-365-write-pretool.mjs +32 -2
  6. package/dist/cli/switchroom.js +1148 -274
  7. package/dist/host-control/main.js +3 -3
  8. package/dist/vault/approvals/kernel-server.js +2 -2
  9. package/dist/vault/broker/server.js +2 -2
  10. package/package.json +3 -2
  11. package/profiles/_base/start.sh.hbs +1 -0
  12. package/profiles/default/CLAUDE.md.hbs +8 -0
  13. package/skills/mental-model-curator/SKILL.md +68 -2
  14. package/skills/switchroom-cli/SKILL.md +25 -0
  15. package/telegram-plugin/auth-snapshot-format.ts +143 -12
  16. package/telegram-plugin/dist/bridge/bridge.js +8 -2
  17. package/telegram-plugin/dist/gateway/gateway.js +1427 -689
  18. package/telegram-plugin/dist/server.js +8 -2
  19. package/telegram-plugin/external-spend.ts +135 -0
  20. package/telegram-plugin/flushed-turn-supersede.ts +117 -13
  21. package/telegram-plugin/gateway/auth-add-flow.ts +215 -6
  22. package/telegram-plugin/gateway/auth-command.ts +138 -5
  23. package/telegram-plugin/gateway/gateway.ts +141 -158
  24. package/telegram-plugin/gateway/inbound-interceptors.ts +13 -3
  25. package/telegram-plugin/gateway/model-command.ts +309 -1
  26. package/telegram-plugin/gateway/narrative-lane.ts +23 -9
  27. package/telegram-plugin/gateway/outbound-send-path.ts +68 -15
  28. package/telegram-plugin/gateway/session-model-source.ts +90 -10
  29. package/telegram-plugin/gateway/status-pin-store.ts +64 -4
  30. package/telegram-plugin/gateway/stream-render.ts +22 -5
  31. package/telegram-plugin/gateway/usage-mask.ts +29 -0
  32. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +19 -2
  33. package/telegram-plugin/quota-bar-format.ts +78 -12
  34. package/telegram-plugin/quota-check.ts +17 -2
  35. package/telegram-plugin/reply-owner-resolve.ts +76 -11
  36. package/telegram-plugin/session-tail.ts +27 -3
  37. package/telegram-plugin/tests/activity-card-wiring.test.ts +47 -0
  38. package/telegram-plugin/tests/auth-add-flow.test.ts +367 -5
  39. package/telegram-plugin/tests/auth-snapshot-format.test.ts +41 -0
  40. package/telegram-plugin/tests/external-spend.test.ts +168 -0
  41. package/telegram-plugin/tests/flushed-turn-supersede.test.ts +117 -0
  42. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +219 -29
  43. package/telegram-plugin/tests/model-command.test.ts +220 -0
  44. package/telegram-plugin/tests/quota-bar-format.test.ts +43 -0
  45. package/telegram-plugin/tests/quota-check.test.ts +57 -0
  46. package/telegram-plugin/tests/reply-owner-resolve.test.ts +257 -13
  47. package/telegram-plugin/tests/send-reply-golden.test.ts +154 -0
  48. package/telegram-plugin/tests/session-model-source.test.ts +142 -0
  49. package/telegram-plugin/tests/session-tail-first-attach.test.ts +115 -2
  50. package/telegram-plugin/tests/status-pin-store.test.ts +198 -0
  51. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +50 -0
  52. package/telegram-plugin/tests/usage-footer-freshness.test.ts +141 -0
  53. package/telegram-plugin/tests/usage-mask.test.ts +35 -0
  54. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +27 -0
  55. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +131 -1
  56. package/vendor/hindsight-memory/CHANGELOG.md +102 -0
  57. package/vendor/hindsight-memory/README.md +2 -1
  58. package/vendor/hindsight-memory/hooks/hooks.json +12 -0
  59. package/vendor/hindsight-memory/scripts/directive_verify.py +100 -3
  60. package/vendor/hindsight-memory/scripts/lib/config.py +150 -1
  61. package/vendor/hindsight-memory/scripts/lib/content.py +55 -5
  62. package/vendor/hindsight-memory/scripts/lib/directives.py +152 -15
  63. package/vendor/hindsight-memory/scripts/lib/parallel_recall.py +142 -0
  64. package/vendor/hindsight-memory/scripts/lib/state.py +31 -0
  65. package/vendor/hindsight-memory/scripts/recall.py +789 -143
  66. package/vendor/hindsight-memory/scripts/reconcile_tail.py +22 -1
  67. package/vendor/hindsight-memory/scripts/retain.py +71 -2
  68. package/vendor/hindsight-memory/scripts/subagent_retain.py +501 -0
  69. package/vendor/hindsight-memory/scripts/tests/test_directive_verify.py +169 -0
  70. package/vendor/hindsight-memory/scripts/tests/test_directives.py +177 -0
  71. package/vendor/hindsight-memory/scripts/tests/test_lesson_tagging.py +200 -0
  72. package/vendor/hindsight-memory/scripts/tests/test_recall_context_turns_default.py +200 -0
  73. package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +477 -0
  74. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +51 -0
  75. package/vendor/hindsight-memory/scripts/tests/test_recall_parallel_deadline.py +409 -0
  76. package/vendor/hindsight-memory/scripts/tests/test_recall_tag_weights.py +96 -0
  77. package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +413 -0
  78. package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +49 -0
  79. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +439 -0
  80. package/vendor/hindsight-memory/settings.json +3 -1
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.19.1", COMMIT_SHA = "370b7c4b";
2123
+ var VERSION = "0.19.3", COMMIT_SHA = "41896be4";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -15452,14 +15452,27 @@ __export(exports_atomic, {
15452
15452
  atomicWriteFileSync: () => atomicWriteFileSync
15453
15453
  });
15454
15454
  import { randomBytes } from "node:crypto";
15455
- import { closeSync as closeSync2, constants, fsyncSync, openSync as openSync2, renameSync, rmSync, writeSync } from "node:fs";
15456
- function atomicWriteFileSync(destPath, contents, mode = 384) {
15455
+ import { closeSync as closeSync2, constants, fchmodSync, fchownSync, fsyncSync, openSync as openSync2, renameSync, rmSync, writeSync } from "node:fs";
15456
+ function atomicWriteFileSync(destPath, contents, modeOrOpts = 384) {
15457
+ const opts = typeof modeOrOpts === "number" ? { mode: modeOrOpts } : modeOrOpts;
15458
+ const mode = opts.mode ?? 384;
15457
15459
  const tmp = `${destPath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
15458
15460
  const buf = typeof contents === "string" ? Buffer.from(contents, "utf-8") : contents;
15459
15461
  let fd = null;
15460
15462
  try {
15461
15463
  fd = openSync2(tmp, TMP_OPEN_FLAGS, mode);
15462
15464
  writeSync(fd, buf, 0, buf.length, 0);
15465
+ fchmodSync(fd, mode);
15466
+ if (opts.uid !== undefined) {
15467
+ try {
15468
+ fchownSync(fd, opts.uid, opts.gid ?? opts.uid);
15469
+ } catch (chownErr) {
15470
+ if (opts.onChownError)
15471
+ opts.onChownError(chownErr);
15472
+ else
15473
+ throw chownErr;
15474
+ }
15475
+ }
15463
15476
  fsyncSync(fd);
15464
15477
  closeSync2(fd);
15465
15478
  fd = null;
@@ -15655,6 +15668,53 @@ function resolveHindsightLlm(llm, litellm) {
15655
15668
  const model = llm?.model?.trim() || (litellm ? litellm.model ?? HINDSIGHT_DEFAULT_LITELLM_MODEL : HINDSIGHT_DEFAULT_MODEL);
15656
15669
  return { provider, model };
15657
15670
  }
15671
+ function isLoopbackHttpUrl(url) {
15672
+ try {
15673
+ const u = new URL(url.includes("://") ? url : `http://${url}`);
15674
+ const h = (u.hostname || "").toLowerCase();
15675
+ return h === "localhost" || h === "127.0.0.1" || h === "::1";
15676
+ } catch {
15677
+ return false;
15678
+ }
15679
+ }
15680
+ function collectHindsightLlmBaseUrls(llm, litellm) {
15681
+ const out = [];
15682
+ const seen = new Set;
15683
+ const push = (raw) => {
15684
+ const v = raw?.trim();
15685
+ if (!v || seen.has(v))
15686
+ return;
15687
+ seen.add(v);
15688
+ out.push(v);
15689
+ };
15690
+ push(litellm?.baseUrl);
15691
+ for (const [k, v] of resolveHindsightPerOpLlm(llm)) {
15692
+ if (k.endsWith("_BASE_URL"))
15693
+ push(v);
15694
+ }
15695
+ return out;
15696
+ }
15697
+ function hindsightNeedsHostNetwork(llm, litellm) {
15698
+ if (litellm?.baseUrl?.trim())
15699
+ return true;
15700
+ return collectHindsightLlmBaseUrls(llm).some(isLoopbackHttpUrl);
15701
+ }
15702
+ function pickHindsightLiteLlmProbeUrl(llm, litellm) {
15703
+ return collectHindsightLlmBaseUrls(llm, litellm)[0];
15704
+ }
15705
+ function buildLiteLlmAwareHealthPy(apiPort, litellmBaseUrl) {
15706
+ let host = "127.0.0.1";
15707
+ let port = "80";
15708
+ try {
15709
+ const u = new URL(litellmBaseUrl.includes("://") ? litellmBaseUrl : `http://${litellmBaseUrl}`);
15710
+ host = u.hostname || host;
15711
+ port = u.port || (u.protocol === "https:" ? "443" : "80");
15712
+ } catch {}
15713
+ return `import socket,urllib.request,sys;` + `r=urllib.request.urlopen('http://localhost:${apiPort}/health',timeout=4);` + `(r.getcode()==200) or sys.exit(1);` + `s=socket.create_connection(('${host}',${Number(port)}),2);s.close()`;
15714
+ }
15715
+ function buildLiteLlmAwareHealthCmd(apiPort, litellmBaseUrl) {
15716
+ return `python3 -c "${buildLiteLlmAwareHealthPy(apiPort, litellmBaseUrl)}"`;
15717
+ }
15658
15718
  function startHindsight(ports, litellm, imageTag, llm, mirrorDir) {
15659
15719
  const apiPort = ports?.apiPort ?? HINDSIGHT_DEFAULT_API_PORT;
15660
15720
  const uiPort = ports?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT;
@@ -15687,7 +15747,9 @@ function startHindsight(ports, litellm, imageTag, llm, mirrorDir) {
15687
15747
  "-e",
15688
15748
  `HINDSIGHT_API_CONSOLIDATION_LLM_PARALLELISM=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM}`,
15689
15749
  "-e",
15690
- `HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`
15750
+ `HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`,
15751
+ "-e",
15752
+ `HINDSIGHT_API_WORKER_ID=${HINDSIGHT_DEFAULT_WORKER_ID}`
15691
15753
  ];
15692
15754
  if (llmProvider === "claude-code") {
15693
15755
  envArgs.push("-e", `ANTHROPIC_MODEL=${llmModel}`);
@@ -15710,8 +15772,15 @@ x-litellm-tags: service:hindsight`);
15710
15772
  `--memory-reservation=${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
15711
15773
  `--pids-limit=${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
15712
15774
  `--shm-size=${HINDSIGHT_DEFAULT_SHM_SIZE}`,
15713
- "--health-cmd",
15714
- litellm ? `python3 -c 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:${apiPort}/health",timeout=4).getcode()==200 else 1)'` : HINDSIGHT_HEALTHCHECK_CMD,
15775
+ ...(() => {
15776
+ const hostNet = hindsightNeedsHostNetwork(llm, litellm);
15777
+ const healthApiPort = hostNet ? apiPort : 8888;
15778
+ const probe = pickHindsightLiteLlmProbeUrl(llm, litellm);
15779
+ return [
15780
+ "--health-cmd",
15781
+ probe ? buildLiteLlmAwareHealthCmd(healthApiPort, probe) : HINDSIGHT_HEALTHCHECK_CMD
15782
+ ];
15783
+ })(),
15715
15784
  "--health-interval",
15716
15785
  "30s",
15717
15786
  "--health-timeout",
@@ -15721,8 +15790,11 @@ x-litellm-tags: service:hindsight`);
15721
15790
  "--health-start-period",
15722
15791
  "60s"
15723
15792
  ];
15724
- if (litellm) {
15793
+ if (hindsightNeedsHostNetwork(llm, litellm)) {
15725
15794
  args.push("--network", "host");
15795
+ if (!litellm) {
15796
+ envArgs.push("-e", `HINDSIGHT_API_PORT=${apiPort}`, "-e", `HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:${apiPort}`);
15797
+ }
15726
15798
  } else {
15727
15799
  args.push("-p", `127.0.0.1:${apiPort}:8888`, "-p", `127.0.0.1:${uiPort}:9999`);
15728
15800
  }
@@ -15744,13 +15816,40 @@ x-litellm-tags: service:hindsight`);
15744
15816
  }
15745
15817
  execFileSync("docker", args, { stdio: "pipe" });
15746
15818
  }
15747
- function stopHindsight() {
15819
+ function listHindsightDataVolumeMounts(exec = (cmd, args) => execFileSync(cmd, args, { stdio: "pipe", encoding: "utf-8" })) {
15748
15820
  try {
15749
- execFileSync("docker", ["stop", "switchroom-hindsight"], { stdio: "pipe" });
15750
- } catch {}
15751
- try {
15752
- execFileSync("docker", ["rm", "switchroom-hindsight"], { stdio: "pipe" });
15753
- } catch {}
15821
+ const out = exec("docker", [
15822
+ "ps",
15823
+ "-a",
15824
+ "--filter",
15825
+ `volume=${HINDSIGHT_DATA_VOLUME}`,
15826
+ "--format",
15827
+ "{{.Names}}"
15828
+ ]);
15829
+ return out.split(`
15830
+ `).map((l) => l.trim()).filter((n) => n.length > 0);
15831
+ } catch {
15832
+ return [];
15833
+ }
15834
+ }
15835
+ function stopHindsight(exec = (cmd, args) => {
15836
+ execFileSync(cmd, args, { stdio: "pipe" });
15837
+ }, listMounts = () => listHindsightDataVolumeMounts()) {
15838
+ const names = new Set([HINDSIGHT_DEFAULT_WORKER_ID, ...listMounts()]);
15839
+ for (const name of names) {
15840
+ if (name !== HINDSIGHT_DEFAULT_WORKER_ID) {
15841
+ console.error(`stopHindsight: removing hindsight data-volume twin ${name} (not the canonical ${HINDSIGHT_DEFAULT_WORKER_ID})`);
15842
+ }
15843
+ try {
15844
+ exec("docker", ["update", "--restart=no", name]);
15845
+ } catch {}
15846
+ try {
15847
+ exec("docker", ["stop", name]);
15848
+ } catch {}
15849
+ try {
15850
+ exec("docker", ["rm", "-f", name]);
15851
+ } catch {}
15852
+ }
15754
15853
  }
15755
15854
  function pullHindsightImage(imageTag) {
15756
15855
  execFileSync("docker", ["pull", hindsightImageRef(imageTag)], { stdio: "inherit" });
@@ -15808,17 +15907,30 @@ function getHindsightStatus() {
15808
15907
  return null;
15809
15908
  }
15810
15909
  }
15811
- function generateHindsightComposeSnippet(llm, mirrorDir) {
15812
- const { provider: llmProvider, model: llmModel } = resolveHindsightLlm(llm);
15910
+ function generateHindsightComposeSnippet(llm, mirrorDir, litellm) {
15911
+ const { provider: llmProvider, model: llmModel } = resolveHindsightLlm(llm, litellm);
15813
15912
  const perOpLlm = resolveHindsightPerOpLlm(llm);
15913
+ const hostNetwork = hindsightNeedsHostNetwork(llm, litellm);
15914
+ const apiPort = HINDSIGHT_DEFAULT_API_PORT;
15915
+ const internalApiPort = hostNetwork ? apiPort : 8888;
15916
+ const probeUrl = pickHindsightLiteLlmProbeUrl(llm, litellm);
15917
+ const healthPy = probeUrl ? buildLiteLlmAwareHealthPy(internalApiPort, probeUrl) : HINDSIGHT_HEALTHCHECK_PY;
15814
15918
  const environment = [
15815
15919
  ` - HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
15816
15920
  ` - HINDSIGHT_API_LLM_PROVIDER=${llmProvider}`,
15817
15921
  ` - HINDSIGHT_API_LLM_MODEL=${llmModel}`,
15818
15922
  ...perOpLlm.map(([k, v]) => ` - ${k}=${v}`),
15819
15923
  ...llmProvider === "claude-code" ? [` - ANTHROPIC_MODEL=${llmModel}`] : [],
15820
- ` - HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:8888`
15924
+ ...hostNetwork ? [
15925
+ ` - HINDSIGHT_API_PORT=${apiPort}`,
15926
+ ` - HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:${apiPort}`
15927
+ ] : [` - HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:8888`]
15821
15928
  ];
15929
+ if (litellm?.baseUrl?.trim() && litellm.apiKey?.trim()) {
15930
+ const litellmRoot = litellm.baseUrl.replace(/\/+$/, "");
15931
+ const anthropicBaseUrl = isClaudeModel(llmModel) ? `${litellmRoot}/anthropic` : litellmRoot;
15932
+ environment.push(` - ANTHROPIC_BASE_URL=${anthropicBaseUrl}`, ` - ANTHROPIC_CUSTOM_HEADERS=x-litellm-api-key: Bearer ${litellm.apiKey}\\nx-litellm-customer-id: hindsight\\nx-litellm-tags: service:hindsight`);
15933
+ }
15822
15934
  const initService = mirrorDir ? [
15823
15935
  " switchroom-hindsight-creds-init:",
15824
15936
  ` image: ${HINDSIGHT_IMAGE}`,
@@ -15835,6 +15947,13 @@ function generateHindsightComposeSnippet(llm, mirrorDir) {
15835
15947
  " switchroom-hindsight-creds-init:",
15836
15948
  " condition: service_completed_successfully"
15837
15949
  ] : [];
15950
+ const networkOrPorts = hostNetwork ? [
15951
+ " network_mode: host"
15952
+ ] : [
15953
+ " ports:",
15954
+ ` - "127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}:8888"`,
15955
+ ' - "127.0.0.1:19999:9999"'
15956
+ ];
15838
15957
  return [
15839
15958
  "services:",
15840
15959
  ...initService,
@@ -15842,9 +15961,7 @@ function generateHindsightComposeSnippet(llm, mirrorDir) {
15842
15961
  ` image: ${HINDSIGHT_IMAGE}`,
15843
15962
  " container_name: switchroom-hindsight",
15844
15963
  ...dependsOn,
15845
- " ports:",
15846
- ` - "127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}:8888"`,
15847
- ' - "127.0.0.1:19999:9999"',
15964
+ ...networkOrPorts,
15848
15965
  " environment:",
15849
15966
  ...environment,
15850
15967
  ` - HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`,
@@ -15857,12 +15974,13 @@ function generateHindsightComposeSnippet(llm, mirrorDir) {
15857
15974
  ` - HINDSIGHT_API_WORKER_CONSOLIDATION_MAX_SLOTS=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS}`,
15858
15975
  ` - HINDSIGHT_API_CONSOLIDATION_LLM_PARALLELISM=${HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM}`,
15859
15976
  ` - HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`,
15977
+ ` - HINDSIGHT_API_WORKER_ID=${HINDSIGHT_DEFAULT_WORKER_ID}`,
15860
15978
  ` mem_limit: ${HINDSIGHT_DEFAULT_MEM_LIMIT}`,
15861
15979
  ` mem_reservation: ${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
15862
15980
  ` pids_limit: ${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
15863
15981
  ` shm_size: ${HINDSIGHT_DEFAULT_SHM_SIZE}`,
15864
15982
  " healthcheck:",
15865
- ` test: ${JSON.stringify(["CMD", "python3", "-c", HINDSIGHT_HEALTHCHECK_PY])}`,
15983
+ ` test: ${JSON.stringify(["CMD", "python3", "-c", healthPy])}`,
15866
15984
  " interval: 30s",
15867
15985
  " timeout: 5s",
15868
15986
  " retries: 3",
@@ -15946,7 +16064,7 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
15946
16064
  atomicWriteFileSync2(configPath, tail, mode);
15947
16065
  return { added: true, reason: "added" };
15948
16066
  }
15949
- var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 100, HINDSIGHT_DEFAULT_MEM_LIMIT = "8g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, HINDSIGHT_LLM_OPS;
16067
+ var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_WORKER_ID = "switchroom-hindsight", HINDSIGHT_DATA_VOLUME = "switchroom-hindsight-data", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_CREDS_MIRROR_VOLUME, HINDSIGHT_CRED_DIR = "/run/claude-creds", HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 100, HINDSIGHT_DEFAULT_MEM_LIMIT = "8g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, HINDSIGHT_LLM_OPS;
15950
16068
  var init_hindsight = __esm(() => {
15951
16069
  init_model_command();
15952
16070
  HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -16509,7 +16627,7 @@ var HINDSIGHT_SHIM_CLI_PATH = "/usr/local/bin/switchroom", HINDSIGHT_SHIM_AGENT_
16509
16627
  var init_hindsight2 = __esm(() => {
16510
16628
  init_users();
16511
16629
  init_hindsight();
16512
- DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
16630
+ DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) \u2014 only retain the outcome once a task " + "actually completes or a decision is made.";
16513
16631
  PROFILE_MEMORY_DEFAULTS = {
16514
16632
  "health-coach": {
16515
16633
  disposition: { skepticism: 2, literalism: 2, empathy: 5 },
@@ -26989,6 +27107,7 @@ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig) {
26989
27107
  settings.recallTypes = ["world", "experience", "observation"];
26990
27108
  settings.recallSkipTrivial = true;
26991
27109
  settings.directiveCaptureNudge = true;
27110
+ settings.recallTagWeights = { sidechain: 0.8 };
26992
27111
  if (additionalBanks.length > 0) {
26993
27112
  settings.recallAdditionalBanks = [...additionalBanks];
26994
27113
  }
@@ -29763,6 +29882,7 @@ function describeAgents(config, litellmConfirmedAgents) {
29763
29882
  smallFastModel: resolved.litellm?.small_fast_model ?? config.litellm?.small_fast_model ?? "claude-haiku-4-5-20251001"
29764
29883
  },
29765
29884
  model: resolveMainModel(resolved.model),
29885
+ tmuxSupervisor: resolved.experimental?.legacy_pty !== true,
29766
29886
  timezone: resolveTimezone(config, resolved, {
29767
29887
  onUtcFallback: () => {
29768
29888
  console.warn(` \u26a0 timezone: no explicit timezone set and server detection resolved to UTC ` + `for agent "${name}" \u2014 cron schedules and the per-turn time hint will ` + `run in UTC. Add \`switchroom.timezone: "Region/City"\` (e.g. ` + `"Australia/Melbourne") to switchroom.yaml to silence this warning.`);
@@ -30033,11 +30153,25 @@ function generateCompose(opts) {
30033
30153
  lines.push(` SWITCHROOM_CONFIG: /state/config/switchroom.yaml`);
30034
30154
  }
30035
30155
  lines.push(` SWITCHROOM_AUTH_BROKER_STATE_DIR: /state/auth-broker`);
30156
+ let authBrokerNeedsHostGateway = false;
30157
+ {
30158
+ const llBase = config.litellm?.base_url;
30159
+ if (typeof llBase === "string" && llBase.trim()) {
30160
+ const raw = llBase.trim().replace(/\/+$/, "");
30161
+ const bridged = raw.replace(/^http:\/\/127\.0\.0\.1(?=[:/]|$)/i, "http://host.docker.internal").replace(/^http:\/\/localhost(?=[:/]|$)/i, "http://host.docker.internal").replace(/^https:\/\/127\.0\.0\.1(?=[:/]|$)/i, "https://host.docker.internal").replace(/^https:\/\/localhost(?=[:/]|$)/i, "https://host.docker.internal");
30162
+ lines.push(` SWITCHROOM_LITELLM_BASE: ${JSON.stringify(bridged)}`);
30163
+ authBrokerNeedsHostGateway = true;
30164
+ }
30165
+ }
30036
30166
  lines.push(` SWITCHROOM_ACCOUNTS_DIR: /state/accounts`);
30037
30167
  lines.push(` SWITCHROOM_AGENTS_DIR: /state/agents`);
30038
30168
  if (opts.operatorUid !== undefined) {
30039
30169
  lines.push(` SWITCHROOM_AUTH_BROKER_OPERATOR_UID: "${opts.operatorUid}"`);
30040
30170
  }
30171
+ if (authBrokerNeedsHostGateway) {
30172
+ lines.push(` extra_hosts:`);
30173
+ lines.push(` - "host.docker.internal:host-gateway"`);
30174
+ }
30041
30175
  lines.push(` volumes:`);
30042
30176
  for (const a of describeAgents(config, opts.litellmConfirmedAgents)) {
30043
30177
  lines.push(` - auth-broker-${a.name}-sock:/run/switchroom/auth-broker/${a.name}`);
@@ -30211,6 +30345,7 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
30211
30345
  env2.ANTHROPIC_SMALL_FAST_MODEL = a.litellm.smallFastModel;
30212
30346
  }
30213
30347
  env2.SWITCHROOM_VOICE_ENGINE = voiceEngine;
30348
+ env2.SWITCHROOM_TMUX_SUPERVISOR = a.tmuxSupervisor ? "1" : "0";
30214
30349
  for (const [k, v] of Object.entries(a.userEnv)) {
30215
30350
  if (env2[k] === undefined)
30216
30351
  env2[k] = v;
@@ -32692,7 +32827,7 @@ function decodeResponse2(line) {
32692
32827
  }
32693
32828
  return ResponseSchema2.parse(parsed);
32694
32829
  }
32695
- var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
32830
+ var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, GetExternalSpendRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GetExternalSpendDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
32696
32831
  var init_protocol2 = __esm(() => {
32697
32832
  init_zod();
32698
32833
  MAX_FRAME_BYTES2 = 64 * 1024;
@@ -32823,6 +32958,12 @@ var init_protocol2 = __esm(() => {
32823
32958
  key: exports_external.string().min(1).max(512),
32824
32959
  windowMs: exports_external.number().int().positive().max(86400000)
32825
32960
  });
32961
+ GetExternalSpendRequestSchema = exports_external.object({
32962
+ v: exports_external.literal(PROTOCOL_VERSION),
32963
+ op: exports_external.literal("get-external-spend"),
32964
+ id: exports_external.string().min(1),
32965
+ forceLive: exports_external.boolean().optional()
32966
+ });
32826
32967
  RequestSchema2 = exports_external.discriminatedUnion("op", [
32827
32968
  GetCredentialsRequestSchema,
32828
32969
  ListStateRequestSchema,
@@ -32836,7 +32977,8 @@ var init_protocol2 = __esm(() => {
32836
32977
  ListGoogleAccountsRequestSchema,
32837
32978
  ListMicrosoftAccountsRequestSchema,
32838
32979
  ProbeQuotaRequestSchema,
32839
- ClaimNotificationRequestSchema
32980
+ ClaimNotificationRequestSchema,
32981
+ GetExternalSpendRequestSchema
32840
32982
  ]);
32841
32983
  GetCredentialsDataSchema = exports_external.object({
32842
32984
  account: exports_external.string(),
@@ -32847,6 +32989,8 @@ var init_protocol2 = __esm(() => {
32847
32989
  label: exports_external.string(),
32848
32990
  expiresAt: exports_external.number().optional(),
32849
32991
  exhausted: exports_external.boolean(),
32992
+ in_service: exports_external.boolean().optional(),
32993
+ entitlement_blocked: exports_external.boolean().optional(),
32850
32994
  exhausted_until: exports_external.number().optional(),
32851
32995
  throttled_until: exports_external.number().optional(),
32852
32996
  threshold_violations: exports_external.number().int().nonnegative().optional(),
@@ -32903,6 +33047,18 @@ var init_protocol2 = __esm(() => {
32903
33047
  ClaimNotificationDataSchema = exports_external.object({
32904
33048
  granted: exports_external.boolean()
32905
33049
  });
33050
+ GetExternalSpendDataSchema = exports_external.object({
33051
+ available: exports_external.boolean(),
33052
+ day24hUsd: exports_external.number().optional(),
33053
+ day7dUsd: exports_external.number().optional(),
33054
+ top: exports_external.array(exports_external.object({
33055
+ label: exports_external.string(),
33056
+ usd: exports_external.number()
33057
+ })).optional(),
33058
+ capturedAtMs: exports_external.number().int().nonnegative().optional(),
33059
+ served: exports_external.enum(["live", "cache"]).optional(),
33060
+ reason: exports_external.string().optional()
33061
+ });
32906
33062
  GoogleAccountStateSchema = exports_external.object({
32907
33063
  account: exports_external.string(),
32908
33064
  expiresAt: exports_external.number(),
@@ -33073,6 +33229,15 @@ class AuthBrokerClient {
33073
33229
  }
33074
33230
  return parsed;
33075
33231
  }
33232
+ async getExternalSpend(forceLive) {
33233
+ const data = await this.send({
33234
+ v: PROTOCOL_VERSION,
33235
+ id: randomUUID(),
33236
+ op: "get-external-spend",
33237
+ ...forceLive ? { forceLive: true } : {}
33238
+ });
33239
+ return data;
33240
+ }
33076
33241
  async setActive(account) {
33077
33242
  const data = await this.send({
33078
33243
  v: PROTOCOL_VERSION,
@@ -36497,7 +36662,8 @@ async function inspectBankHealth(mcpUrl, bankId, opts) {
36497
36662
  pendingOperations: 0,
36498
36663
  newestDocumentAt: null,
36499
36664
  unextractedDocuments: [],
36500
- mentalModels: []
36665
+ mentalModels: [],
36666
+ activeDirectiveCount: null
36501
36667
  };
36502
36668
  const stats = await getJson(`${base}/v1/default/banks/${bank}/stats`, opts);
36503
36669
  if (!stats.ok)
@@ -36508,6 +36674,8 @@ async function inspectBankHealth(mcpUrl, bankId, opts) {
36508
36674
  const models = await getJson(`${base}/v1/default/banks/${bank}/mental-models`, opts);
36509
36675
  if (!models.ok)
36510
36676
  return { ...empty, reason: models.reason };
36677
+ const directives = await getJson(`${base}/v1/default/banks/${bank}/directives?active_only=true`, opts);
36678
+ const activeDirectiveCount = directives.ok && Array.isArray(directives.data.items) ? directives.data.items.length : null;
36511
36679
  const docItems = docs.data.items ?? [];
36512
36680
  let newestDocumentAt = null;
36513
36681
  const unextracted = [];
@@ -36534,6 +36702,7 @@ async function inspectBankHealth(mcpUrl, bankId, opts) {
36534
36702
  pendingOperations: stats.data.pending_operations ?? 0,
36535
36703
  newestDocumentAt,
36536
36704
  unextractedDocuments: unextracted,
36705
+ activeDirectiveCount,
36537
36706
  mentalModels: (models.data.items ?? []).filter((m) => typeof m?.id === "string" && typeof m?.name === "string").map((m) => {
36538
36707
  const { basedOnCounts, totalSourceFacts, derivedFromModelIds } = summarizeBasedOn(m.reflect_response);
36539
36708
  return {
@@ -41862,6 +42031,29 @@ var init_hindsight_tools = __esm(() => {
41862
42031
 
41863
42032
  // src/cli/doctor-memory.ts
41864
42033
  import { execFileSync as execFileSync18 } from "node:child_process";
42034
+ function classifyDirectiveCount(count, bankLabel) {
42035
+ if (count === null)
42036
+ return null;
42037
+ const name = `${bankLabel} directives`;
42038
+ if (count > MAX_DIRECTIVES) {
42039
+ const truncated = count - MAX_DIRECTIVES;
42040
+ return {
42041
+ name,
42042
+ status: "fail",
42043
+ detail: `${count} active directives \u2014 exceeds MAX_DIRECTIVES=${MAX_DIRECTIVES}, so the ` + `${truncated} lowest-priority directive(s) are SILENTLY truncated from the ` + `<active_directives> recall block and never reach the agent`,
42044
+ fix: "Retire or merge stale directives so the active count is at or below " + `${MAX_DIRECTIVES} (deletes stay operator-approved). The mental-model-curator ` + "skill's directive merge/retire pass is the durable path."
42045
+ };
42046
+ }
42047
+ if (count > DIRECTIVE_WARN_THRESHOLD) {
42048
+ return {
42049
+ name,
42050
+ status: "warn",
42051
+ detail: `${count} active directives \u2014 approaching the MAX_DIRECTIVES=${MAX_DIRECTIVES} cap ` + `above which the recall block silently truncates the overflow`,
42052
+ fix: "Prune or merge low-value directives before the count crosses " + `${MAX_DIRECTIVES}. Review with \`switchroom memory --directives <bank>\`.`
42053
+ };
42054
+ }
42055
+ return null;
42056
+ }
41865
42057
  function classifyShmSize(bytes) {
41866
42058
  const mib = Math.round(bytes / 1024 / 1024);
41867
42059
  if (bytes < MIN_HINDSIGHT_SHM_BYTES) {
@@ -41964,9 +42156,124 @@ function classifyAutohealStatus(logs) {
41964
42156
  }
41965
42157
  return { name: "hindsight autoheal", status: "ok", detail: "no auto-restarts" };
41966
42158
  }
42159
+ function classifyLlmVerification(logs) {
42160
+ const latestFailed = new Map;
42161
+ const failDetail = new Map;
42162
+ let sawAny = false;
42163
+ for (const line of logs.split(`
42164
+ `)) {
42165
+ const fail3 = line.match(/LLM connection verification failed for '([^']+)' config:\s*(.*?)(?:\.\s*Server will start|$)/);
42166
+ if (fail3) {
42167
+ sawAny = true;
42168
+ const cfg = fail3[1];
42169
+ latestFailed.set(cfg, true);
42170
+ failDetail.set(cfg, (fail3[2] ?? "").trim());
42171
+ continue;
42172
+ }
42173
+ if (/connection verified successfully/.test(line) || /LLM verification passed/.test(line)) {
42174
+ sawAny = true;
42175
+ latestFailed.set("default", false);
42176
+ }
42177
+ }
42178
+ if (!sawAny)
42179
+ return null;
42180
+ const failedConfigs = [...latestFailed.entries()].filter(([, failed]) => failed).map(([cfg]) => cfg);
42181
+ if (failedConfigs.length === 0) {
42182
+ return {
42183
+ name: "hindsight LLM verification",
42184
+ status: "ok",
42185
+ detail: "boot LLM connection verified"
42186
+ };
42187
+ }
42188
+ const named = failedConfigs.map((cfg) => {
42189
+ const err = failDetail.get(cfg);
42190
+ return err ? `'${cfg}' (${err})` : `'${cfg}'`;
42191
+ }).join(", ");
42192
+ return {
42193
+ name: "hindsight LLM verification",
42194
+ status: "fail",
42195
+ detail: `boot LLM connection verification FAILED for config(s): ${named} \u2014 ` + `hindsight is serving but every LLM-dependent op (retain/reflect/` + `consolidation extraction) for that config will fail silently until the ` + `provider is reachable`,
42196
+ fix: "Almost always a model-name drift: the pinned `hindsight.llm.*.model` no " + "longer exists in the LiteLLM proxy's model_list (run `switchroom doctor` " + "\u2014 the LiteLLM model-routing check names the exact missing model), or the " + "provider is down. Fix the model reference (or the proxy), then " + "`switchroom memory --restart` so hindsight re-verifies."
42197
+ };
42198
+ }
42199
+ function classifyHindsightDataVolumeMounts(names) {
42200
+ const uniq = [...new Set(names.filter(Boolean))];
42201
+ if (uniq.length === 0) {
42202
+ return {
42203
+ name: "hindsight data-volume exclusive",
42204
+ status: "ok",
42205
+ detail: "no container currently mounts the live data volume"
42206
+ };
42207
+ }
42208
+ const twins = uniq.filter((n) => n !== HINDSIGHT_DEFAULT_WORKER_ID);
42209
+ if (uniq.length === 1 && uniq[0] === HINDSIGHT_DEFAULT_WORKER_ID) {
42210
+ return {
42211
+ name: "hindsight data-volume exclusive",
42212
+ status: "ok",
42213
+ detail: `only ${HINDSIGHT_DEFAULT_WORKER_ID} mounts ${HINDSIGHT_DATA_VOLUME}`
42214
+ };
42215
+ }
42216
+ if (twins.length > 0) {
42217
+ return {
42218
+ name: "hindsight data-volume exclusive",
42219
+ status: "fail",
42220
+ detail: `multiple containers mount ${HINDSIGHT_DATA_VOLUME}: [${uniq.join(", ")}] \u2014 ` + `dual postmasters corrupt the embedded PG checkpoint`,
42221
+ fix: "`switchroom memory --restart` (stopHindsight now disables restart + removes " + "every volume twin), or manually `docker update --restart=no <twin> && " + "docker rm -f <twin>` before starting the live container."
42222
+ };
42223
+ }
42224
+ return {
42225
+ name: "hindsight data-volume exclusive",
42226
+ status: "warn",
42227
+ detail: `unexpected volume mount set: [${uniq.join(", ")}]`
42228
+ };
42229
+ }
42230
+ function classifyHindsightNetworkMode(networkMode, litellmConfigured) {
42231
+ if (!litellmConfigured) {
42232
+ return {
42233
+ name: "hindsight network mode",
42234
+ status: "ok",
42235
+ detail: networkMode ? `${networkMode} (LiteLLM not configured)` : "unknown (LiteLLM not configured)"
42236
+ };
42237
+ }
42238
+ const mode = (networkMode ?? "").trim() || "unknown";
42239
+ if (mode === "host") {
42240
+ return {
42241
+ name: "hindsight network mode",
42242
+ status: "ok",
42243
+ detail: "host \u2014 LiteLLM 127.0.0.1 reachable"
42244
+ };
42245
+ }
42246
+ return {
42247
+ name: "hindsight network mode",
42248
+ status: "fail",
42249
+ detail: `NetworkMode=${mode} while LiteLLM routing is configured \u2014 retains will die with ` + `"Connection error" to 127.0.0.1:4010 while /health stays green`,
42250
+ fix: "`switchroom memory --restart` so startHindsight recreates with --network host. " + "Do not `docker start` a bridge/mis-networked container."
42251
+ };
42252
+ }
42253
+ function classifyLiteLlmReachability(reachable, endpoint) {
42254
+ if (reachable === null)
42255
+ return null;
42256
+ if (reachable) {
42257
+ return {
42258
+ name: "hindsight LiteLLM reachability",
42259
+ status: "ok",
42260
+ detail: `TCP ok to ${endpoint}`
42261
+ };
42262
+ }
42263
+ return {
42264
+ name: "hindsight LiteLLM reachability",
42265
+ status: "fail",
42266
+ detail: `cannot TCP-connect to ${endpoint} \u2014 hindsight LLM ops will fail`,
42267
+ fix: "Start/fix the LiteLLM proxy (host :4010), then `switchroom memory --restart` " + "if hindsight was started while it was down."
42268
+ };
42269
+ }
41967
42270
  function checkHindsightContainerHealth(opts) {
41968
42271
  const name = opts?.containerName ?? "switchroom-hindsight";
41969
- const exec = opts?.exec ?? ((cmd, args) => execFileSync18(cmd, args, { stdio: ["ignore", "pipe", "ignore"], timeout: 8000 }).toString());
42272
+ const exec = opts?.exec ?? ((cmd, args) => execFileSync18(cmd, args, {
42273
+ stdio: ["ignore", "pipe", "ignore"],
42274
+ timeout: 8000,
42275
+ maxBuffer: 16 * 1024 * 1024
42276
+ }).toString());
41970
42277
  const results = [];
41971
42278
  let shmRaw;
41972
42279
  try {
@@ -41978,6 +42285,29 @@ function checkHindsightContainerHealth(opts) {
41978
42285
  if (Number.isFinite(shmBytes) && shmBytes > 0) {
41979
42286
  results.push(classifyShmSize(shmBytes));
41980
42287
  }
42288
+ try {
42289
+ const startedAt = exec("docker", [
42290
+ "inspect",
42291
+ name,
42292
+ "--format",
42293
+ "{{.State.StartedAt}}"
42294
+ ]).trim();
42295
+ const startMs = Date.parse(startedAt);
42296
+ if (Number.isFinite(startMs)) {
42297
+ const untilIso = new Date(startMs + 10 * 60000).toISOString();
42298
+ const bootLogs = exec("docker", [
42299
+ "logs",
42300
+ "--since",
42301
+ startedAt,
42302
+ "--until",
42303
+ untilIso,
42304
+ name
42305
+ ]);
42306
+ const verifyRow = classifyLlmVerification(bootLogs);
42307
+ if (verifyRow)
42308
+ results.push(verifyRow);
42309
+ }
42310
+ } catch {}
41981
42311
  try {
41982
42312
  const logs = exec("docker", ["logs", "--since", "10m", name]);
41983
42313
  results.push(classifyExtractionLogs(logs));
@@ -41986,6 +42316,69 @@ function checkHindsightContainerHealth(opts) {
41986
42316
  const autohealLogs = exec("docker", ["logs", "--since", "1h", "switchroom-hindsight-autoheal"]);
41987
42317
  results.push(classifyAutohealStatus(autohealLogs));
41988
42318
  } catch {}
42319
+ try {
42320
+ const mounts = listHindsightDataVolumeMounts(exec);
42321
+ results.push(classifyHindsightDataVolumeMounts(mounts));
42322
+ } catch {}
42323
+ try {
42324
+ const netMode = exec("docker", [
42325
+ "inspect",
42326
+ name,
42327
+ "--format",
42328
+ "{{.HostConfig.NetworkMode}}"
42329
+ ]).trim();
42330
+ const envBlob = exec("docker", [
42331
+ "inspect",
42332
+ name,
42333
+ "--format",
42334
+ "{{range .Config.Env}}{{println .}}{{end}}"
42335
+ ]);
42336
+ const perOpBasePresent = /HINDSIGHT_API_(RETAIN|REFLECT|CONSOLIDATION)_LLM_BASE_URL=/.test(envBlob);
42337
+ const anthropicBaseMatch = envBlob.match(/(?:^|\n)ANTHROPIC_BASE_URL=(\S+)/);
42338
+ const anthropicLooksLikeLiteLlm = (() => {
42339
+ const raw = anthropicBaseMatch?.[1]?.trim();
42340
+ if (!raw)
42341
+ return false;
42342
+ try {
42343
+ const u = new URL(raw.includes("://") ? raw : `http://${raw}`);
42344
+ const port = u.port || (u.protocol === "https:" ? "443" : "80");
42345
+ const path5 = (u.pathname || "").replace(/\/+$/, "");
42346
+ if (port === "4010")
42347
+ return true;
42348
+ if (/\/anthropic$/i.test(path5))
42349
+ return true;
42350
+ if (/litellm/i.test(u.hostname || ""))
42351
+ return true;
42352
+ return false;
42353
+ } catch {
42354
+ return false;
42355
+ }
42356
+ })();
42357
+ const litellmConfigured = perOpBasePresent || anthropicLooksLikeLiteLlm;
42358
+ results.push(classifyHindsightNetworkMode(netMode, litellmConfigured));
42359
+ if (litellmConfigured) {
42360
+ const m = envBlob.match(/HINDSIGHT_API_RETAIN_LLM_BASE_URL=(\S+)/) ?? envBlob.match(/HINDSIGHT_API_REFLECT_LLM_BASE_URL=(\S+)/) ?? envBlob.match(/HINDSIGHT_API_CONSOLIDATION_LLM_BASE_URL=(\S+)/) ?? (anthropicLooksLikeLiteLlm ? anthropicBaseMatch : null);
42361
+ let endpoint = "127.0.0.1:4010";
42362
+ if (m) {
42363
+ try {
42364
+ const u = new URL(m[1].includes("://") ? m[1] : `http://${m[1]}`);
42365
+ endpoint = `${u.hostname || "127.0.0.1"}:${u.port || "80"}`;
42366
+ } catch {}
42367
+ }
42368
+ const [host, portStr] = endpoint.split(":");
42369
+ const port = Number(portStr) || 80;
42370
+ let reachable = null;
42371
+ try {
42372
+ exec("bash", ["-c", `echo > /dev/tcp/${host}/${port}`]);
42373
+ reachable = true;
42374
+ } catch {
42375
+ reachable = false;
42376
+ }
42377
+ const row = classifyLiteLlmReachability(reachable, endpoint);
42378
+ if (row)
42379
+ results.push(row);
42380
+ }
42381
+ } catch {}
41989
42382
  return results;
41990
42383
  }
41991
42384
  function classifyHindsightHealthProbe(status, port) {
@@ -42073,12 +42466,145 @@ function classifyToolContract(advertised) {
42073
42466
  }
42074
42467
  return results;
42075
42468
  }
42076
- var MIN_HINDSIGHT_SHM_BYTES, CONSOLIDATION_BACKLOG_WARN = 25, CONSOLIDATION_BACKLOG_FAIL = 200;
42469
+ var MIN_HINDSIGHT_SHM_BYTES, MAX_DIRECTIVES = 15, DIRECTIVE_WARN_THRESHOLD = 12, CONSOLIDATION_BACKLOG_WARN = 25, CONSOLIDATION_BACKLOG_FAIL = 200;
42077
42470
  var init_doctor_memory = __esm(() => {
42078
42471
  init_hindsight_tools();
42472
+ init_hindsight();
42079
42473
  MIN_HINDSIGHT_SHM_BYTES = 1024 * 1024 * 1024;
42080
42474
  });
42081
42475
 
42476
+ // src/litellm/model-validation.ts
42477
+ function isExplicitLitellmRoute(model) {
42478
+ return model.includes("/");
42479
+ }
42480
+ function collectReferencedModels(config) {
42481
+ const refs = [];
42482
+ const litellmEnabled = config.litellm?.enabled === true;
42483
+ if (!litellmEnabled)
42484
+ return refs;
42485
+ const llm = config.hindsight?.llm;
42486
+ if (llm) {
42487
+ const push = (model, consumer) => {
42488
+ if (model)
42489
+ refs.push({ model, consumer });
42490
+ };
42491
+ push(llm.model, "hindsight.llm.model (global)");
42492
+ push(llm.retain?.model, "hindsight.llm.retain");
42493
+ push(llm.reflect?.model, "hindsight.llm.reflect");
42494
+ push(llm.consolidation?.model, "hindsight.llm.consolidation");
42495
+ }
42496
+ for (const [name, agent] of Object.entries(config.agents ?? {})) {
42497
+ if (!agent)
42498
+ continue;
42499
+ const agentRoutes = agent.litellm?.enabled !== false;
42500
+ if (!agentRoutes)
42501
+ continue;
42502
+ if (agent.model && isExplicitLitellmRoute(agent.model)) {
42503
+ refs.push({ model: agent.model, consumer: `agents.${name}.model` });
42504
+ }
42505
+ if (agent.fallback_model && isExplicitLitellmRoute(agent.fallback_model)) {
42506
+ refs.push({ model: agent.fallback_model, consumer: `agents.${name}.fallback_model` });
42507
+ }
42508
+ }
42509
+ return refs;
42510
+ }
42511
+ function validateModelReferences(refs, proxyModels) {
42512
+ const missing = new Map;
42513
+ for (const { model, consumer } of refs) {
42514
+ if (proxyModels.has(model))
42515
+ continue;
42516
+ const list = missing.get(model);
42517
+ if (list) {
42518
+ if (!list.includes(consumer))
42519
+ list.push(consumer);
42520
+ } else {
42521
+ missing.set(model, [consumer]);
42522
+ }
42523
+ }
42524
+ return [...missing.entries()].map(([model, consumers]) => ({ model, consumers }));
42525
+ }
42526
+ async function fetchProxyModels(baseUrl, apiKey, fetchFn = fetch, timeoutMs = 8000) {
42527
+ const url = `${baseUrl.replace(/\/+$/, "")}/v1/models`;
42528
+ const controller = new AbortController;
42529
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
42530
+ try {
42531
+ const res = await fetchFn(url, {
42532
+ method: "GET",
42533
+ headers: { Authorization: `Bearer ${apiKey}` },
42534
+ ...{ signal: controller.signal }
42535
+ });
42536
+ if (!res.ok) {
42537
+ return { kind: "unreachable", msg: `proxy returned HTTP ${res.status} for ${url}` };
42538
+ }
42539
+ const body = await res.json();
42540
+ const data = body?.data;
42541
+ if (!Array.isArray(data)) {
42542
+ return { kind: "unreachable", msg: `proxy ${url} response had no model \`data\` array` };
42543
+ }
42544
+ const models = data.map((m) => m?.id).filter((id) => typeof id === "string");
42545
+ return { kind: "ok", models };
42546
+ } catch (err) {
42547
+ return { kind: "unreachable", msg: err.message ?? String(err) };
42548
+ } finally {
42549
+ clearTimeout(timer);
42550
+ }
42551
+ }
42552
+ function classifyModelReferences(missing, refCount) {
42553
+ if (missing.length === 0) {
42554
+ return {
42555
+ name: "litellm model routing",
42556
+ status: "ok",
42557
+ detail: `all ${refCount} referenced model(s) present in the proxy model_list`
42558
+ };
42559
+ }
42560
+ const detail = missing.map((m) => `\`${m.model}\` (referenced by ${m.consumers.join(", ")})`).join("; ");
42561
+ return {
42562
+ name: "litellm model routing",
42563
+ status: "fail",
42564
+ detail: `${missing.length} model reference(s) NOT in the LiteLLM proxy model_list: ` + `${detail} \u2014 every LLM call from that consumer will 400 ("Invalid model ` + `name") until the proxy lists it or the reference is corrected`,
42565
+ fix: "Either add the missing model group back to the LiteLLM proxy config " + "(the operator-maintained `litellm-config.yaml` `model_list`) and redeploy " + "the proxy, or repoint the consumer at a model the proxy DOES route " + "(`switchroom doctor` lists them via the proxy `/v1/models`). For " + "hindsight, then `switchroom memory --restart` so it re-verifies."
42566
+ };
42567
+ }
42568
+ async function runLitellmModelChecks(config, opts) {
42569
+ const refs = collectReferencedModels(config);
42570
+ if (refs.length === 0)
42571
+ return [];
42572
+ const litellm = config.litellm;
42573
+ const baseUrl = litellm?.base_url;
42574
+ const adminKeyRef = litellm?.admin_key;
42575
+ if (!baseUrl || !adminKeyRef) {
42576
+ return [
42577
+ {
42578
+ name: "litellm model routing",
42579
+ status: "warn",
42580
+ detail: `${refs.length} model reference(s) to validate but litellm ` + `${!baseUrl ? "base_url" : "admin_key"} is unresolved \u2014 cannot query ` + `the proxy model_list`
42581
+ }
42582
+ ];
42583
+ }
42584
+ const apiKey = opts.resolveSecret(adminKeyRef);
42585
+ if (!apiKey) {
42586
+ return [
42587
+ {
42588
+ name: "litellm model routing",
42589
+ status: "warn",
42590
+ detail: `could not resolve the litellm admin_key (\`${adminKeyRef}\`) to query ` + `the proxy model_list \u2014 ${refs.length} model reference(s) left unverified`
42591
+ }
42592
+ ];
42593
+ }
42594
+ const probe2 = await fetchProxyModels(baseUrl, apiKey, opts.fetchFn);
42595
+ if (probe2.kind === "unreachable") {
42596
+ return [
42597
+ {
42598
+ name: "litellm model routing",
42599
+ status: "warn",
42600
+ detail: `LiteLLM proxy unreachable (${probe2.msg}) \u2014 ${refs.length} model ` + `reference(s) left unverified; not failing on an environment limitation`
42601
+ }
42602
+ ];
42603
+ }
42604
+ const missing = validateModelReferences(refs, new Set(probe2.models));
42605
+ return [classifyModelReferences(missing, refs.length)];
42606
+ }
42607
+
42082
42608
  // src/cli/doctor-docker.ts
42083
42609
  import { readFileSync as readFileSync56 } from "node:fs";
42084
42610
  import { spawnSync as spawnSync8 } from "node:child_process";
@@ -45264,6 +45790,69 @@ var init_doctor_timezone = __esm(() => {
45264
45790
  init_timezone();
45265
45791
  });
45266
45792
 
45793
+ // src/cli/doctor-fix-session-model.ts
45794
+ import { join as join67 } from "node:path";
45795
+ function describeReconcileChanges(changes) {
45796
+ if (changes.length === 0)
45797
+ return "Reconcile rewrote no other files.";
45798
+ const MAX_LISTED = 6;
45799
+ const listed = changes.slice(0, MAX_LISTED).join(", ");
45800
+ const more = changes.length > MAX_LISTED ? ` (+${changes.length - MAX_LISTED} more)` : "";
45801
+ return `Full per-agent reconcile applied \u2014 rewrote ${changes.length} managed file(s): ` + listed + more;
45802
+ }
45803
+ function fixSessionModelCarrierDrift(config, deps) {
45804
+ const agentsDir = resolveAgentsDir(config);
45805
+ const results = [];
45806
+ for (const name of Object.keys(config.agents ?? {})) {
45807
+ const startShPath = join67(agentsDir, name, "start.sh");
45808
+ const label = `${name}: start.sh /model session carrier (--fix)`;
45809
+ const pre = deps.check(name, startShPath);
45810
+ if (pre.status === "ok") {
45811
+ results.push({
45812
+ name: label,
45813
+ status: "ok",
45814
+ detail: "rev5 carrier already present \u2014 nothing to fix"
45815
+ });
45816
+ continue;
45817
+ }
45818
+ if (pre.status === "skip") {
45819
+ results.push({ ...pre, name: label });
45820
+ continue;
45821
+ }
45822
+ let reconcileChanges;
45823
+ try {
45824
+ reconcileChanges = deps.reconcile(name).changes;
45825
+ } catch (err) {
45826
+ results.push({
45827
+ name: label,
45828
+ status: "fail",
45829
+ detail: `drift detected (${pre.detail ?? "carrier check failed"}) but reconcile failed: ${err.message}`,
45830
+ fix: "Fix the reconcile error, or run `switchroom apply` manually."
45831
+ });
45832
+ continue;
45833
+ }
45834
+ const post = deps.check(name, startShPath);
45835
+ if (post.status === "ok") {
45836
+ results.push({
45837
+ name: label,
45838
+ status: "ok",
45839
+ detail: "healed: start.sh regenerated with the rev5 `.session-model` carrier \u2014 takes effect on the agent's next restart. " + describeReconcileChanges(reconcileChanges)
45840
+ });
45841
+ } else {
45842
+ results.push({
45843
+ name: label,
45844
+ status: "fail",
45845
+ detail: `regenerated start.sh still fails the carrier check: ${post.detail ?? post.status} \u2014 the installed template may be stale`,
45846
+ fix: "Update switchroom (the bundled profiles/_base/start.sh.hbs must carry the rev5 carrier), then re-run `switchroom doctor --fix`."
45847
+ });
45848
+ }
45849
+ }
45850
+ return results;
45851
+ }
45852
+ var init_doctor_fix_session_model = __esm(() => {
45853
+ init_loader();
45854
+ });
45855
+
45267
45856
  // src/cli/doctor.ts
45268
45857
  var exports_doctor = {};
45269
45858
  __export(exports_doctor, {
@@ -45289,6 +45878,7 @@ __export(exports_doctor, {
45289
45878
  checkTelegram: () => checkTelegram,
45290
45879
  checkTcp: () => checkTcp,
45291
45880
  checkStartShStale: () => checkStartShStale,
45881
+ checkStartShSessionModelCarrier: () => checkStartShSessionModelCarrier,
45292
45882
  checkSkillsPrerequisites: () => checkSkillsPrerequisites,
45293
45883
  checkRepoHygiene: () => checkRepoHygiene,
45294
45884
  checkPendingRetainsQueues: () => checkPendingRetainsQueues,
@@ -45322,16 +45912,16 @@ import {
45322
45912
  readdirSync as readdirSync23,
45323
45913
  statSync as statSync38
45324
45914
  } from "node:fs";
45325
- import { dirname as dirname20, join as join67, resolve as resolve39 } from "node:path";
45915
+ import { dirname as dirname20, join as join68, resolve as resolve39 } from "node:path";
45326
45916
  import { createPublicKey, createPrivateKey } from "node:crypto";
45327
45917
  function findInNvm(bin) {
45328
- const nvmRoot = join67(process.env.HOME ?? "", ".nvm", "versions", "node");
45918
+ const nvmRoot = join68(process.env.HOME ?? "", ".nvm", "versions", "node");
45329
45919
  if (!existsSync66(nvmRoot))
45330
45920
  return null;
45331
45921
  try {
45332
45922
  const versions = readdirSync23(nvmRoot).sort().reverse();
45333
45923
  for (const v of versions) {
45334
- const candidate = join67(nvmRoot, v, "bin", bin);
45924
+ const candidate = join68(nvmRoot, v, "bin", bin);
45335
45925
  try {
45336
45926
  const s = statSync38(candidate);
45337
45927
  if (s.isFile() || s.isSymbolicLink()) {
@@ -45358,7 +45948,7 @@ function whichOnPath(bin) {
45358
45948
  for (const dir of pathEnv.split(":")) {
45359
45949
  if (!dir)
45360
45950
  continue;
45361
- const candidate = join67(dir, bin);
45951
+ const candidate = join68(dir, bin);
45362
45952
  if (isX(candidate))
45363
45953
  return candidate;
45364
45954
  }
@@ -45524,7 +46114,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
45524
46114
  if (envBrowsersPath && envBrowsersPath.length > 0) {
45525
46115
  cacheLocations.push(envBrowsersPath);
45526
46116
  }
45527
- cacheLocations.push(join67(homeDir, ".cache", "ms-playwright"));
46117
+ cacheLocations.push(join68(homeDir, ".cache", "ms-playwright"));
45528
46118
  for (const cacheDir of cacheLocations) {
45529
46119
  if (!existsSync66(cacheDir))
45530
46120
  continue;
@@ -45532,10 +46122,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
45532
46122
  const entries = readdirSync23(cacheDir).filter((e) => e.startsWith("chromium"));
45533
46123
  for (const entry of entries) {
45534
46124
  const candidates2 = [
45535
- join67(cacheDir, entry, "chrome-linux64", "chrome"),
45536
- join67(cacheDir, entry, "chrome-linux", "chrome"),
45537
- join67(cacheDir, entry, "chrome-linux64", "headless_shell"),
45538
- join67(cacheDir, entry, "chrome-linux", "headless_shell")
46125
+ join68(cacheDir, entry, "chrome-linux64", "chrome"),
46126
+ join68(cacheDir, entry, "chrome-linux", "chrome"),
46127
+ join68(cacheDir, entry, "chrome-linux64", "headless_shell"),
46128
+ join68(cacheDir, entry, "chrome-linux", "headless_shell")
45539
46129
  ];
45540
46130
  for (const path5 of candidates2) {
45541
46131
  if (existsSync66(path5))
@@ -45667,7 +46257,7 @@ function checkDeployMounts(opts) {
45667
46257
  const home2 = opts?.home ?? process.env.HOME ?? "/root";
45668
46258
  const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
45669
46259
  const results = [];
45670
- const dockerComposePlugin = join67(home2, ".docker", "cli-plugins", "docker-compose");
46260
+ const dockerComposePlugin = join68(home2, ".docker", "cli-plugins", "docker-compose");
45671
46261
  const pluginKind = pathKind(dockerComposePlugin);
45672
46262
  if (pluginKind === "dir") {
45673
46263
  results.push({
@@ -45705,7 +46295,7 @@ function checkDeployMounts(opts) {
45705
46295
  function checkLegacyState() {
45706
46296
  const results = [];
45707
46297
  const h = process.env.HOME ?? "/root";
45708
- const clerkDir = join67(h, LEGACY_STATE_DIR);
46298
+ const clerkDir = join68(h, LEGACY_STATE_DIR);
45709
46299
  const clerkPresent = existsSync66(clerkDir);
45710
46300
  results.push({
45711
46301
  name: "legacy ~/.clerk state",
@@ -45715,7 +46305,7 @@ function checkLegacyState() {
45715
46305
  fix: "Legacy state detected. Run `mv ~/.clerk ~/.switchroom` and rename " + "any top-level `clerk:` key in switchroom.yaml to `switchroom:`. " + "This back-compat shim is REMOVED in v0.13.0 \u2014 no automatic " + "migration exists."
45716
46306
  } : {}
45717
46307
  });
45718
- const legacySock = join67(h, ".switchroom", "vault-broker.sock");
46308
+ const legacySock = join68(h, ".switchroom", "vault-broker.sock");
45719
46309
  let sockStat = null;
45720
46310
  try {
45721
46311
  sockStat = lstatSync9(legacySock);
@@ -45979,6 +46569,9 @@ async function checkBankIngestHealth(config, url, opts) {
45979
46569
  });
45980
46570
  continue;
45981
46571
  }
46572
+ const directiveRow = classifyDirectiveCount(h.activeDirectiveCount, label);
46573
+ if (directiveRow)
46574
+ results.push(directiveRow);
45982
46575
  if (h.totalDocuments === 0) {
45983
46576
  results.push({
45984
46577
  name: label,
@@ -46281,7 +46874,7 @@ async function checkTelegram(config) {
46281
46874
  const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
46282
46875
  if (plugin !== "switchroom")
46283
46876
  continue;
46284
- const envPath = join67(agentsDir, name, "telegram", ".env");
46877
+ const envPath = join68(agentsDir, name, "telegram", ".env");
46285
46878
  const read = tryReadHostFile(envPath);
46286
46879
  if (read.kind === "eacces") {
46287
46880
  results.push({
@@ -46362,9 +46955,56 @@ function checkStartShStale(agentName, startShPath) {
46362
46955
  }
46363
46956
  return { name: label, status: "ok", detail: "supervisor block present" };
46364
46957
  }
46958
+ function checkStartShSessionModelCarrier(agentName, startShPath) {
46959
+ const label = `${agentName}: start.sh /model session carrier`;
46960
+ if (!existsSync66(startShPath)) {
46961
+ return {
46962
+ name: label,
46963
+ status: "warn",
46964
+ detail: `${startShPath} not found`,
46965
+ fix: `Run \`switchroom doctor --fix\` (or \`switchroom apply\`) to scaffold start.sh (rev5 \`.session-model\` carrier).`
46966
+ };
46967
+ }
46968
+ let content;
46969
+ try {
46970
+ content = readFileSync59(startShPath, "utf-8");
46971
+ } catch (err) {
46972
+ return {
46973
+ name: label,
46974
+ status: "skip",
46975
+ detail: `unreadable from host (${err.message}) \u2014 agent-UID-owned; the session-model carrier block can't be checked from the operator UID`,
46976
+ fix: `Verify in-agent: docker exec switchroom-${agentName} sh -c 'grep -E "\\[ -f .*\\.session-model\\" \\]" /state/agent/start.sh && echo ok'`
46977
+ };
46978
+ }
46979
+ const fileTestPaths = [...content.matchAll(/\[\s*-f\s+([^\]]+?)\s*\]/g)].map((m) => m[1].replace(/["']/g, "").trim());
46980
+ const hasBareSessionModelTest = fileTestPaths.some((p) => /(?:^|\/)\.session-model$/.test(p));
46981
+ const hasJsonCarrierParse = /configuredDefaultAtWrite/.test(content) || /session-model-boot-attempts/.test(content);
46982
+ if (hasBareSessionModelTest && hasJsonCarrierParse) {
46983
+ return {
46984
+ name: label,
46985
+ status: "ok",
46986
+ detail: "rev5 `.session-model` consume-once carrier present"
46987
+ };
46988
+ }
46989
+ if (hasBareSessionModelTest && !hasJsonCarrierParse) {
46990
+ return {
46991
+ name: label,
46992
+ status: "fail",
46993
+ detail: "start.sh tests `.session-model` but lacks rev5 JSON apply (`configuredDefaultAtWrite` / boot-attempts) \u2014 /model switches may not stick",
46994
+ fix: "Run `switchroom doctor --fix` (or `switchroom apply`) to regenerate start.sh from profiles/_base/start.sh.hbs (rev5 carrier). Takes effect on the next agent restart (no need to bounce until release)."
46995
+ };
46996
+ }
46997
+ const legacyOnly = /\.session-model-override/.test(content) && !hasBareSessionModelTest;
46998
+ return {
46999
+ name: label,
47000
+ status: "fail",
47001
+ detail: legacyOnly ? "start.sh only reads legacy `.session-model-override`; gateway writes rev5 `.session-model` \u2014 Telegram /model relaunches boot the yaml pin (silent miss)" : "start.sh missing rev5 `.session-model` carrier file test \u2014 Telegram /model cannot apply session overrides across relaunch",
47002
+ fix: "Run `switchroom doctor --fix` (or `switchroom apply`) to regenerate start.sh from the latest template (rev5 `.session-model` carrier). The rewrite is on disk immediately; session switches apply on the next agent restart/release bounce."
47003
+ };
47004
+ }
46365
47005
  function checkLeakedHomeSwitchroom(agentName, agentDir) {
46366
47006
  const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
46367
- const path5 = join67(agentDir, "home", ".switchroom");
47007
+ const path5 = join68(agentDir, "home", ".switchroom");
46368
47008
  let stats;
46369
47009
  try {
46370
47010
  stats = lstatSync9(path5);
@@ -46401,7 +47041,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
46401
47041
  }
46402
47042
  function checkRepoHygiene(repoRoot) {
46403
47043
  const results = [];
46404
- const exportDir = join67(repoRoot, "clerk-export");
47044
+ const exportDir = join68(repoRoot, "clerk-export");
46405
47045
  if (existsSync66(exportDir)) {
46406
47046
  results.push({
46407
47047
  name: "repo hygiene: clerk-export/ on disk (#1072)",
@@ -46410,7 +47050,7 @@ function checkRepoHygiene(repoRoot) {
46410
47050
  fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
46411
47051
  });
46412
47052
  }
46413
- const knownTarball = join67(repoRoot, "clerk-export-with-secrets.tar.gz");
47053
+ const knownTarball = join68(repoRoot, "clerk-export-with-secrets.tar.gz");
46414
47054
  if (existsSync66(knownTarball)) {
46415
47055
  results.push({
46416
47056
  name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
@@ -46428,7 +47068,7 @@ function checkRepoHygiene(repoRoot) {
46428
47068
  results.push({
46429
47069
  name: `repo hygiene: ${name} on disk (#1072)`,
46430
47070
  status: "warn",
46431
- detail: `${join67(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
47071
+ detail: `${join68(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
46432
47072
  fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
46433
47073
  });
46434
47074
  }
@@ -46451,9 +47091,9 @@ function checkRepoHygiene(repoRoot) {
46451
47091
  }
46452
47092
  function isSwitchroomCheckout(dir) {
46453
47093
  try {
46454
- if (!existsSync66(join67(dir, ".git")))
47094
+ if (!existsSync66(join68(dir, ".git")))
46455
47095
  return false;
46456
- const pkgPath = join67(dir, "package.json");
47096
+ const pkgPath = join68(dir, "package.json");
46457
47097
  if (!existsSync66(pkgPath))
46458
47098
  return false;
46459
47099
  const pkg = JSON.parse(readFileSync59(pkgPath, "utf-8"));
@@ -46490,7 +47130,8 @@ function checkAgents(config, configPath) {
46490
47130
  fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
46491
47131
  });
46492
47132
  }
46493
- results.push(checkStartShStale(name, join67(agentDir, "start.sh")));
47133
+ results.push(checkStartShStale(name, join68(agentDir, "start.sh")));
47134
+ results.push(checkStartShSessionModelCarrier(name, join68(agentDir, "start.sh")));
46494
47135
  results.push(checkLeakedHomeSwitchroom(name, agentDir));
46495
47136
  const status = statuses[name];
46496
47137
  const active = status?.active ?? "unknown";
@@ -46567,7 +47208,7 @@ function checkAgents(config, configPath) {
46567
47208
  }
46568
47209
  }
46569
47210
  if (agentConfig.channels?.telegram?.plugin === "switchroom") {
46570
- const mcpJsonPath = join67(agentDir, ".mcp.json");
47211
+ const mcpJsonPath = join68(agentDir, ".mcp.json");
46571
47212
  if (!existsSync66(mcpJsonPath)) {
46572
47213
  results.push({
46573
47214
  name: `${name}: .mcp.json`,
@@ -46869,7 +47510,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
46869
47510
  };
46870
47511
  }
46871
47512
  const credDir = dirname20(envPath);
46872
- const authScript = join67(credDir, "claude-auth.py");
47513
+ const authScript = join68(credDir, "claude-auth.py");
46873
47514
  if (!existsSync66(authScript)) {
46874
47515
  return {
46875
47516
  name: "mff: auth flow",
@@ -47086,7 +47727,7 @@ function runDockerSection(config) {
47086
47727
  });
47087
47728
  }
47088
47729
  function registerDoctorCommand(program3) {
47089
- program3.command("doctor").description("Diagnose Switchroom's setup: deps, vault, memory, agents, MCP wireup").option("--json", "Output as JSON").option("--skill <name>", "Run probes for a specific skill only (e.g. mff)").option("--fast", "Skip in-agent (hostd) liveness probes \u2014 offline/quick; the host-unverifiable rows stay `skip`").action(withConfigError(async (opts) => {
47730
+ program3.command("doctor").description("Diagnose Switchroom's setup: deps, vault, memory, agents, MCP wireup").option("--json", "Output as JSON").option("--skill <name>", "Run probes for a specific skill only (e.g. mff)").option("--fast", "Skip in-agent (hostd) liveness probes \u2014 offline/quick; the host-unverifiable rows stay `skip`").option("--fix", "Auto-remediate rev5 /model `.session-model` carrier drift by running the FULL per-agent reconcile for each drifted agent (start.sh + all switchroom-managed files, same as `switchroom apply` for that agent; staged switchroom.yaml edits land too \u2014 files rewritten are listed in the result row). Takes effect on the agent's next restart").action(withConfigError(async (opts) => {
47090
47731
  try {
47091
47732
  getConfigPath(program3);
47092
47733
  } catch (_e) {
@@ -47155,7 +47796,23 @@ function registerDoctorCommand(program3) {
47155
47796
  return { kind: "unreachable", msg: err.message };
47156
47797
  }
47157
47798
  };
47799
+ const fixSections = opts.fix ? [
47800
+ {
47801
+ title: "Auto-remediation (--fix)",
47802
+ results: fixSessionModelCarrierDrift(config, {
47803
+ check: checkStartShSessionModelCarrier,
47804
+ reconcile: (name) => {
47805
+ const agentConfig = config.agents?.[name];
47806
+ if (!agentConfig)
47807
+ throw new Error(`agent ${name} not in config`);
47808
+ const result = reconcileAgent(name, agentConfig, resolveAgentsDir(config), config.telegram, config, configPath, { preserveClaudeMd: true });
47809
+ return { changes: result.changes };
47810
+ }
47811
+ })
47812
+ }
47813
+ ] : [];
47158
47814
  const sections = [
47815
+ ...fixSections,
47159
47816
  { title: "Dependencies", results: checkDependencies() },
47160
47817
  { title: "Skills Prerequisites", results: checkSkillsPrerequisites() },
47161
47818
  { title: "Manifest Drift", results: await checkManifestDrift() },
@@ -47174,6 +47831,22 @@ function registerDoctorCommand(program3) {
47174
47831
  },
47175
47832
  { title: "Vault access", results: await runSecretAccessChecks(config) },
47176
47833
  { title: "Memory (Hindsight)", results: await checkHindsight(config) },
47834
+ {
47835
+ title: "LiteLLM model routing (#3407)",
47836
+ results: await runLitellmModelChecks(config, {
47837
+ resolveSecret: (ref) => {
47838
+ if (!isVaultReference(ref))
47839
+ return ref;
47840
+ if (!passphrase || !existsSync66(vaultPath))
47841
+ return null;
47842
+ try {
47843
+ return getStringSecret(passphrase, vaultPath, parseVaultReference(ref));
47844
+ } catch {
47845
+ return null;
47846
+ }
47847
+ }
47848
+ })
47849
+ },
47177
47850
  { title: "Telegram", results: await checkTelegram(config) },
47178
47851
  { title: "Agents", results: checkAgents(config, configPath) },
47179
47852
  {
@@ -47286,12 +47959,14 @@ var init_doctor = __esm(() => {
47286
47959
  init_helpers();
47287
47960
  init_lifecycle();
47288
47961
  init_quarantine();
47962
+ init_scaffold();
47289
47963
  init_manager();
47290
47964
  init_accounts();
47291
47965
  init_manifest();
47292
47966
  init_hindsight2();
47293
47967
  init_hindsight();
47294
47968
  init_doctor_memory();
47969
+ init_resolver();
47295
47970
  init_doctor_docker();
47296
47971
  init_doctor_auth_broker();
47297
47972
  init_doctor_hostd();
@@ -47309,6 +47984,7 @@ var init_doctor = __esm(() => {
47309
47984
  init_doctor_agent_smoke();
47310
47985
  init_doctor_vault_broker_durability();
47311
47986
  init_doctor_timezone();
47987
+ init_doctor_fix_session_model();
47312
47988
  DEFAULT_DEPLOY_MOUNTS_DEPS = { pathKind: defaultPathKind };
47313
47989
  MANIFEST_WARN_ONLY = new Set([
47314
47990
  "@playwright/mcp",
@@ -47894,7 +48570,7 @@ var init_fleet_defaults = __esm(() => {
47894
48570
 
47895
48571
  // src/agents/connection-health.ts
47896
48572
  import { mkdirSync as mkdirSync46, writeFileSync as writeFileSync28 } from "node:fs";
47897
- import { join as join83 } from "node:path";
48573
+ import { join as join84 } from "node:path";
47898
48574
  async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
47899
48575
  const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
47900
48576
  if (reqs.length === 0)
@@ -47951,8 +48627,8 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
47951
48627
  return issues;
47952
48628
  }
47953
48629
  function writeConnectionHealthFile(agentDir, health, deps) {
47954
- const dir = join83(agentDir, ".claude");
47955
- const path8 = join83(dir, CONNECTION_HEALTH_FILENAME);
48630
+ const dir = join84(agentDir, ".claude");
48631
+ const path8 = join84(dir, CONNECTION_HEALTH_FILENAME);
47956
48632
  (deps?.mkdir ?? ((p, o) => mkdirSync46(p, o)))(dir, { recursive: true });
47957
48633
  (deps?.writeFile ?? ((p, d) => writeFileSync28(p, d)))(path8, JSON.stringify(health, null, 2) + `
47958
48634
  `);
@@ -47976,9 +48652,9 @@ var init_connection_health = () => {};
47976
48652
 
47977
48653
  // src/cli/update-prompt-hook.ts
47978
48654
  import { existsSync as existsSync83, readFileSync as readFileSync72, writeFileSync as writeFileSync29, chmodSync as chmodSync12, mkdirSync as mkdirSync47 } from "node:fs";
47979
- import { join as join84 } from "node:path";
48655
+ import { join as join85 } from "node:path";
47980
48656
  function containerHookCommand() {
47981
- return join84(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
48657
+ return join85(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
47982
48658
  }
47983
48659
  function updatePromptHookScript() {
47984
48660
  return `#!/bin/bash
@@ -48044,9 +48720,9 @@ exit 0
48044
48720
  `;
48045
48721
  }
48046
48722
  function installUpdatePromptHook(agentDir) {
48047
- const hooksDir = join84(agentDir, ".claude", "hooks");
48723
+ const hooksDir = join85(agentDir, ".claude", "hooks");
48048
48724
  mkdirSync47(hooksDir, { recursive: true });
48049
- const scriptPath = join84(hooksDir, HOOK_FILENAME);
48725
+ const scriptPath = join85(hooksDir, HOOK_FILENAME);
48050
48726
  const desired = updatePromptHookScript();
48051
48727
  let installed = false;
48052
48728
  const existing = existsSync83(scriptPath) ? readFileSync72(scriptPath, "utf-8") : "";
@@ -48059,7 +48735,7 @@ function installUpdatePromptHook(agentDir) {
48059
48735
  chmodSync12(scriptPath, 493);
48060
48736
  } catch {}
48061
48737
  }
48062
- const settingsPath = join84(agentDir, ".claude", "settings.json");
48738
+ const settingsPath = join85(agentDir, ".claude", "settings.json");
48063
48739
  if (!existsSync83(settingsPath)) {
48064
48740
  return { scriptPath, settingsPath, installed };
48065
48741
  }
@@ -48117,6 +48793,10 @@ function installUpdatePromptHook(agentDir) {
48117
48793
  var HOOK_FILENAME = "update-card-on-prompt.sh", CONTAINER_AGENT_DIR = "/state/agent";
48118
48794
  var init_update_prompt_hook = () => {};
48119
48795
 
48796
+ // src/litellm/external-spend.ts
48797
+ var LITELLM_MASTER_KEY_STATE_BASENAME = "litellm-master-key";
48798
+ var init_external_spend = () => {};
48799
+
48120
48800
  // src/cli/install-detect.ts
48121
48801
  import * as fs5 from "node:fs";
48122
48802
  import * as path8 from "node:path";
@@ -48594,6 +49274,7 @@ __export(exports_apply, {
48594
49274
  provisionLiteLLMKeys: () => provisionLiteLLMKeys,
48595
49275
  probeVaultProvisioning: () => probeVaultProvisioning,
48596
49276
  parseComposeServiceNames: () => parseComposeServiceNames,
49277
+ materializeLitellmMasterKeyForBroker: () => materializeLitellmMasterKeyForBroker,
48597
49278
  isInAgentContainer: () => isInAgentContainer,
48598
49279
  inspectVaultBindMountDir: () => inspectVaultBindMountDir,
48599
49280
  formatScaffoldFailureResolution: () => formatScaffoldFailureResolution,
@@ -48606,11 +49287,11 @@ __export(exports_apply, {
48606
49287
  DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
48607
49288
  COMPOSE_PROJECT: () => COMPOSE_PROJECT2
48608
49289
  });
48609
- import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync86, mkdirSync as mkdirSync49, readFileSync as readFileSync74, readdirSync as readdirSync30, renameSync as renameSync20, statSync as statSync47, writeFileSync as writeFileSync31 } from "node:fs";
49290
+ import { accessSync as accessSync3, chmodSync as chmodSync14, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync86, mkdirSync as mkdirSync49, readFileSync as readFileSync74, readdirSync as readdirSync30, renameSync as renameSync20, statSync as statSync47, writeFileSync as writeFileSync31 } from "node:fs";
48610
49291
  import { mkdir as mkdir2 } from "node:fs/promises";
48611
49292
  import { spawnSync as childSpawnSync } from "node:child_process";
48612
49293
  import readline from "node:readline";
48613
- import { dirname as dirname30, join as join86, resolve as resolve52 } from "node:path";
49294
+ import { dirname as dirname30, join as join87, resolve as resolve52 } from "node:path";
48614
49295
  import { homedir as homedir49 } from "node:os";
48615
49296
  import { execFileSync as execFileSync27 } from "node:child_process";
48616
49297
  function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
@@ -48622,13 +49303,28 @@ async function resolveOperatorVaultPassphrase(home2) {
48622
49303
  return envPass;
48623
49304
  try {
48624
49305
  const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
48625
- const blobPath = join86(home2, ".switchroom", "vault-auto-unlock");
49306
+ const blobPath = join87(home2, ".switchroom", "vault-auto-unlock");
48626
49307
  const pass = readAutoUnlockFile2(blobPath);
48627
49308
  return pass && pass.length > 0 ? pass : null;
48628
49309
  } catch {
48629
49310
  return null;
48630
49311
  }
48631
49312
  }
49313
+ function materializeLitellmMasterKeyForBroker(masterKey, home2 = process.env.HOME ?? "/root") {
49314
+ try {
49315
+ const stateDir = join87(home2, ".switchroom", "state", "auth-broker");
49316
+ mkdirSync49(stateDir, { recursive: true, mode: 448 });
49317
+ const path9 = join87(stateDir, LITELLM_MASTER_KEY_STATE_BASENAME);
49318
+ writeFileSync31(path9, masterKey.trim() + `
49319
+ `, { mode: 384 });
49320
+ try {
49321
+ chmodSync14(path9, 384);
49322
+ } catch {}
49323
+ return { ok: true, path: path9 };
49324
+ } catch (err) {
49325
+ return { ok: false, error: err.message };
49326
+ }
49327
+ }
48632
49328
  async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ctx) {
48633
49329
  const { writeOut, failures } = ctx;
48634
49330
  const optedIn = [];
@@ -48644,6 +49340,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
48644
49340
  const needsHindsight = config.litellm?.enabled === true && config.memory?.backend === "hindsight";
48645
49341
  if (optedIn.length === 0 && !needsHindsight)
48646
49342
  return;
49343
+ let brokerKeyMaterialized = false;
48647
49344
  const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2, validateKey: validateKey2, bindKeyToTeam: bindKeyToTeam2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
48648
49345
  Promise.resolve().then(() => (init_client(), exports_client)),
48649
49346
  Promise.resolve().then(() => (init_provision(), exports_provision)),
@@ -48753,6 +49450,14 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
48753
49450
  masterKey = resolved.entry.value;
48754
49451
  }
48755
49452
  }
49453
+ if (masterKey && !brokerKeyMaterialized) {
49454
+ const mat = materializeLitellmMasterKeyForBroker(masterKey, ctx.home ?? homedir49());
49455
+ brokerKeyMaterialized = true;
49456
+ if (!mat.ok) {
49457
+ ctx.writeErr(source_default.yellow(` ! litellm: could not materialize master key for auth-broker external-spend (${mat.error})
49458
+ `));
49459
+ }
49460
+ }
48756
49461
  if (existing.kind === "ok") {
48757
49462
  const storedKey = existing.entry.kind === "string" ? existing.entry.value : null;
48758
49463
  let driftReprovision = false;
@@ -48935,7 +49640,7 @@ function resolveVaultBindMountDir(homeDir, ctx) {
48935
49640
  if (isCustomPath && ctx.customVaultPath) {
48936
49641
  return dirname30(ctx.customVaultPath);
48937
49642
  }
48938
- return join86(homeDir, ".switchroom", "vault");
49643
+ return join87(homeDir, ".switchroom", "vault");
48939
49644
  }
48940
49645
  function inspectVaultBindMountDir(vaultDir) {
48941
49646
  if (!existsSync86(vaultDir))
@@ -48966,41 +49671,41 @@ function hasVaultRefs(value) {
48966
49671
  async function ensureHostMountSources(config) {
48967
49672
  const home2 = resolveHostHomeForCompose();
48968
49673
  const dirs = [
48969
- join86(home2, ".switchroom", "approvals"),
48970
- join86(home2, ".switchroom", "scheduler"),
48971
- join86(home2, ".switchroom", "logs"),
48972
- join86(home2, ".switchroom", "compose"),
48973
- join86(home2, ".switchroom", "broker-operator")
49674
+ join87(home2, ".switchroom", "approvals"),
49675
+ join87(home2, ".switchroom", "scheduler"),
49676
+ join87(home2, ".switchroom", "logs"),
49677
+ join87(home2, ".switchroom", "compose"),
49678
+ join87(home2, ".switchroom", "broker-operator")
48974
49679
  ];
48975
49680
  for (const name of Object.keys(config.agents)) {
48976
- dirs.push(join86(home2, ".switchroom", "agents", name));
48977
- dirs.push(join86(home2, ".switchroom", "logs", name));
48978
- dirs.push(join86(home2, ".claude", "projects", name));
48979
- dirs.push(join86(home2, ".switchroom", "audit", name));
48980
- if (existsSync86(join86(home2, ".switchroom-config"))) {
48981
- dirs.push(join86(home2, ".switchroom-config", "agents", name, "personal-skills"));
49681
+ dirs.push(join87(home2, ".switchroom", "agents", name));
49682
+ dirs.push(join87(home2, ".switchroom", "logs", name));
49683
+ dirs.push(join87(home2, ".claude", "projects", name));
49684
+ dirs.push(join87(home2, ".switchroom", "audit", name));
49685
+ if (existsSync86(join87(home2, ".switchroom-config"))) {
49686
+ dirs.push(join87(home2, ".switchroom-config", "agents", name, "personal-skills"));
48982
49687
  }
48983
49688
  }
48984
49689
  for (const dir of dirs) {
48985
49690
  await mkdir2(dir, { recursive: true });
48986
49691
  }
48987
- const autoUnlockPath = join86(home2, ".switchroom", "vault-auto-unlock");
49692
+ const autoUnlockPath = join87(home2, ".switchroom", "vault-auto-unlock");
48988
49693
  if (!existsSync86(autoUnlockPath)) {
48989
49694
  writeFileSync31(autoUnlockPath, "", { mode: 384 });
48990
49695
  }
48991
- const auditLogPath = join86(home2, ".switchroom", "vault-audit.log");
49696
+ const auditLogPath = join87(home2, ".switchroom", "vault-audit.log");
48992
49697
  if (!existsSync86(auditLogPath)) {
48993
49698
  writeFileSync31(auditLogPath, "", { mode: 420 });
48994
49699
  }
48995
49700
  const grantsDbDir = getGrantsDbDir(home2);
48996
49701
  mkdirSync49(grantsDbDir, { recursive: true, mode: 448 });
48997
49702
  migrateLegacyGrantsDbLocation(getGrantsDbPath(home2));
48998
- const hostdAuditLogPath = join86(home2, ".switchroom", "host-control-audit.log");
49703
+ const hostdAuditLogPath = join87(home2, ".switchroom", "host-control-audit.log");
48999
49704
  if (!existsSync86(hostdAuditLogPath)) {
49000
49705
  writeFileSync31(hostdAuditLogPath, "", { mode: 420 });
49001
49706
  }
49002
49707
  for (const name of Object.keys(config.agents)) {
49003
- const tokenPath = join86(home2, ".switchroom", "agents", name, ".vault-token");
49708
+ const tokenPath = join87(home2, ".switchroom", "agents", name, ".vault-token");
49004
49709
  if (!existsSync86(tokenPath)) {
49005
49710
  writeFileSync31(tokenPath, "", { mode: 384 });
49006
49711
  }
@@ -49009,15 +49714,15 @@ async function ensureHostMountSources(config) {
49009
49714
  chownSync8(tokenPath, uid, uid);
49010
49715
  } catch {}
49011
49716
  }
49012
- const fleetDir = join86(home2, ".switchroom", "fleet");
49717
+ const fleetDir = join87(home2, ".switchroom", "fleet");
49013
49718
  await mkdir2(fleetDir, { recursive: true });
49014
- const invariantsPath = join86(fleetDir, "switchroom-invariants.md");
49719
+ const invariantsPath = join87(fleetDir, "switchroom-invariants.md");
49015
49720
  const invariantsCanonical = renderFleetInvariants();
49016
49721
  const invariantsCurrent = existsSync86(invariantsPath) ? readFileSync74(invariantsPath, "utf-8") : null;
49017
49722
  if (invariantsCurrent !== invariantsCanonical) {
49018
49723
  writeFileSync31(invariantsPath, invariantsCanonical, { mode: 420 });
49019
49724
  }
49020
- const fleetClaudePath = join86(fleetDir, "CLAUDE.md");
49725
+ const fleetClaudePath = join87(fleetDir, "CLAUDE.md");
49021
49726
  if (!existsSync86(fleetClaudePath)) {
49022
49727
  writeFileSync31(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
49023
49728
  mode: 420
@@ -49103,8 +49808,8 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
49103
49808
  }
49104
49809
  function writeInstallTypeCache(homeDir = homedir49()) {
49105
49810
  const ctx = detectInstallType();
49106
- const dir = join86(homeDir, ".switchroom");
49107
- const out = join86(dir, "install-type.json");
49811
+ const dir = join87(homeDir, ".switchroom");
49812
+ const out = join87(dir, "install-type.json");
49108
49813
  const tmp = `${out}.tmp`;
49109
49814
  mkdirSync49(dir, { recursive: true });
49110
49815
  const payload = {
@@ -49171,17 +49876,17 @@ Applying switchroom config...
49171
49876
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
49172
49877
  `));
49173
49878
  try {
49174
- installUpdatePromptHook(join86(agentsDir, name));
49879
+ installUpdatePromptHook(join87(agentsDir, name));
49175
49880
  } catch (hookErr) {
49176
49881
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
49177
49882
  `));
49178
49883
  }
49179
- await refreshAgentConnectionHealth(config, name, join86(agentsDir, name), {
49884
+ await refreshAgentConnectionHealth(config, name, join87(agentsDir, name), {
49180
49885
  vaultAclReader: connHealthVaultAclReader
49181
49886
  });
49182
49887
  try {
49183
49888
  const uid = allocateAgentUid(name);
49184
- alignAgentUid(name, join86(agentsDir, name), uid, {
49889
+ alignAgentUid(name, join87(agentsDir, name), uid, {
49185
49890
  confirm: !options.nonInteractive,
49186
49891
  writeOut
49187
49892
  });
@@ -49226,7 +49931,7 @@ Applying switchroom config...
49226
49931
  for (const name of agentNames) {
49227
49932
  try {
49228
49933
  const uid = allocateAgentUid(name);
49229
- alignAgentUid(name, join86(agentsDir, name), uid, {
49934
+ alignAgentUid(name, join87(agentsDir, name), uid, {
49230
49935
  confirm: !options.nonInteractive,
49231
49936
  writeOut
49232
49937
  });
@@ -49586,7 +50291,7 @@ function findUnwritableAgentDirs(config, opts) {
49586
50291
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
49587
50292
  const unwritable = [];
49588
50293
  for (const name of targets) {
49589
- const startSh = join86(agentsDir, name, "start.sh");
50294
+ const startSh = join87(agentsDir, name, "start.sh");
49590
50295
  if (!existsSync86(startSh))
49591
50296
  continue;
49592
50297
  try {
@@ -49782,6 +50487,7 @@ var init_apply = __esm(() => {
49782
50487
  init_connection_health();
49783
50488
  init_update_prompt_hook();
49784
50489
  init_compose();
50490
+ init_external_spend();
49785
50491
  init_write_compose();
49786
50492
  init_profiles();
49787
50493
  init_install_detect();
@@ -49791,7 +50497,7 @@ var init_apply = __esm(() => {
49791
50497
  switchroom: switchroom_default,
49792
50498
  minimal: minimal_default
49793
50499
  };
49794
- DEFAULT_COMPOSE_PATH2 = join86(homedir49(), ".switchroom", "compose", "docker-compose.yml");
50500
+ DEFAULT_COMPOSE_PATH2 = join87(homedir49(), ".switchroom", "compose", "docker-compose.yml");
49795
50501
  IN_AGENT_CONTAINER_APPLY_MSG = "`switchroom apply`'s full per-agent scaffold cannot run from inside an " + "agent container \u2014 this is a host/hostd operation by construction " + "(no vault at the container HOME, and no `docker compose` v2 plugin here).\nTo roll the fleet to a new version, drive the hostd rollout (`mcp__hostd__rollout`): it runs a `--compose-only` apply plus a per-agent restart-reconcile, and each agent refreshes its own templates " + `on restart \u2014 the roll completes without any agent running a full apply.
49796
50502
  ` + "A full host-side `sudo switchroom apply` is only needed for structural changes (compose regeneration / new-agent scaffolding), and is run by the operator on the host, never from inside an agent.";
49797
50503
  SELF_ELEVATE_PRESERVED_ENV = [
@@ -64018,6 +64724,12 @@ var init_mapping = __esm(() => {
64018
64724
  severity: 1,
64019
64725
  job_spec: "feel-like-a-colleague",
64020
64726
  signature: "represent:obligation-escalation"
64727
+ },
64728
+ "litellm-header-passthrough-misconfig": {
64729
+ failure_mode: "constraint-violation",
64730
+ severity: 3,
64731
+ job_spec: "keep-my-subscription-honest",
64732
+ signature: "litellm-header-passthrough:oauth-leak-scope"
64021
64733
  }
64022
64734
  };
64023
64735
  ALL_JOB_SPECS = [
@@ -64208,6 +64920,122 @@ var init_test_agents = __esm(() => {
64208
64920
  ];
64209
64921
  });
64210
64922
 
64923
+ // src/litellm/header-passthrough-guard.ts
64924
+ function isClaudeAllowlistedGroup(name) {
64925
+ if (name.endsWith("-openrouter"))
64926
+ return false;
64927
+ return name.startsWith("claude-") || name === "sonnet" || name === "fable";
64928
+ }
64929
+ function flagTruthy(v) {
64930
+ return v === true || v === "true";
64931
+ }
64932
+ function* iterGroups(mgs) {
64933
+ if (!mgs || typeof mgs !== "object")
64934
+ return;
64935
+ if (Array.isArray(mgs)) {
64936
+ for (const entry of mgs) {
64937
+ if (!entry || typeof entry !== "object")
64938
+ continue;
64939
+ const e = entry;
64940
+ const name = typeof e.model_group === "string" && e.model_group || typeof e.group_name === "string" && e.group_name || typeof e.model_name === "string" && e.model_name || null;
64941
+ if (name)
64942
+ yield [name, e];
64943
+ }
64944
+ return;
64945
+ }
64946
+ for (const [name, settings] of Object.entries(mgs)) {
64947
+ if (settings && typeof settings === "object") {
64948
+ yield [name, settings];
64949
+ }
64950
+ }
64951
+ }
64952
+ function detectHeaderMisconfig(parsed) {
64953
+ const violations = [];
64954
+ if (!parsed || typeof parsed !== "object")
64955
+ return violations;
64956
+ const root = parsed;
64957
+ const ls = root.litellm_settings;
64958
+ if (ls && typeof ls === "object" && flagTruthy(ls[FORWARD_HEADERS_FLAG])) {
64959
+ violations.push({
64960
+ scope: "global",
64961
+ detail: `${FORWARD_HEADERS_FLAG}: true under litellm_settings \u2014 the GLOBAL ` + `master switch forwards the subscription OAuth Authorization header ` + `to EVERY upstream (OpenRouter/OpenAI included). Scope it to Claude ` + `groups only.`
64962
+ });
64963
+ }
64964
+ for (const [group, settings] of iterGroups(root.model_group_settings)) {
64965
+ if (flagTruthy(settings[FORWARD_HEADERS_FLAG]) && !isClaudeAllowlistedGroup(group)) {
64966
+ violations.push({
64967
+ scope: "model_group",
64968
+ group,
64969
+ detail: `${FORWARD_HEADERS_FLAG}: true on non-Claude group '${group}' \u2014 ` + `forwards the subscription OAuth Authorization header to a ` + `non-subscription upstream. Remove it from this group.`
64970
+ });
64971
+ }
64972
+ }
64973
+ return violations;
64974
+ }
64975
+ function parseLitellmConfig(text) {
64976
+ try {
64977
+ return import_yaml26.parse(text);
64978
+ } catch {
64979
+ return null;
64980
+ }
64981
+ }
64982
+ var import_yaml26, FORWARD_HEADERS_FLAG = "forward_client_headers_to_llm_api", DEFAULT_LITELLM_CONFIG_PATH = "/data/coolify/services/vhz4jc1tzvk6gdql8jueiwq4/litellm-config.yaml";
64983
+ var init_header_passthrough_guard = __esm(() => {
64984
+ import_yaml26 = __toESM(require_dist(), 1);
64985
+ });
64986
+
64987
+ // src/fleet-health/litellm-config-sensor.ts
64988
+ import { readFileSync as readFileSync85, existsSync as existsSync98 } from "node:fs";
64989
+ function resolveLitellmConfigPath(explicit) {
64990
+ return explicit ?? process.env.LITELLM_CONFIG_PATH ?? DEFAULT_LITELLM_CONFIG_PATH;
64991
+ }
64992
+ function scanLitellmConfig(opts = {}) {
64993
+ const path9 = resolveLitellmConfigPath(opts.path);
64994
+ const exists = opts.existsFn ?? existsSync98;
64995
+ const read = opts.readFn ?? ((p) => readFileSync85(p, "utf-8"));
64996
+ const log = opts.log ?? (() => {});
64997
+ const nowIso = opts.nowIso ?? new Date().toISOString();
64998
+ if (!exists(path9)) {
64999
+ log(`fleet-health: litellm-config sensor SKIPPED \u2014 config file absent at ${path9} ` + `(set LITELLM_CONFIG_PATH if it moved; hermetic CI/dev expected to skip)`);
65000
+ return { status: "skipped", path: path9, findings: [] };
65001
+ }
65002
+ let text;
65003
+ try {
65004
+ text = read(path9);
65005
+ } catch (e) {
65006
+ log(`fleet-health: litellm-config sensor SKIPPED \u2014 ${path9} unreadable: ${String(e)}`);
65007
+ return { status: "skipped", path: path9, findings: [] };
65008
+ }
65009
+ const parsed = parseLitellmConfig(text);
65010
+ if (parsed == null) {
65011
+ log(`fleet-health: litellm-config sensor SKIPPED \u2014 ${path9} unparseable YAML`);
65012
+ return { status: "skipped", path: path9, findings: [] };
65013
+ }
65014
+ const violations = detectHeaderMisconfig(parsed);
65015
+ if (violations.length === 0) {
65016
+ log(`fleet-health: litellm-config sensor OK \u2014 header passthrough correctly scoped (${path9})`);
65017
+ return { status: "ok", path: path9, findings: [] };
65018
+ }
65019
+ const findings = violations.map((v, i) => {
65020
+ const where = v.scope === "global" ? "litellm_settings (global)" : `group '${v.group}'`;
65021
+ return {
65022
+ signal: "litellm-header-passthrough-misconfig",
65023
+ agent: LITELLM_PROXY_PSEUDO_AGENT,
65024
+ turn_id: `litellm-config:${v.scope}:${v.group ?? "global"}`,
65025
+ log_pointer: `${path9}: ${where} \u2014 ${v.detail}`,
65026
+ ts: nowIso
65027
+ };
65028
+ });
65029
+ for (const f of findings) {
65030
+ log(`fleet-health: litellm-config sensor VIOLATION \u2014 ${f.log_pointer}`);
65031
+ }
65032
+ return { status: "violation", path: path9, findings };
65033
+ }
65034
+ var LITELLM_PROXY_PSEUDO_AGENT = "litellm-proxy";
65035
+ var init_litellm_config_sensor = __esm(() => {
65036
+ init_header_passthrough_guard();
65037
+ });
65038
+
64211
65039
  // src/fleet-health/scan.ts
64212
65040
  var exports_scan = {};
64213
65041
  __export(exports_scan, {
@@ -64219,9 +65047,9 @@ __export(exports_scan, {
64219
65047
  ledgerPathForBase: () => ledgerPathForBase
64220
65048
  });
64221
65049
  import {
64222
- readFileSync as readFileSync85,
65050
+ readFileSync as readFileSync86,
64223
65051
  readdirSync as readdirSync38,
64224
- existsSync as existsSync98,
65052
+ existsSync as existsSync99,
64225
65053
  mkdirSync as mkdirSync56,
64226
65054
  writeFileSync as writeFileSync37
64227
65055
  } from "node:fs";
@@ -64232,7 +65060,7 @@ function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.en
64232
65060
  }
64233
65061
  function listAgents(base) {
64234
65062
  const dir = resolve57(base, "agents");
64235
- if (!existsSync98(dir))
65063
+ if (!existsSync99(dir))
64236
65064
  return [];
64237
65065
  try {
64238
65066
  return readdirSync38(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
@@ -64261,16 +65089,16 @@ function runScan(opts = {}) {
64261
65089
  let gwText = "";
64262
65090
  let sawArtifact = false;
64263
65091
  try {
64264
- if (existsSync98(turnsPath)) {
64265
- turnsText = readFileSync85(turnsPath, "utf-8");
65092
+ if (existsSync99(turnsPath)) {
65093
+ turnsText = readFileSync86(turnsPath, "utf-8");
64266
65094
  sawArtifact = true;
64267
65095
  }
64268
65096
  } catch (e) {
64269
65097
  log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
64270
65098
  }
64271
65099
  try {
64272
- if (existsSync98(gwPath)) {
64273
- gwText = readFileSync85(gwPath, "utf-8");
65100
+ if (existsSync99(gwPath)) {
65101
+ gwText = readFileSync86(gwPath, "utf-8");
64274
65102
  sawArtifact = true;
64275
65103
  }
64276
65104
  } catch (e) {
@@ -64292,6 +65120,8 @@ function runScan(opts = {}) {
64292
65120
  skipped.push(agent);
64293
65121
  }
64294
65122
  }
65123
+ const litellm = scanLitellmConfig({ path: opts.litellmConfigPath, log });
65124
+ findings.push(...litellm.findings);
64295
65125
  const prior = readLedgerIfPresent(base);
64296
65126
  const ledger = buildLedger(findings, {
64297
65127
  ownerAgent: opts.ownerAgent,
@@ -64310,9 +65140,9 @@ function runScan(opts = {}) {
64310
65140
  function readLedgerIfPresent(base) {
64311
65141
  const path9 = ledgerPathForBase(base);
64312
65142
  try {
64313
- if (!existsSync98(path9))
65143
+ if (!existsSync99(path9))
64314
65144
  return null;
64315
- return JSON.parse(readFileSync85(path9, "utf-8"));
65145
+ return JSON.parse(readFileSync86(path9, "utf-8"));
64316
65146
  } catch {
64317
65147
  return null;
64318
65148
  }
@@ -64332,6 +65162,7 @@ var init_scan = __esm(() => {
64332
65162
  init_detect();
64333
65163
  init_ledger();
64334
65164
  init_test_agents();
65165
+ init_litellm_config_sensor();
64335
65166
  });
64336
65167
 
64337
65168
  // src/cli/index.ts
@@ -70086,6 +70917,10 @@ function isThin(d) {
70086
70917
  }
70087
70918
  function classifyState(args) {
70088
70919
  const { exhausted, exhaustedUntil, window, now } = args;
70920
+ if (args.entitlementBlocked === true)
70921
+ return "org-disabled";
70922
+ if (args.inService === false)
70923
+ return "retired";
70089
70924
  if (window.probeThin)
70090
70925
  return "unprobed";
70091
70926
  const norm = refillNormalizedUtils({
@@ -70130,17 +70965,30 @@ function buildScheduleRows(state, probe, now) {
70130
70965
  const exhaustedUntil = typeof a.exhausted_until === "number" ? new Date(a.exhausted_until) : null;
70131
70966
  const exhausted = a.exhausted && (exhaustedUntil?.getTime() ?? 0) > now.getTime();
70132
70967
  const rank = state.fallback_order.indexOf(a.label);
70968
+ const isActive = a.label === state.active;
70969
+ const inService = isActive ? true : a.in_service !== false;
70970
+ const entitlementBlocked = isActive ? false : a.entitlement_blocked === true;
70133
70971
  return {
70134
70972
  label: a.label,
70135
- isActive: a.label === state.active,
70973
+ isActive,
70136
70974
  fallbackRank: rank === -1 ? null : rank + 1,
70975
+ inService,
70976
+ entitlementBlocked,
70137
70977
  window,
70138
70978
  exhausted,
70139
70979
  exhaustedUntil,
70140
- state: classifyState({ exhausted, exhaustedUntil, window, now })
70980
+ state: classifyState({
70981
+ exhausted,
70982
+ exhaustedUntil,
70983
+ window,
70984
+ now,
70985
+ inService,
70986
+ entitlementBlocked
70987
+ })
70141
70988
  };
70142
70989
  });
70143
- const key = (r) => r.isActive ? -1 : r.fallbackRank ?? 9999;
70990
+ const outOfService = (r) => !r.inService || r.entitlementBlocked;
70991
+ const key = (r) => outOfService(r) ? 1e5 : r.isActive ? -1 : r.fallbackRank ?? 9999;
70144
70992
  return rows.sort((a, b) => key(a) - key(b) || a.label.localeCompare(b.label));
70145
70993
  }
70146
70994
  var EM_DASH = "\u2014";
@@ -70193,6 +71041,10 @@ function formatWeeklyCell(w, now, tz) {
70193
71041
  return `${pct} \u00b7 ${formatResetDay(w.weeklyResetAt, tz)} (${rel})`;
70194
71042
  }
70195
71043
  function poolLabel(row) {
71044
+ if (row.entitlementBlocked)
71045
+ return "disabled";
71046
+ if (!row.inService)
71047
+ return "retired";
70196
71048
  if (row.isActive)
70197
71049
  return row.fallbackRank ? `active #${row.fallbackRank}` : "active";
70198
71050
  if (row.fallbackRank)
@@ -70204,6 +71056,10 @@ function formatStateCell(row, now) {
70204
71056
  const rel = horizon ? ` \u00b7 ${formatDuration(horizon.getTime() - now.getTime())}` : "";
70205
71057
  const overageNote = row.window.overageServeBlocking && row.window.overageReason ? ` (overage off: ${row.window.overageReason})` : row.window.overageServeBlocking ? " (overage off)" : "";
70206
71058
  switch (row.state) {
71059
+ case "org-disabled":
71060
+ return "DISABLED (org)";
71061
+ case "retired":
71062
+ return "retired";
70207
71063
  case "quota-exhausted":
70208
71064
  return `quota-exhausted${rel}`;
70209
71065
  case "healthy":
@@ -70225,10 +71081,16 @@ function pad3(s, n) {
70225
71081
  return s.padEnd(n);
70226
71082
  }
70227
71083
  function glyphFor(row, color) {
70228
- const g = row.isActive ? "\u25cf" : row.exhausted ? "!" : row.state === "unprobed" ? "\u00b7" : "\u2713";
71084
+ const g = row.isActive ? "\u25cf" : row.state === "org-disabled" ? "\u2298" : row.state === "retired" ? "\u2205" : row.exhausted ? "!" : row.state === "unprobed" ? "\u00b7" : "\u2713";
70229
71085
  if (!color)
70230
71086
  return g;
70231
- return row.isActive ? source_default.green(g) : row.exhausted ? source_default.red(g) : source_default.gray(g);
71087
+ if (row.isActive)
71088
+ return source_default.green(g);
71089
+ if (row.state === "org-disabled")
71090
+ return source_default.red(g);
71091
+ if (row.state === "retired")
71092
+ return source_default.gray(g);
71093
+ return row.exhausted ? source_default.red(g) : source_default.gray(g);
70232
71094
  }
70233
71095
  function colorState(text, state, color) {
70234
71096
  if (!color)
@@ -70236,6 +71098,7 @@ function colorState(text, state, color) {
70236
71098
  switch (state) {
70237
71099
  case "healthy":
70238
71100
  return source_default.green(text);
71101
+ case "org-disabled":
70239
71102
  case "weekly-walled":
70240
71103
  return source_default.red(text);
70241
71104
  case "quota-exhausted":
@@ -70449,8 +71312,11 @@ function formatQuotaUtilCell(a, now = Date.now()) {
70449
71312
  function printAccountsTable(state) {
70450
71313
  console.log(source_default.bold(" ACCOUNT STATUS EXPIRES QUOTA 5h\u00b77d QUOTA-RESET"));
70451
71314
  for (const a of state.accounts) {
70452
- const marker = a.label === state.active ? source_default.green("\u25cf") : a.exhausted ? source_default.red("!") : source_default.gray("\u2713");
70453
- const status = a.label === state.active ? source_default.green("active ") : a.exhausted ? source_default.red("exhausted") : "available";
71315
+ const isActive = a.label === state.active;
71316
+ const orgBlocked = a.entitlement_blocked === true;
71317
+ const retired = a.in_service === false;
71318
+ const marker = isActive ? source_default.green("\u25cf") : orgBlocked ? source_default.red("\u2298") : retired ? source_default.gray("\u2205") : a.exhausted ? source_default.red("!") : source_default.gray("\u2713");
71319
+ const status = isActive ? source_default.green("active ") : orgBlocked ? source_default.red("DISABLED ") : retired ? source_default.gray("retired ") : a.exhausted ? source_default.red("exhausted") : "available";
70454
71320
  const label = a.label.padEnd(32);
70455
71321
  const exp = formatExpiry2(a.expiresAt).padEnd(10);
70456
71322
  const util3 = formatQuotaUtilCell(a).padEnd(20);
@@ -70499,8 +71365,16 @@ function printUsageTable(state) {
70499
71365
  console.log(source_default.bold(" ACCOUNT PREMIUM (Fable) HEADROOM STANDARD HEADROOM"));
70500
71366
  const WIDTH = 44;
70501
71367
  for (const a of state.accounts) {
70502
- const marker = a.label === state.active ? source_default.green("*") : " ";
71368
+ const isActive = a.label === state.active;
71369
+ const orgBlocked = a.entitlement_blocked === true;
71370
+ const retired = a.in_service === false;
71371
+ const marker = isActive ? source_default.green("*") : orgBlocked ? source_default.red("\u2298") : retired ? source_default.gray("\u2205") : " ";
70503
71372
  const label = a.label.padEnd(32);
71373
+ if (orgBlocked || retired) {
71374
+ const note = orgBlocked ? source_default.red("DISABLED (org) \u2014 no fleet routing") : source_default.gray("retired \u2014 removed from fleet rotation");
71375
+ console.log(` ${marker} ${label} ${note}`);
71376
+ continue;
71377
+ }
70504
71378
  const premium = usageHeadroomCell(a.usage_ledger?.premium ?? null);
70505
71379
  const standard = usageHeadroomCell(a.usage_ledger?.standard ?? null);
70506
71380
  const premiumCol = premium.color(premium.plain.padEnd(WIDTH));
@@ -78331,7 +79205,7 @@ Cross-agent reflection plan
78331
79205
  }
78332
79206
  console.log();
78333
79207
  }));
78334
- memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current.").option("--tag <version>", "Pin the hindsight image tag to pull + run (e.g. v0.15.18), overriding the " + "default floating `:latest`. Threaded through by `switchroom rollout` so a " + "version-pinned roll recreates hindsight on the SAME tag as the rest of the " + "fleet. Omit for `:latest` (the standalone default).").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").action(async (opts) => {
79208
+ memory.command("setup").description("Manage the Hindsight Docker container").option("--stop", "Stop and remove the Hindsight container").option("--status", "Show Hindsight container status").option("--recreate", "Pull the latest image and recreate the container (reusing its current port). Used by `switchroom update` to keep the hindsight singleton current. Also required after any `hindsight.llm` model/provider config change \u2014 env is only re-derived on recreate, a plain docker restart is not enough (see docs/operators/hindsight-model-change.md).").option("--tag <version>", "Pin the hindsight image tag to pull + run (e.g. v0.15.18), overriding the " + "default floating `:latest`. Threaded through by `switchroom rollout` so a " + "version-pinned roll recreates hindsight on the SAME tag as the rest of the " + "fleet. Omit for `:latest` (the standalone default).").option("--provider <provider>", "LLM provider (ollama, openai, anthropic)").action(async (opts) => {
78335
79209
  if (opts.status) {
78336
79210
  if (!isDockerAvailable()) {
78337
79211
  console.log(source_default.red(" Docker is not available."));
@@ -84110,7 +84984,7 @@ init_lifecycle();
84110
84984
  init_compose_env();
84111
84985
  import { cpSync as cpSync2, existsSync as existsSync67, mkdirSync as mkdirSync35, readFileSync as readFileSync60, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync40, chownSync as chownSync5 } from "node:fs";
84112
84986
  import { spawnSync as spawnSync12 } from "node:child_process";
84113
- import { join as join68, dirname as dirname21, resolve as resolve40 } from "node:path";
84987
+ import { join as join69, dirname as dirname21, resolve as resolve40 } from "node:path";
84114
84988
  import { homedir as homedir40 } from "node:os";
84115
84989
 
84116
84990
  // src/cli/release-yaml.ts
@@ -84218,13 +85092,13 @@ function defaultPersistPin(configPath) {
84218
85092
  } catch {}
84219
85093
  };
84220
85094
  }
84221
- var DEFAULT_COMPOSE_PATH = join68(homedir40(), ".switchroom", "compose", "docker-compose.yml");
85095
+ var DEFAULT_COMPOSE_PATH = join69(homedir40(), ".switchroom", "compose", "docker-compose.yml");
84222
85096
  function runningFromSwitchroomCheckout(scriptPath) {
84223
85097
  let dir = dirname21(scriptPath);
84224
85098
  for (let i = 0;i < 12; i++) {
84225
- if (existsSync67(join68(dir, ".git"))) {
85099
+ if (existsSync67(join69(dir, ".git"))) {
84226
85100
  try {
84227
- const pkg = JSON.parse(readFileSync60(join68(dir, "package.json"), "utf-8"));
85101
+ const pkg = JSON.parse(readFileSync60(join69(dir, "package.json"), "utf-8"));
84228
85102
  if (pkg.name === "switchroom")
84229
85103
  return true;
84230
85104
  } catch {}
@@ -84435,7 +85309,7 @@ function planUpdate(opts) {
84435
85309
  return;
84436
85310
  }
84437
85311
  const source = resolve40(import.meta.dirname, "../../skills");
84438
- const dest = join68(homedir40(), ".switchroom", "skills", "_bundled");
85312
+ const dest = join69(homedir40(), ".switchroom", "skills", "_bundled");
84439
85313
  if (!existsSync67(source)) {
84440
85314
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
84441
85315
  `);
@@ -84560,7 +85434,7 @@ function defaultStatusProbe(composePath) {
84560
85434
  } catch {}
84561
85435
  let dir = dirname21(scriptPath);
84562
85436
  for (let i = 0;i < 8; i++) {
84563
- const pkgPath = join68(dir, "package.json");
85437
+ const pkgPath = join69(dir, "package.json");
84564
85438
  if (existsSync67(pkgPath)) {
84565
85439
  try {
84566
85440
  const pkg = JSON.parse(readFileSync60(pkgPath, "utf-8"));
@@ -85263,7 +86137,7 @@ init_lifecycle();
85263
86137
  init_resolve_version();
85264
86138
  import { execSync as execSync3 } from "node:child_process";
85265
86139
  import { existsSync as existsSync68, readFileSync as readFileSync62 } from "node:fs";
85266
- import { dirname as dirname22, join as join69 } from "node:path";
86140
+ import { dirname as dirname22, join as join70 } from "node:path";
85267
86141
  function getClaudeCodeVersion() {
85268
86142
  try {
85269
86143
  const out = execSync3("claude --version 2>/dev/null", {
@@ -85313,11 +86187,11 @@ function formatUptime3(timestamp) {
85313
86187
  function locateSwitchroomInstallDir() {
85314
86188
  let dir = import.meta.dirname;
85315
86189
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
85316
- const pkgPath = join69(dir, "package.json");
86190
+ const pkgPath = join70(dir, "package.json");
85317
86191
  if (existsSync68(pkgPath)) {
85318
86192
  try {
85319
86193
  const pkg = JSON.parse(readFileSync62(pkgPath, "utf-8"));
85320
- if (pkg.name === "switchroom" && existsSync68(join69(dir, ".git"))) {
86194
+ if (pkg.name === "switchroom" && existsSync68(join70(dir, ".git"))) {
85321
86195
  return dir;
85322
86196
  }
85323
86197
  } catch {}
@@ -85500,12 +86374,12 @@ import {
85500
86374
  statSync as statSync42,
85501
86375
  unlinkSync as unlinkSync14
85502
86376
  } from "node:fs";
85503
- import { join as join70 } from "node:path";
86377
+ import { join as join71 } from "node:path";
85504
86378
  var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
85505
86379
  var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
85506
86380
  var MIN_KEEP = 2;
85507
86381
  function collectSessionJsonl(claudeConfigDir) {
85508
- const projects = join70(claudeConfigDir, "projects");
86382
+ const projects = join71(claudeConfigDir, "projects");
85509
86383
  if (!existsSync69(projects))
85510
86384
  return [];
85511
86385
  const found = [];
@@ -85517,7 +86391,7 @@ function collectSessionJsonl(claudeConfigDir) {
85517
86391
  return;
85518
86392
  }
85519
86393
  for (const name of entries) {
85520
- const full = join70(dir, name);
86394
+ const full = join71(dir, name);
85521
86395
  let st;
85522
86396
  try {
85523
86397
  st = statSync42(full);
@@ -85648,7 +86522,7 @@ import {
85648
86522
  writeFileSync as writeFileSync19,
85649
86523
  writeSync as writeSync9
85650
86524
  } from "node:fs";
85651
- import { join as join71 } from "node:path";
86525
+ import { join as join72 } from "node:path";
85652
86526
  import { randomBytes as randomBytes12 } from "node:crypto";
85653
86527
  import { execSync as execSync4 } from "node:child_process";
85654
86528
 
@@ -86079,7 +86953,7 @@ function redactedMarker(ruleId) {
86079
86953
  var ISSUES_FILE = "issues.jsonl";
86080
86954
  var ISSUES_LOCK = "issues.lock";
86081
86955
  function readAll(stateDir) {
86082
- const path5 = join71(stateDir, ISSUES_FILE);
86956
+ const path5 = join72(stateDir, ISSUES_FILE);
86083
86957
  if (!existsSync70(path5))
86084
86958
  return [];
86085
86959
  let raw;
@@ -86157,7 +87031,7 @@ function record(stateDir, input, nowFn = Date.now) {
86157
87031
  });
86158
87032
  }
86159
87033
  function resolve43(stateDir, fingerprint, nowFn = Date.now) {
86160
- if (!existsSync70(join71(stateDir, ISSUES_FILE)))
87034
+ if (!existsSync70(join72(stateDir, ISSUES_FILE)))
86161
87035
  return 0;
86162
87036
  return withLock(stateDir, () => {
86163
87037
  const all = readAll(stateDir);
@@ -86175,7 +87049,7 @@ function resolve43(stateDir, fingerprint, nowFn = Date.now) {
86175
87049
  });
86176
87050
  }
86177
87051
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
86178
- if (!existsSync70(join71(stateDir, ISSUES_FILE)))
87052
+ if (!existsSync70(join72(stateDir, ISSUES_FILE)))
86179
87053
  return 0;
86180
87054
  return withLock(stateDir, () => {
86181
87055
  const all = readAll(stateDir);
@@ -86193,7 +87067,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
86193
87067
  });
86194
87068
  }
86195
87069
  function prune(stateDir, opts = {}) {
86196
- if (!existsSync70(join71(stateDir, ISSUES_FILE)))
87070
+ if (!existsSync70(join72(stateDir, ISSUES_FILE)))
86197
87071
  return 0;
86198
87072
  return withLock(stateDir, () => {
86199
87073
  const all = readAll(stateDir);
@@ -86226,7 +87100,7 @@ function ensureDir(stateDir) {
86226
87100
  mkdirSync36(stateDir, { recursive: true });
86227
87101
  }
86228
87102
  function writeAll(stateDir, events) {
86229
- const path5 = join71(stateDir, ISSUES_FILE);
87103
+ const path5 = join72(stateDir, ISSUES_FILE);
86230
87104
  sweepOrphanTmpFiles(stateDir);
86231
87105
  const tmp = `${path5}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
86232
87106
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -86248,7 +87122,7 @@ function sweepOrphanTmpFiles(stateDir) {
86248
87122
  for (const entry of entries) {
86249
87123
  if (!entry.startsWith(TMP_PREFIX))
86250
87124
  continue;
86251
- const tmpPath = join71(stateDir, entry);
87125
+ const tmpPath = join72(stateDir, entry);
86252
87126
  try {
86253
87127
  const stat = statSync43(tmpPath);
86254
87128
  if (stat.mtimeMs < cutoff) {
@@ -86260,7 +87134,7 @@ function sweepOrphanTmpFiles(stateDir) {
86260
87134
  var LOCK_RETRY_MS = 25;
86261
87135
  var LOCK_TIMEOUT_MS = 1e4;
86262
87136
  function withLock(stateDir, fn) {
86263
- const lockPath = join71(stateDir, ISSUES_LOCK);
87137
+ const lockPath = join72(stateDir, ISSUES_LOCK);
86264
87138
  const startedAt = Date.now();
86265
87139
  let fd = null;
86266
87140
  while (fd === null) {
@@ -86545,7 +87419,7 @@ function relTime(deltaMs) {
86545
87419
  init_source();
86546
87420
  import { existsSync as existsSync73 } from "node:fs";
86547
87421
  import { homedir as homedir44 } from "node:os";
86548
- import { join as join74, resolve as resolve44 } from "node:path";
87422
+ import { join as join75, resolve as resolve44 } from "node:path";
86549
87423
 
86550
87424
  // src/deps/python.ts
86551
87425
  import { createHash as createHash13 } from "node:crypto";
@@ -86556,7 +87430,7 @@ import {
86556
87430
  rmSync as rmSync13,
86557
87431
  writeFileSync as writeFileSync20
86558
87432
  } from "node:fs";
86559
- import { dirname as dirname23, join as join72 } from "node:path";
87433
+ import { dirname as dirname23, join as join73 } from "node:path";
86560
87434
  import { homedir as homedir42 } from "node:os";
86561
87435
  import { execFileSync as execFileSync21 } from "node:child_process";
86562
87436
 
@@ -86569,7 +87443,7 @@ class PythonEnvError extends Error {
86569
87443
  }
86570
87444
  }
86571
87445
  function defaultPythonCacheRoot() {
86572
- return join72(homedir42(), ".switchroom", "deps", "python");
87446
+ return join73(homedir42(), ".switchroom", "deps", "python");
86573
87447
  }
86574
87448
  function hashFile(path5) {
86575
87449
  return createHash13("sha256").update(readFileSync64(path5)).digest("hex");
@@ -86581,11 +87455,11 @@ function ensurePythonEnv(opts) {
86581
87455
  if (!existsSync71(requirementsPath)) {
86582
87456
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
86583
87457
  }
86584
- const venvDir = join72(cacheRoot, skillName);
86585
- const stampPath = join72(venvDir, ".requirements.sha256");
86586
- const binDir = join72(venvDir, "bin");
86587
- const pythonBin = join72(binDir, "python");
86588
- const pipBin = join72(binDir, "pip");
87458
+ const venvDir = join73(cacheRoot, skillName);
87459
+ const stampPath = join73(venvDir, ".requirements.sha256");
87460
+ const binDir = join73(venvDir, "bin");
87461
+ const pythonBin = join73(binDir, "python");
87462
+ const pipBin = join73(binDir, "pip");
86589
87463
  const targetHash = hashFile(requirementsPath);
86590
87464
  if (!force && existsSync71(stampPath) && existsSync71(pythonBin)) {
86591
87465
  const existingHash = readFileSync64(stampPath, "utf8").trim();
@@ -86644,7 +87518,7 @@ import {
86644
87518
  rmSync as rmSync14,
86645
87519
  writeFileSync as writeFileSync21
86646
87520
  } from "node:fs";
86647
- import { dirname as dirname24, join as join73 } from "node:path";
87521
+ import { dirname as dirname24, join as join74 } from "node:path";
86648
87522
  import { homedir as homedir43 } from "node:os";
86649
87523
  import { execFileSync as execFileSync22 } from "node:child_process";
86650
87524
 
@@ -86668,7 +87542,7 @@ var LOCKFILES_FOR = {
86668
87542
  npm: ["package-lock.json"]
86669
87543
  };
86670
87544
  function defaultNodeCacheRoot() {
86671
- return join73(homedir43(), ".switchroom", "deps", "node");
87545
+ return join74(homedir43(), ".switchroom", "deps", "node");
86672
87546
  }
86673
87547
  function hashDepInputs(packageJsonPath) {
86674
87548
  const sourceDir = dirname24(packageJsonPath);
@@ -86677,7 +87551,7 @@ function hashDepInputs(packageJsonPath) {
86677
87551
  `);
86678
87552
  hasher.update(readFileSync65(packageJsonPath));
86679
87553
  for (const lockName of ALL_LOCKFILES) {
86680
- const lockPath = join73(sourceDir, lockName);
87554
+ const lockPath = join74(sourceDir, lockName);
86681
87555
  if (existsSync72(lockPath)) {
86682
87556
  hasher.update(`
86683
87557
  `);
@@ -86697,10 +87571,10 @@ function ensureNodeEnv(opts) {
86697
87571
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
86698
87572
  }
86699
87573
  const sourceDir = dirname24(packageJsonPath);
86700
- const envDir = join73(cacheRoot, skillName);
86701
- const stampPath = join73(envDir, ".package.sha256");
86702
- const nodeModulesDir = join73(envDir, "node_modules");
86703
- const binDir = join73(nodeModulesDir, ".bin");
87574
+ const envDir = join74(cacheRoot, skillName);
87575
+ const stampPath = join74(envDir, ".package.sha256");
87576
+ const nodeModulesDir = join74(envDir, "node_modules");
87577
+ const binDir = join74(nodeModulesDir, ".bin");
86704
87578
  const targetHash = hashDepInputs(packageJsonPath);
86705
87579
  if (!force && existsSync72(stampPath) && existsSync72(nodeModulesDir)) {
86706
87580
  const existingHash = readFileSync65(stampPath, "utf8").trim();
@@ -86718,12 +87592,12 @@ function ensureNodeEnv(opts) {
86718
87592
  rmSync14(envDir, { recursive: true, force: true });
86719
87593
  }
86720
87594
  mkdirSync38(envDir, { recursive: true });
86721
- copyFileSync10(packageJsonPath, join73(envDir, "package.json"));
87595
+ copyFileSync10(packageJsonPath, join74(envDir, "package.json"));
86722
87596
  let copiedLockfile = false;
86723
87597
  for (const lockName of LOCKFILES_FOR[installer]) {
86724
- const lockPath = join73(sourceDir, lockName);
87598
+ const lockPath = join74(sourceDir, lockName);
86725
87599
  if (existsSync72(lockPath)) {
86726
- copyFileSync10(lockPath, join73(envDir, lockName));
87600
+ copyFileSync10(lockPath, join74(envDir, lockName));
86727
87601
  copiedLockfile = true;
86728
87602
  }
86729
87603
  }
@@ -86762,13 +87636,13 @@ function registerDepsCommand(program3) {
86762
87636
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
86763
87637
  process.exit(1);
86764
87638
  }
86765
- const skillDir = join74(skillsRoot, skill);
87639
+ const skillDir = join75(skillsRoot, skill);
86766
87640
  if (!existsSync73(skillDir)) {
86767
87641
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
86768
87642
  process.exit(1);
86769
87643
  }
86770
- const requirementsPath = join74(skillDir, "requirements.txt");
86771
- const packageJsonPath = join74(skillDir, "package.json");
87644
+ const requirementsPath = join75(skillDir, "requirements.txt");
87645
+ const packageJsonPath = join75(skillDir, "package.json");
86772
87646
  const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync73(requirementsPath));
86773
87647
  const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync73(packageJsonPath));
86774
87648
  let did = 0;
@@ -87723,7 +88597,7 @@ init_helpers();
87723
88597
  init_loader();
87724
88598
  init_merge();
87725
88599
  import { copyFileSync as copyFileSync11, existsSync as existsSync75, readFileSync as readFileSync66, writeFileSync as writeFileSync22 } from "node:fs";
87726
- import { join as join75, resolve as resolve46 } from "node:path";
88600
+ import { join as join76, resolve as resolve46 } from "node:path";
87727
88601
  init_scaffold();
87728
88602
  init_profiles();
87729
88603
  init_schema();
@@ -87749,7 +88623,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
87749
88623
  profileName,
87750
88624
  profilePath,
87751
88625
  workspaceDir,
87752
- soulPath: join75(workspaceDir, "SOUL.md"),
88626
+ soulPath: join76(workspaceDir, "SOUL.md"),
87753
88627
  soul: merged.soul
87754
88628
  };
87755
88629
  }
@@ -87816,7 +88690,7 @@ function registerSoulCommand(program3) {
87816
88690
  init_helpers();
87817
88691
  init_loader();
87818
88692
  import { existsSync as existsSync76, readFileSync as readFileSync67, readdirSync as readdirSync26, statSync as statSync44 } from "node:fs";
87819
- import { resolve as resolve47, join as join76 } from "node:path";
88693
+ import { resolve as resolve47, join as join77 } from "node:path";
87820
88694
  import { createHash as createHash15 } from "node:crypto";
87821
88695
  init_merge();
87822
88696
  init_hindsight2();
@@ -87827,7 +88701,7 @@ function estimateTokens(bytes) {
87827
88701
  return Math.round(bytes / 3.7);
87828
88702
  }
87829
88703
  function readMcpServerNames(agentDir) {
87830
- const mcpPath = join76(agentDir, ".mcp.json");
88704
+ const mcpPath = join77(agentDir, ".mcp.json");
87831
88705
  if (!existsSync76(mcpPath))
87832
88706
  return [];
87833
88707
  try {
@@ -87841,7 +88715,7 @@ function sha256(content) {
87841
88715
  return createHash15("sha256").update(content).digest("hex").slice(0, 16);
87842
88716
  }
87843
88717
  function findLatestTranscriptJsonl(claudeConfigDir) {
87844
- const projectsDir = join76(claudeConfigDir, "projects");
88718
+ const projectsDir = join77(claudeConfigDir, "projects");
87845
88719
  if (!existsSync76(projectsDir))
87846
88720
  return;
87847
88721
  try {
@@ -87850,8 +88724,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
87850
88724
  for (const entry of entries) {
87851
88725
  if (!entry.isDirectory())
87852
88726
  continue;
87853
- const projectPath = join76(projectsDir, entry.name);
87854
- const transcriptPath = join76(projectPath, "transcript.jsonl");
88727
+ const projectPath = join77(projectsDir, entry.name);
88728
+ const transcriptPath = join77(projectPath, "transcript.jsonl");
87855
88729
  if (!existsSync76(transcriptPath))
87856
88730
  continue;
87857
88731
  const stat3 = statSync44(transcriptPath);
@@ -87920,11 +88794,11 @@ function registerDebugCommand(program3) {
87920
88794
  process.exit(1);
87921
88795
  }
87922
88796
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
87923
- const claudeConfigDir = join76(agentDir, ".claude");
87924
- const claudeMdPath = join76(agentDir, "CLAUDE.md");
87925
- const soulMdPath = join76(agentDir, "SOUL.md");
87926
- const workspaceSoulMdPath = join76(workspaceDir, "SOUL.md");
87927
- const handoffPath = join76(agentDir, ".handoff.md");
88797
+ const claudeConfigDir = join77(agentDir, ".claude");
88798
+ const claudeMdPath = join77(agentDir, "CLAUDE.md");
88799
+ const soulMdPath = join77(agentDir, "SOUL.md");
88800
+ const workspaceSoulMdPath = join77(workspaceDir, "SOUL.md");
88801
+ const handoffPath = join77(agentDir, ".handoff.md");
87928
88802
  const lastN = parseInt(opts.last, 10);
87929
88803
  if (isNaN(lastN) || lastN < 1) {
87930
88804
  console.error("--last must be a positive integer");
@@ -88053,9 +88927,9 @@ function registerDebugCommand(program3) {
88053
88927
  const soulMdBytes = soulMdContent.length;
88054
88928
  const perTurnBytes = dynamicResult.concatenated.length;
88055
88929
  const userBytes = userMessage?.text.length ?? 0;
88056
- const fleetDir = join76(agentsDir, "..", "fleet");
88057
- const fleetInvPath = join76(fleetDir, "switchroom-invariants.md");
88058
- const fleetClaudePath = join76(fleetDir, "CLAUDE.md");
88930
+ const fleetDir = join77(agentsDir, "..", "fleet");
88931
+ const fleetInvPath = join77(fleetDir, "switchroom-invariants.md");
88932
+ const fleetClaudePath = join77(fleetDir, "CLAUDE.md");
88059
88933
  const fleetInvBytes = existsSync76(fleetInvPath) ? readFileSync67(fleetInvPath, "utf-8").length : 0;
88060
88934
  const fleetClaudeBytes = existsSync76(fleetClaudePath) ? readFileSync67(fleetClaudePath, "utf-8").length : 0;
88061
88935
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
@@ -88091,7 +88965,7 @@ init_source();
88091
88965
  // src/worktree/claim.ts
88092
88966
  import { execFileSync as execFileSync23 } from "node:child_process";
88093
88967
  import { closeSync as closeSync13, mkdirSync as mkdirSync40, openSync as openSync13, existsSync as existsSync78, unlinkSync as unlinkSync17 } from "node:fs";
88094
- import { join as join78, resolve as resolve49 } from "node:path";
88968
+ import { join as join79, resolve as resolve49 } from "node:path";
88095
88969
  import { homedir as homedir46 } from "node:os";
88096
88970
  import { randomBytes as randomBytes13 } from "node:crypto";
88097
88971
 
@@ -88105,13 +88979,13 @@ import {
88105
88979
  existsSync as existsSync77,
88106
88980
  renameSync as renameSync17
88107
88981
  } from "node:fs";
88108
- import { join as join77, resolve as resolve48 } from "node:path";
88982
+ import { join as join78, resolve as resolve48 } from "node:path";
88109
88983
  import { homedir as homedir45 } from "node:os";
88110
88984
  function registryDir() {
88111
- return resolve48(process.env.SWITCHROOM_WORKTREE_DIR ?? join77(homedir45(), ".switchroom", "worktrees"));
88985
+ return resolve48(process.env.SWITCHROOM_WORKTREE_DIR ?? join78(homedir45(), ".switchroom", "worktrees"));
88112
88986
  }
88113
88987
  function recordPath(id) {
88114
- return join77(registryDir(), `${id}.json`);
88988
+ return join78(registryDir(), `${id}.json`);
88115
88989
  }
88116
88990
  function ensureDir2() {
88117
88991
  mkdirSync39(registryDir(), { recursive: true });
@@ -88162,7 +89036,7 @@ function acquireRepoLock(repoPath) {
88162
89036
  const lockDir = registryDir();
88163
89037
  mkdirSync40(lockDir, { recursive: true });
88164
89038
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
88165
- const lockPath = join78(lockDir, `.lock-${lockName}`);
89039
+ const lockPath = join79(lockDir, `.lock-${lockName}`);
88166
89040
  const deadline = Date.now() + 5000;
88167
89041
  let fd = null;
88168
89042
  while (fd === null) {
@@ -88189,7 +89063,7 @@ function acquireRepoLock(repoPath) {
88189
89063
  }
88190
89064
  var DEFAULT_CONCURRENCY = 5;
88191
89065
  function worktreesBaseDir() {
88192
- return resolve49(process.env.SWITCHROOM_WORKTREE_BASE ?? join78(homedir46(), ".switchroom", "worktree-checkouts"));
89066
+ return resolve49(process.env.SWITCHROOM_WORKTREE_BASE ?? join79(homedir46(), ".switchroom", "worktree-checkouts"));
88193
89067
  }
88194
89068
  function shortId() {
88195
89069
  return randomBytes13(4).toString("hex");
@@ -88211,7 +89085,7 @@ function resolveRepoPath(repo, codeRepos) {
88211
89085
  }
88212
89086
  function expandHome(p) {
88213
89087
  if (p.startsWith("~/"))
88214
- return join78(homedir46(), p.slice(2));
89088
+ return join79(homedir46(), p.slice(2));
88215
89089
  return p;
88216
89090
  }
88217
89091
  async function claimWorktree(input, codeRepos) {
@@ -88239,7 +89113,7 @@ async function claimWorktree(input, codeRepos) {
88239
89113
  branch = `task/${taskSuffix}-${id}`;
88240
89114
  const baseDir = worktreesBaseDir();
88241
89115
  mkdirSync40(baseDir, { recursive: true });
88242
- worktreePath = join78(baseDir, `${id}-${taskSuffix}`);
89116
+ worktreePath = join79(baseDir, `${id}-${taskSuffix}`);
88243
89117
  const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
88244
89118
  const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
88245
89119
  const now = new Date().toISOString();
@@ -88470,7 +89344,7 @@ import {
88470
89344
  rmSync as rmSync15
88471
89345
  } from "node:fs";
88472
89346
  import { homedir as homedir47 } from "node:os";
88473
- import { join as join79, resolve as resolve50 } from "node:path";
89347
+ import { join as join80, resolve as resolve50 } from "node:path";
88474
89348
  function parseGitdirPointer(dotGitFileContents) {
88475
89349
  const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
88476
89350
  return m ? m[1] : null;
@@ -88585,7 +89459,7 @@ function defaultPrSignal(repo, branch, exec) {
88585
89459
  }
88586
89460
  }
88587
89461
  function trashRoot() {
88588
- return resolve50(process.env.SWITCHROOM_WORKTREE_TRASH ?? join79(homedir47(), ".switchroom", "worktree-gc-trash"));
89462
+ return resolve50(process.env.SWITCHROOM_WORKTREE_TRASH ?? join80(homedir47(), ".switchroom", "worktree-gc-trash"));
88589
89463
  }
88590
89464
  function planGc(roots, deps = {}) {
88591
89465
  const exists = deps.existsSync ?? existsSync81;
@@ -88595,7 +89469,7 @@ function planGc(roots, deps = {}) {
88595
89469
  const exec = deps.exec ?? defaultExec;
88596
89470
  const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
88597
89471
  const stamp = deps.dateStamp ?? "undated";
88598
- const trash = join79(trashRoot(), stamp);
89472
+ const trash = join80(trashRoot(), stamp);
88599
89473
  let claimed;
88600
89474
  try {
88601
89475
  claimed = new Set(listRecords().map((r) => resolve50(r.path)));
@@ -88631,10 +89505,10 @@ function planGc(roots, deps = {}) {
88631
89505
  continue;
88632
89506
  }
88633
89507
  for (const name of entries) {
88634
- const dir = join79(root, name);
89508
+ const dir = join80(root, name);
88635
89509
  if (isEphemeralPath(dir))
88636
89510
  continue;
88637
- const dotGit = join79(dir, ".git");
89511
+ const dotGit = join80(dir, ".git");
88638
89512
  if (!exists(dotGit))
88639
89513
  continue;
88640
89514
  let st;
@@ -88663,7 +89537,7 @@ function planGc(roots, deps = {}) {
88663
89537
  ownerRepos.add(repoRoot);
88664
89538
  if (exists(ptr))
88665
89539
  continue;
88666
- orphans.push({ dir, owner: repoRoot, dest: join79(trash, name) });
89540
+ orphans.push({ dir, owner: repoRoot, dest: join80(trash, name) });
88667
89541
  }
88668
89542
  }
88669
89543
  const registered = [];
@@ -88774,7 +89648,7 @@ function listTrashEntries(nowMs, deps = {}) {
88774
89648
  return [];
88775
89649
  const out = [];
88776
89650
  for (const stamp of readDir(root)) {
88777
- const stampDir = join79(root, stamp);
89651
+ const stampDir = join80(root, stamp);
88778
89652
  let names;
88779
89653
  try {
88780
89654
  names = readDir(stampDir);
@@ -88782,7 +89656,7 @@ function listTrashEntries(nowMs, deps = {}) {
88782
89656
  continue;
88783
89657
  }
88784
89658
  for (const name of names) {
88785
- const p = join79(stampDir, name);
89659
+ const p = join80(stampDir, name);
88786
89660
  let mtimeMs = nowMs;
88787
89661
  try {
88788
89662
  mtimeMs = statSync45(p).mtimeMs;
@@ -88806,7 +89680,7 @@ function purgeTrash(paths) {
88806
89680
  return { deleted, errors: errors2 };
88807
89681
  }
88808
89682
  function defaultRoots() {
88809
- return [join79(homedir47(), "code")];
89683
+ return [join80(homedir47(), "code")];
88810
89684
  }
88811
89685
 
88812
89686
  // src/cli/worktree.ts
@@ -89034,7 +89908,7 @@ import {
89034
89908
  rmSync as rmSync16,
89035
89909
  writeFileSync as writeFileSync24
89036
89910
  } from "node:fs";
89037
- import { join as join80 } from "node:path";
89911
+ import { join as join81 } from "node:path";
89038
89912
  function encodeCredentialsFilename(email) {
89039
89913
  const SAFE = new Set([
89040
89914
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -89224,16 +90098,16 @@ function resolveCredentialsDir(env2) {
89224
90098
  if (explicit && explicit.length > 0)
89225
90099
  return explicit;
89226
90100
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
89227
- return join80(stateBase, "google-workspace-mcp", "credentials");
90101
+ return join81(stateBase, "google-workspace-mcp", "credentials");
89228
90102
  }
89229
90103
  function writeSeedFile(dir, email, seed) {
89230
90104
  mkdirSync42(dir, { recursive: true, mode: 448 });
89231
90105
  chmodSync11(dir, 448);
89232
90106
  for (const name of readdirSync29(dir)) {
89233
- rmSync16(join80(dir, name), { force: true, recursive: true });
90107
+ rmSync16(join81(dir, name), { force: true, recursive: true });
89234
90108
  }
89235
90109
  const filename = encodeCredentialsFilename(email);
89236
- const filePath = join80(dir, filename);
90110
+ const filePath = join81(dir, filename);
89237
90111
  writeFileSync24(filePath, JSON.stringify(seed), { mode: 384 });
89238
90112
  chmodSync11(filePath, 384);
89239
90113
  return filePath;
@@ -89393,7 +90267,7 @@ function registerDriveMcpLauncherCommand(program3) {
89393
90267
  init_scaffold_integration();
89394
90268
  import { spawn as spawn5 } from "node:child_process";
89395
90269
  import { writeFileSync as writeFileSync25, mkdirSync as mkdirSync43 } from "node:fs";
89396
- import { dirname as dirname25, join as join81 } from "node:path";
90270
+ import { dirname as dirname25, join as join82 } from "node:path";
89397
90271
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
89398
90272
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
89399
90273
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
@@ -89438,7 +90312,7 @@ function heartbeatPath(agentName, account) {
89438
90312
  const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
89439
90313
  if (override) {
89440
90314
  const base = slug ? `m365-launcher-${agentName}-${slug}` : `m365-launcher-${agentName}`;
89441
- return join81(override, `${base}.heartbeat.json`);
90315
+ return join82(override, `${base}.heartbeat.json`);
89442
90316
  }
89443
90317
  return slug ? `/state/agent/m365-launcher-${slug}.heartbeat.json` : "/state/agent/m365-launcher.heartbeat.json";
89444
90318
  }
@@ -89796,7 +90670,7 @@ function registerNotionMcpLauncherCommand(program3) {
89796
90670
  init_hindsight();
89797
90671
  import { mkdirSync as mkdirSync45, readFileSync as readFileSync70, renameSync as renameSync19, writeFileSync as writeFileSync27 } from "node:fs";
89798
90672
  import { tmpdir as tmpdir5 } from "node:os";
89799
- import { join as join82 } from "node:path";
90673
+ import { join as join83 } from "node:path";
89800
90674
  import { createInterface as createInterface6 } from "node:readline";
89801
90675
  var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
89802
90676
  "2025-06-18",
@@ -90019,12 +90893,12 @@ class HindsightShim {
90019
90893
  `));
90020
90894
  }
90021
90895
  get cachePath() {
90022
- return join82(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
90896
+ return join83(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
90023
90897
  }
90024
90898
  writeCache(result) {
90025
90899
  try {
90026
90900
  mkdirSync45(this.opts.cacheDir, { recursive: true });
90027
- const tmp = join82(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
90901
+ const tmp = join83(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
90028
90902
  writeFileSync27(tmp, JSON.stringify(result, null, 2) + `
90029
90903
  `);
90030
90904
  renameSync19(tmp, this.cachePath);
@@ -90197,7 +91071,7 @@ function resolveShimOptionsFromEnv(env2) {
90197
91071
  return {
90198
91072
  url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
90199
91073
  bankId: env2.HINDSIGHT_BANK_ID || "",
90200
- cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join82(home2, ".hindsight-shim")
91074
+ cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join83(home2, ".hindsight-shim")
90201
91075
  };
90202
91076
  }
90203
91077
  function registerHindsightMcpShimCommand(program3) {
@@ -90872,7 +91746,7 @@ function runRedactStdin() {
90872
91746
 
90873
91747
  // src/cli/status-ask.ts
90874
91748
  import { readFileSync as readFileSync75, existsSync as existsSync87, readdirSync as readdirSync31 } from "node:fs";
90875
- import { join as join87 } from "node:path";
91749
+ import { join as join88 } from "node:path";
90876
91750
  import { homedir as homedir50 } from "node:os";
90877
91751
 
90878
91752
  // src/status-ask/report.ts
@@ -91208,7 +92082,7 @@ function resolveSources(explicitPath) {
91208
92082
  const config = loadConfig();
91209
92083
  agentsDir = resolveAgentsDir(config);
91210
92084
  } catch {
91211
- agentsDir = join87(homedir50(), ".switchroom", "agents");
92085
+ agentsDir = join88(homedir50(), ".switchroom", "agents");
91212
92086
  }
91213
92087
  if (!existsSync87(agentsDir))
91214
92088
  return [];
@@ -91220,7 +92094,7 @@ function resolveSources(explicitPath) {
91220
92094
  return [];
91221
92095
  }
91222
92096
  for (const name of entries) {
91223
- const path9 = join87(agentsDir, name, "runtime-metrics.jsonl");
92097
+ const path9 = join88(agentsDir, name, "runtime-metrics.jsonl");
91224
92098
  if (existsSync87(path9)) {
91225
92099
  sources.push({ path: path9, agent: name });
91226
92100
  }
@@ -91262,21 +92136,21 @@ import {
91262
92136
  unlinkSync as unlinkSync18,
91263
92137
  writeSync as writeSync10
91264
92138
  } from "node:fs";
91265
- import { join as join88, resolve as resolve53 } from "node:path";
92139
+ import { join as join89, resolve as resolve53 } from "node:path";
91266
92140
  var STAGING_SUBDIR = ".staging";
91267
92141
  function overlayPathsFor(agent, opts = {}) {
91268
92142
  const base = opts.root ? resolve53(opts.root, agent) : resolve53(resolveDualPath(`~/.switchroom/agents/${agent}`));
91269
- const scheduleDir = join88(base, "schedule.d");
91270
- const scheduleStagingDir = join88(scheduleDir, STAGING_SUBDIR);
91271
- const skillsDir = join88(base, "skills.d");
91272
- const skillsStagingDir = join88(skillsDir, STAGING_SUBDIR);
92143
+ const scheduleDir = join89(base, "schedule.d");
92144
+ const scheduleStagingDir = join89(scheduleDir, STAGING_SUBDIR);
92145
+ const skillsDir = join89(base, "skills.d");
92146
+ const skillsStagingDir = join89(skillsDir, STAGING_SUBDIR);
91273
92147
  return {
91274
92148
  agentRoot: base,
91275
92149
  scheduleDir,
91276
92150
  scheduleStagingDir,
91277
92151
  skillsDir,
91278
92152
  skillsStagingDir,
91279
- lockPath: join88(base, ".lock"),
92153
+ lockPath: join89(base, ".lock"),
91280
92154
  stagingDir: scheduleStagingDir
91281
92155
  };
91282
92156
  }
@@ -91330,8 +92204,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
91330
92204
  const paths = overlayPathsFor(agent, opts);
91331
92205
  return withAgentLock(paths, () => {
91332
92206
  ensureDirs(paths);
91333
- const stagingPath = join88(paths.scheduleStagingDir, `${slug}.yaml`);
91334
- const finalPath = join88(paths.scheduleDir, `${slug}.yaml`);
92207
+ const stagingPath = join89(paths.scheduleStagingDir, `${slug}.yaml`);
92208
+ const finalPath = join89(paths.scheduleDir, `${slug}.yaml`);
91335
92209
  const fd = openSync14(stagingPath, "w", 384);
91336
92210
  try {
91337
92211
  writeSync10(fd, yamlText);
@@ -91347,8 +92221,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
91347
92221
  const paths = overlayPathsFor(agent, opts);
91348
92222
  return withAgentLock(paths, () => {
91349
92223
  ensureSkillsDirs(paths);
91350
- const stagingPath = join88(paths.skillsStagingDir, `${slug}.yaml`);
91351
- const finalPath = join88(paths.skillsDir, `${slug}.yaml`);
92224
+ const stagingPath = join89(paths.skillsStagingDir, `${slug}.yaml`);
92225
+ const finalPath = join89(paths.skillsDir, `${slug}.yaml`);
91352
92226
  const fd = openSync14(stagingPath, "w", 384);
91353
92227
  try {
91354
92228
  writeSync10(fd, yamlText);
@@ -91363,7 +92237,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
91363
92237
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
91364
92238
  const paths = overlayPathsFor(agent, opts);
91365
92239
  return withAgentLock(paths, () => {
91366
- const finalPath = join88(paths.skillsDir, `${slug}.yaml`);
92240
+ const finalPath = join89(paths.skillsDir, `${slug}.yaml`);
91367
92241
  if (!existsSync88(finalPath))
91368
92242
  return false;
91369
92243
  unlinkSync18(finalPath);
@@ -91378,7 +92252,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
91378
92252
  for (const name of readdirSync32(paths.skillsDir)) {
91379
92253
  if (!/\.ya?ml$/i.test(name))
91380
92254
  continue;
91381
- const full = join88(paths.skillsDir, name);
92255
+ const full = join89(paths.skillsDir, name);
91382
92256
  try {
91383
92257
  const raw = readFileSync76(full, "utf-8");
91384
92258
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -91390,7 +92264,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
91390
92264
  function deleteOverlayEntry(agent, slug, opts = {}) {
91391
92265
  const paths = overlayPathsFor(agent, opts);
91392
92266
  return withAgentLock(paths, () => {
91393
- const finalPath = join88(paths.scheduleDir, `${slug}.yaml`);
92267
+ const finalPath = join89(paths.scheduleDir, `${slug}.yaml`);
91394
92268
  if (!existsSync88(finalPath))
91395
92269
  return false;
91396
92270
  unlinkSync18(finalPath);
@@ -91405,7 +92279,7 @@ function listOverlayEntries(agent, opts = {}) {
91405
92279
  for (const name of readdirSync32(paths.scheduleDir)) {
91406
92280
  if (!/\.ya?ml$/i.test(name))
91407
92281
  continue;
91408
- const full = join88(paths.scheduleDir, name);
92282
+ const full = join89(paths.scheduleDir, name);
91409
92283
  try {
91410
92284
  const raw = readFileSync76(full, "utf-8");
91411
92285
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -91638,12 +92512,12 @@ import {
91638
92512
  writeFileSync as writeFileSync32,
91639
92513
  writeSync as writeSync11
91640
92514
  } from "node:fs";
91641
- import { join as join89 } from "node:path";
92515
+ import { join as join90 } from "node:path";
91642
92516
  import { randomBytes as randomBytes15 } from "node:crypto";
91643
92517
  var STAGE_ID_PREFIX = "cap_";
91644
92518
  function pendingDir(agent, opts = {}) {
91645
92519
  const paths = overlayPathsFor(agent, opts);
91646
- return join89(paths.scheduleDir, ".pending");
92520
+ return join90(paths.scheduleDir, ".pending");
91647
92521
  }
91648
92522
  function ensurePendingDir(agent, opts = {}) {
91649
92523
  const dir = pendingDir(agent, opts);
@@ -91656,8 +92530,8 @@ function newStageId() {
91656
92530
  function stagePendingScheduleEntry(opts) {
91657
92531
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
91658
92532
  const stageId = opts.stageId ?? newStageId();
91659
- const yamlPath = join89(dir, `${stageId}.yaml`);
91660
- const metaPath = join89(dir, `${stageId}.meta.json`);
92533
+ const yamlPath = join90(dir, `${stageId}.yaml`);
92534
+ const metaPath = join90(dir, `${stageId}.meta.json`);
91661
92535
  const meta = {
91662
92536
  v: 1,
91663
92537
  stage_id: stageId,
@@ -91691,8 +92565,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
91691
92565
  if (!name.endsWith(".meta.json"))
91692
92566
  continue;
91693
92567
  const stageId = name.slice(0, -".meta.json".length);
91694
- const metaPath = join89(dir, name);
91695
- const yamlPath = join89(dir, `${stageId}.yaml`);
92568
+ const metaPath = join90(dir, name);
92569
+ const yamlPath = join90(dir, `${stageId}.yaml`);
91696
92570
  if (!existsSync89(yamlPath))
91697
92571
  continue;
91698
92572
  try {
@@ -91711,7 +92585,7 @@ function commitPendingScheduleEntry(opts) {
91711
92585
  return { committed: false, reason: "not_found" };
91712
92586
  const slug = match.meta.entry.name ?? match.stageId;
91713
92587
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
91714
- const finalPath = join89(paths.scheduleDir, `${slug}.yaml`);
92588
+ const finalPath = join90(paths.scheduleDir, `${slug}.yaml`);
91715
92589
  if (existsSync89(finalPath)) {
91716
92590
  return { committed: false, reason: "slug_collision" };
91717
92591
  }
@@ -92364,7 +93238,7 @@ import { existsSync as existsSync91 } from "node:fs";
92364
93238
  init_reconcile_default_skills();
92365
93239
  init_agent_config();
92366
93240
  var import_yaml23 = __toESM(require_dist(), 1);
92367
- import { join as join90 } from "node:path";
93241
+ import { join as join91 } from "node:path";
92368
93242
  var MAX_SKILLS_PER_AGENT = 20;
92369
93243
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
92370
93244
  function exitCodeFor2(code) {
@@ -92439,7 +93313,7 @@ function skillInstall(opts) {
92439
93313
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
92440
93314
  }
92441
93315
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
92442
- const skillPath = join90(poolDir, skillName);
93316
+ const skillPath = join91(poolDir, skillName);
92443
93317
  if (!existsSync91(skillPath)) {
92444
93318
  return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
92445
93319
  }
@@ -92618,7 +93492,7 @@ import {
92618
93492
  writeFileSync as writeFileSync33
92619
93493
  } from "node:fs";
92620
93494
  import { tmpdir as tmpdir6, homedir as homedir51 } from "node:os";
92621
- import { dirname as dirname31, join as join91, relative as relative2, resolve as resolve54 } from "node:path";
93495
+ import { dirname as dirname31, join as join92, relative as relative2, resolve as resolve54 } from "node:path";
92622
93496
  import { spawnSync as spawnSync16 } from "node:child_process";
92623
93497
 
92624
93498
  // src/cli/skill-common.ts
@@ -92852,7 +93726,7 @@ function scanForClaudeP2(content) {
92852
93726
  function resolveSkillsPoolDir2(override) {
92853
93727
  const raw = override ?? "~/.switchroom/skills";
92854
93728
  if (raw.startsWith("~/")) {
92855
- return join91(homedir51(), raw.slice(2));
93729
+ return join92(homedir51(), raw.slice(2));
92856
93730
  }
92857
93731
  if (raw === "~")
92858
93732
  return homedir51();
@@ -92891,7 +93765,7 @@ function loadFromDir(dir) {
92891
93765
  const walk2 = (sub) => {
92892
93766
  const entries = readdirSync34(sub, { withFileTypes: true });
92893
93767
  for (const ent of entries) {
92894
- const full = join91(sub, ent.name);
93768
+ const full = join92(sub, ent.name);
92895
93769
  const rel = relative2(abs, full);
92896
93770
  if (ent.isSymbolicLink()) {
92897
93771
  fail3(`refusing to read symlink inside --from dir: ${rel}`);
@@ -92926,7 +93800,7 @@ function loadFromTarball(tarPath) {
92926
93800
  fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
92927
93801
  }
92928
93802
  }
92929
- const staging = mkdtempSync5(join91(tmpdir6(), "skill-apply-extract-"));
93803
+ const staging = mkdtempSync5(join92(tmpdir6(), "skill-apply-extract-"));
92930
93804
  try {
92931
93805
  const flags = isGz ? ["-xzf"] : ["-xf"];
92932
93806
  const r = spawnSync16("tar", [
@@ -93012,8 +93886,8 @@ function validatePayload(name, files) {
93012
93886
  errors2.push(`${path9} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
93013
93887
  }
93014
93888
  } else if (PY_SCRIPT_RE2.test(path9)) {
93015
- const tmp = mkdtempSync5(join91(tmpdir6(), "skill-apply-py-"));
93016
- const tmpPy = join91(tmp, "check.py");
93889
+ const tmp = mkdtempSync5(join92(tmpdir6(), "skill-apply-py-"));
93890
+ const tmpPy = join92(tmp, "check.py");
93017
93891
  try {
93018
93892
  writeFileSync33(tmpPy, content);
93019
93893
  const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
@@ -93036,7 +93910,7 @@ function diffSummary(currentDir, files) {
93036
93910
  if (existsSync92(currentDir)) {
93037
93911
  const walk2 = (sub) => {
93038
93912
  for (const ent of readdirSync34(sub, { withFileTypes: true })) {
93039
- const full = join91(sub, ent.name);
93913
+ const full = join92(sub, ent.name);
93040
93914
  const rel = relative2(currentDir, full);
93041
93915
  if (ent.isDirectory()) {
93042
93916
  walk2(full);
@@ -93072,7 +93946,7 @@ function writePayload(poolDir, name, files) {
93072
93946
  if (!existsSync92(poolDir)) {
93073
93947
  mkdirSync52(poolDir, { recursive: true, mode: 493 });
93074
93948
  }
93075
- const target = join91(poolDir, name);
93949
+ const target = join92(poolDir, name);
93076
93950
  let targetIsSymlink = false;
93077
93951
  try {
93078
93952
  const st = lstatSync11(target);
@@ -93083,11 +93957,11 @@ function writePayload(poolDir, name, files) {
93083
93957
  if (targetIsSymlink) {
93084
93958
  fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
93085
93959
  }
93086
- const staging = mkdtempSync5(join91(poolDir, `.skill-apply-stage-${name}-`));
93960
+ const staging = mkdtempSync5(join92(poolDir, `.skill-apply-stage-${name}-`));
93087
93961
  let oldRename = null;
93088
93962
  try {
93089
93963
  for (const [path9, content] of Object.entries(files)) {
93090
- const full = join91(staging, path9);
93964
+ const full = join92(staging, path9);
93091
93965
  mkdirSync52(dirname31(full), { recursive: true, mode: 493 });
93092
93966
  const fd = openSync16(full, "wx");
93093
93967
  try {
@@ -93165,7 +94039,7 @@ function registerSkillCommand(program3) {
93165
94039
  }
93166
94040
  const config = loadConfig();
93167
94041
  const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
93168
- const currentDir = join91(poolDir, name);
94042
+ const currentDir = join92(poolDir, name);
93169
94043
  console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
93170
94044
  console.log(source_default.bold("Diff vs current pool content:"));
93171
94045
  console.log(diffSummary(currentDir, files));
@@ -93210,7 +94084,7 @@ import {
93210
94084
  utimesSync,
93211
94085
  writeFileSync as writeFileSync34
93212
94086
  } from "node:fs";
93213
- import { dirname as dirname32, join as join92, relative as relative3, resolve as resolve55 } from "node:path";
94087
+ import { dirname as dirname32, join as join93, relative as relative3, resolve as resolve55 } from "node:path";
93214
94088
  import { homedir as homedir52, tmpdir as tmpdir7 } from "node:os";
93215
94089
  import { spawnSync as spawnSync17 } from "node:child_process";
93216
94090
  init_helpers();
@@ -93222,10 +94096,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
93222
94096
  var PERSONAL_SKILLS_SUBPATH = "personal-skills";
93223
94097
  function resolveConfigSkillsDir(agent) {
93224
94098
  const override = process.env.SWITCHROOM_CONFIG_DIR;
93225
- const candidate = override ? resolve55(override) : join92(homedir52(), ".switchroom-config");
94099
+ const candidate = override ? resolve55(override) : join93(homedir52(), ".switchroom-config");
93226
94100
  if (!existsSync93(candidate))
93227
94101
  return null;
93228
- return join92(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
94102
+ return join93(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
93229
94103
  }
93230
94104
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
93231
94105
  function sweepMirrorPriors(configSkillsRoot) {
@@ -93243,7 +94117,7 @@ function sweepMirrorPriors(configSkillsRoot) {
93243
94117
  if (now - ts < MIRROR_PRIOR_TTL_MS)
93244
94118
  continue;
93245
94119
  try {
93246
- rmSync19(join92(configSkillsRoot, ent), { recursive: true, force: true });
94120
+ rmSync19(join93(configSkillsRoot, ent), { recursive: true, force: true });
93247
94121
  } catch {}
93248
94122
  }
93249
94123
  } catch {}
@@ -93252,7 +94126,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
93252
94126
  const configSkillsRoot = resolveConfigSkillsDir(agent);
93253
94127
  if (!configSkillsRoot)
93254
94128
  return;
93255
- const dest = join92(configSkillsRoot, name);
94129
+ const dest = join93(configSkillsRoot, name);
93256
94130
  try {
93257
94131
  if (liveSkillDir !== null) {
93258
94132
  try {
@@ -93267,19 +94141,19 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
93267
94141
  if (liveSkillDir === null) {
93268
94142
  sweepMirrorPriors(configSkillsRoot);
93269
94143
  if (existsSync93(dest)) {
93270
- const trash = join92(configSkillsRoot, `.${name}-trash-${Date.now()}`);
94144
+ const trash = join93(configSkillsRoot, `.${name}-trash-${Date.now()}`);
93271
94145
  renameSync24(dest, trash);
93272
94146
  }
93273
94147
  return;
93274
94148
  }
93275
94149
  mkdirSync53(configSkillsRoot, { recursive: true, mode: 493 });
93276
94150
  sweepMirrorPriors(configSkillsRoot);
93277
- const staging = mkdtempSync6(join92(configSkillsRoot, `.${name}-staging-`));
94151
+ const staging = mkdtempSync6(join93(configSkillsRoot, `.${name}-staging-`));
93278
94152
  const walk2 = (src, dst) => {
93279
94153
  mkdirSync53(dst, { recursive: true, mode: 493 });
93280
94154
  for (const ent of readdirSync35(src, { withFileTypes: true })) {
93281
- const s = join92(src, ent.name);
93282
- const d = join92(dst, ent.name);
94155
+ const s = join93(src, ent.name);
94156
+ const d = join93(dst, ent.name);
93283
94157
  if (ent.isSymbolicLink())
93284
94158
  continue;
93285
94159
  if (ent.isDirectory())
@@ -93291,7 +94165,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
93291
94165
  };
93292
94166
  walk2(liveSkillDir, staging);
93293
94167
  if (existsSync93(dest)) {
93294
- const prior = join92(configSkillsRoot, `.${name}-prior-${Date.now()}`);
94168
+ const prior = join93(configSkillsRoot, `.${name}-prior-${Date.now()}`);
93295
94169
  renameSync24(dest, prior);
93296
94170
  }
93297
94171
  renameSync24(staging, dest);
@@ -93321,16 +94195,16 @@ function resolveAgent(opts) {
93321
94195
  function resolveAgentsRoot(opts) {
93322
94196
  if (opts.root)
93323
94197
  return resolve55(opts.root);
93324
- return join92(homedir52(), ".switchroom", "agents");
94198
+ return join93(homedir52(), ".switchroom", "agents");
93325
94199
  }
93326
94200
  function personalSkillDir(agentsRoot, agent, name) {
93327
- return join92(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
94201
+ return join93(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
93328
94202
  }
93329
94203
  function trashDir(agentsRoot, agent) {
93330
- return join92(agentsRoot, agent, ".claude", TRASH_DIRNAME);
94204
+ return join93(agentsRoot, agent, ".claude", TRASH_DIRNAME);
93331
94205
  }
93332
94206
  function countPersonalSkills(agentsRoot, agent) {
93333
- const skillsDir = join92(agentsRoot, agent, ".claude", "skills");
94207
+ const skillsDir = join93(agentsRoot, agent, ".claude", "skills");
93334
94208
  if (!existsSync93(skillsDir))
93335
94209
  return 0;
93336
94210
  let n = 0;
@@ -93368,7 +94242,7 @@ function loadFromDir2(dir) {
93368
94242
  const files = {};
93369
94243
  const walk2 = (sub) => {
93370
94244
  for (const ent of readdirSync35(sub, { withFileTypes: true })) {
93371
- const full = join92(sub, ent.name);
94245
+ const full = join93(sub, ent.name);
93372
94246
  if (ent.isSymbolicLink()) {
93373
94247
  fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
93374
94248
  }
@@ -93421,8 +94295,8 @@ function behavioralValidate(files) {
93421
94295
  errors2.push(`${path9} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
93422
94296
  }
93423
94297
  } else if (PY_SCRIPT_RE.test(path9)) {
93424
- const tmp = mkdtempSync6(join92(tmpdir7(), "skill-personal-py-"));
93425
- const tmpPy = join92(tmp, "check.py");
94298
+ const tmp = mkdtempSync6(join93(tmpdir7(), "skill-personal-py-"));
94299
+ const tmpPy = join93(tmp, "check.py");
93426
94300
  try {
93427
94301
  writeFileSync34(tmpPy, content);
93428
94302
  const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
@@ -93446,7 +94320,7 @@ function sweepTrash(agentsRoot, agent) {
93446
94320
  for (const ent of readdirSync35(trash, { withFileTypes: true })) {
93447
94321
  if (!ent.isDirectory())
93448
94322
  continue;
93449
- const entPath = join92(trash, ent.name);
94323
+ const entPath = join93(trash, ent.name);
93450
94324
  try {
93451
94325
  const st = statSync50(entPath);
93452
94326
  if (now - st.mtimeMs > TRASH_TTL_MS) {
@@ -93467,11 +94341,11 @@ function writePersonalSkill(targetDir, files) {
93467
94341
  fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
93468
94342
  }
93469
94343
  mkdirSync53(dirname32(targetDir), { recursive: true, mode: 493 });
93470
- const staging = mkdtempSync6(join92(dirname32(targetDir), `.skill-personal-stage-`));
94344
+ const staging = mkdtempSync6(join93(dirname32(targetDir), `.skill-personal-stage-`));
93471
94345
  let oldRename = null;
93472
94346
  try {
93473
94347
  for (const [path9, content] of Object.entries(files)) {
93474
- const full = join92(staging, path9);
94348
+ const full = join93(staging, path9);
93475
94349
  mkdirSync53(dirname32(full), { recursive: true, mode: 493 });
93476
94350
  const fd = openSync17(full, "wx");
93477
94351
  try {
@@ -93618,10 +94492,10 @@ function editPersonalAction(name, opts) {
93618
94492
  }
93619
94493
  var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
93620
94494
  function defaultSharedRoot() {
93621
- return join92(homedir52(), ".switchroom", "skills");
94495
+ return join93(homedir52(), ".switchroom", "skills");
93622
94496
  }
93623
94497
  function defaultBundledRoot() {
93624
- return join92(homedir52(), ".switchroom", "skills", "_bundled");
94498
+ return join93(homedir52(), ".switchroom", "skills", "_bundled");
93625
94499
  }
93626
94500
  function resolveCloneSource(source, opts) {
93627
94501
  const m = CLONE_SOURCE_RE.exec(source);
@@ -93631,7 +94505,7 @@ function resolveCloneSource(source, opts) {
93631
94505
  const tier = m[1];
93632
94506
  const slug = m[2];
93633
94507
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
93634
- const dir = join92(root, slug);
94508
+ const dir = join93(root, slug);
93635
94509
  if (!existsSync93(dir)) {
93636
94510
  fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
93637
94511
  }
@@ -93647,7 +94521,7 @@ function readSourceFiles(dir) {
93647
94521
  const skipped = [];
93648
94522
  const walk2 = (sub) => {
93649
94523
  for (const ent of readdirSync35(sub, { withFileTypes: true })) {
93650
- const full = join92(sub, ent.name);
94524
+ const full = join93(sub, ent.name);
93651
94525
  if (ent.isSymbolicLink()) {
93652
94526
  continue;
93653
94527
  }
@@ -93758,7 +94632,7 @@ function removePersonalAction(name, opts) {
93758
94632
  const trashRoot2 = trashDir(agentsRoot, agent);
93759
94633
  mkdirSync53(trashRoot2, { recursive: true, mode: 493 });
93760
94634
  const ts = Date.now();
93761
- const trashTarget = join92(trashRoot2, `${name}-${ts}`);
94635
+ const trashTarget = join93(trashRoot2, `${name}-${ts}`);
93762
94636
  renameSync24(target, trashTarget);
93763
94637
  const now = new Date(ts);
93764
94638
  utimesSync(trashTarget, now, now);
@@ -93777,7 +94651,7 @@ function listPersonalAction(opts) {
93777
94651
  const agent = resolveAgent(opts);
93778
94652
  const agentsRoot = resolveAgentsRoot(opts);
93779
94653
  sweepTrash(agentsRoot, agent);
93780
- const skillsDir = join92(agentsRoot, agent, ".claude", "skills");
94654
+ const skillsDir = join93(agentsRoot, agent, ".claude", "skills");
93781
94655
  const personal = [];
93782
94656
  if (existsSync93(skillsDir)) {
93783
94657
  for (const ent of readdirSync35(skillsDir, { withFileTypes: true })) {
@@ -93786,7 +94660,7 @@ function listPersonalAction(opts) {
93786
94660
  if (!ent.name.startsWith(PERSONAL_PREFIX))
93787
94661
  continue;
93788
94662
  const skillName = ent.name.slice(PERSONAL_PREFIX.length);
93789
- const skillPath = join92(skillsDir, ent.name);
94663
+ const skillPath = join93(skillsDir, ent.name);
93790
94664
  let fileCount = 0;
93791
94665
  let totalBytes = 0;
93792
94666
  const walk2 = (sub) => {
@@ -93794,10 +94668,10 @@ function listPersonalAction(opts) {
93794
94668
  if (e.isFile()) {
93795
94669
  fileCount += 1;
93796
94670
  try {
93797
- totalBytes += statSync50(join92(sub, e.name)).size;
94671
+ totalBytes += statSync50(join93(sub, e.name)).size;
93798
94672
  } catch {}
93799
94673
  } else if (e.isDirectory()) {
93800
- walk2(join92(sub, e.name));
94674
+ walk2(join93(sub, e.name));
93801
94675
  }
93802
94676
  }
93803
94677
  };
@@ -93836,11 +94710,11 @@ function registerSkillPersonalCommands(program3) {
93836
94710
  // src/cli/self-improve-propose-skill.ts
93837
94711
  import { createConnection as createConnection4 } from "node:net";
93838
94712
  import { homedir as homedir53 } from "node:os";
93839
- import { join as join93 } from "node:path";
94713
+ import { join as join94 } from "node:path";
93840
94714
  import { readFileSync as readFileSync81 } from "node:fs";
93841
94715
  var IPC_CONNECT_TIMEOUT_MS = 5000;
93842
94716
  function gatewaySocketPath() {
93843
- return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join93(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join93(homedir53(), ".claude", "channels", "telegram", "gateway.sock"));
94717
+ return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join94(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join94(homedir53(), ".claude", "channels", "telegram", "gateway.sock"));
93844
94718
  }
93845
94719
  function fail5(msg, code = 1) {
93846
94720
  console.error(msg);
@@ -93914,7 +94788,7 @@ init_helpers();
93914
94788
  var import_yaml25 = __toESM(require_dist(), 1);
93915
94789
  import { existsSync as existsSync94, readdirSync as readdirSync36, readFileSync as readFileSync82, statSync as statSync51 } from "node:fs";
93916
94790
  import { homedir as homedir54 } from "node:os";
93917
- import { join as join94, resolve as resolve56 } from "node:path";
94791
+ import { join as join95, resolve as resolve56 } from "node:path";
93918
94792
  var PERSONAL_PREFIX2 = "personal-";
93919
94793
  var BUNDLED_SUBDIR = "_bundled";
93920
94794
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
@@ -93928,7 +94802,7 @@ function defaultBundledRoot2() {
93928
94802
  return resolve56(homedir54(), ".switchroom/skills/_bundled");
93929
94803
  }
93930
94804
  function readSkillFrontmatter(skillDir) {
93931
- const mdPath = join94(skillDir, "SKILL.md");
94805
+ const mdPath = join95(skillDir, "SKILL.md");
93932
94806
  if (!existsSync94(mdPath))
93933
94807
  return null;
93934
94808
  let content;
@@ -93961,7 +94835,7 @@ function readSkillFrontmatter(skillDir) {
93961
94835
  return { fm: parsed };
93962
94836
  }
93963
94837
  function statSkillMd(skillDir) {
93964
- const mdPath = join94(skillDir, "SKILL.md");
94838
+ const mdPath = join95(skillDir, "SKILL.md");
93965
94839
  try {
93966
94840
  const st = statSync51(mdPath);
93967
94841
  return { size: st.size, mtime: st.mtime.toISOString() };
@@ -93972,7 +94846,7 @@ function statSkillMd(skillDir) {
93972
94846
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
93973
94847
  if (!AGENT_NAME_RE3.test(agent))
93974
94848
  return [];
93975
- const skillsDir = join94(agentsRoot, agent, ".claude/skills");
94849
+ const skillsDir = join95(agentsRoot, agent, ".claude/skills");
93976
94850
  if (!existsSync94(skillsDir))
93977
94851
  return [];
93978
94852
  const out = [];
@@ -93985,7 +94859,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
93985
94859
  for (const ent of entries) {
93986
94860
  if (!ent.startsWith(PERSONAL_PREFIX2))
93987
94861
  continue;
93988
- const dirPath = join94(skillsDir, ent);
94862
+ const dirPath = join95(skillsDir, ent);
93989
94863
  try {
93990
94864
  if (!statSync51(dirPath).isDirectory())
93991
94865
  continue;
@@ -94025,7 +94899,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
94025
94899
  continue;
94026
94900
  if (ent.startsWith("."))
94027
94901
  continue;
94028
- const dirPath = join94(sharedRoot, ent);
94902
+ const dirPath = join95(sharedRoot, ent);
94029
94903
  try {
94030
94904
  if (!statSync51(dirPath).isDirectory())
94031
94905
  continue;
@@ -94061,7 +94935,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
94061
94935
  for (const ent of entries) {
94062
94936
  if (ent.startsWith("."))
94063
94937
  continue;
94064
- const dirPath = join94(bundledRoot, ent);
94938
+ const dirPath = join95(bundledRoot, ent);
94065
94939
  try {
94066
94940
  if (!statSync51(dirPath).isDirectory())
94067
94941
  continue;
@@ -94218,7 +95092,7 @@ import {
94218
95092
  copyFileSync as copyFileSync13
94219
95093
  } from "node:fs";
94220
95094
  import { homedir as homedir55 } from "node:os";
94221
- import { join as join95 } from "node:path";
95095
+ import { join as join96 } from "node:path";
94222
95096
  import { spawnSync as spawnSync20 } from "node:child_process";
94223
95097
 
94224
95098
  // src/cli/singleton-stale-cleanup.ts
@@ -94567,7 +95441,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
94567
95441
  return resolved;
94568
95442
  }
94569
95443
  function resolveHostdSkillsTarget(hostHome) {
94570
- const skillsPath = join95(hostHome, ".switchroom", "skills");
95444
+ const skillsPath = join96(hostHome, ".switchroom", "skills");
94571
95445
  let st;
94572
95446
  try {
94573
95447
  st = lstatSync13(skillsPath);
@@ -94591,10 +95465,10 @@ function resolveHostdSkillsTarget(hostHome) {
94591
95465
  return target;
94592
95466
  }
94593
95467
  function hostdDir() {
94594
- return join95(homedir55(), ".switchroom", "hostd");
95468
+ return join96(homedir55(), ".switchroom", "hostd");
94595
95469
  }
94596
95470
  function hostdComposePath() {
94597
- return join95(hostdDir(), "docker-compose.yml");
95471
+ return join96(hostdDir(), "docker-compose.yml");
94598
95472
  }
94599
95473
  function backupExistingCompose() {
94600
95474
  const p = hostdComposePath();
@@ -94723,7 +95597,7 @@ function doStatus() {
94723
95597
  for (const name of readdirSync37(dir)) {
94724
95598
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
94725
95599
  continue;
94726
- const sockPath = join95(dir, name, "sock");
95600
+ const sockPath = join96(dir, name, "sock");
94727
95601
  if (existsSync96(sockPath)) {
94728
95602
  const st = statSync52(sockPath);
94729
95603
  if ((st.mode & 61440) === 49152) {
@@ -94817,7 +95691,7 @@ init_helpers();
94817
95691
  init_operator_uid();
94818
95692
  import { chownSync as chownSync9, existsSync as existsSync97, mkdirSync as mkdirSync55, writeFileSync as writeFileSync36, copyFileSync as copyFileSync14 } from "node:fs";
94819
95693
  import { homedir as homedir56 } from "node:os";
94820
- import { join as join96 } from "node:path";
95694
+ import { join as join97 } from "node:path";
94821
95695
  import { spawnSync as spawnSync21 } from "node:child_process";
94822
95696
  function resolveWebImageTag(explicitTag, release) {
94823
95697
  if (explicitTag)
@@ -94917,10 +95791,10 @@ services:
94917
95791
  `;
94918
95792
  }
94919
95793
  function webdDir() {
94920
- return join96(homedir56(), ".switchroom", "web");
95794
+ return join97(homedir56(), ".switchroom", "web");
94921
95795
  }
94922
95796
  function webdComposePath() {
94923
- return join96(webdDir(), "docker-compose.yml");
95797
+ return join97(webdDir(), "docker-compose.yml");
94924
95798
  }
94925
95799
  function backupExistingCompose2() {
94926
95800
  const p = webdComposePath();
@@ -95080,9 +95954,9 @@ function registerWebdCommand(program3) {
95080
95954
  // src/cli/host-repair.ts
95081
95955
  init_source();
95082
95956
  import { homedir as homedir57 } from "node:os";
95083
- import { join as join97 } from "node:path";
95957
+ import { join as join98 } from "node:path";
95084
95958
  var ARTIFACT_ALLOWLIST = {
95085
- dockerComposePluginDir: (home2) => join97(home2, ".docker", "cli-plugins", "docker-compose"),
95959
+ dockerComposePluginDir: (home2) => join98(home2, ".docker", "cli-plugins", "docker-compose"),
95086
95960
  stateSentinel: "/state"
95087
95961
  };
95088
95962
  function isStateBogusAutoDir(probe2) {