switchroom 0.19.30 → 0.19.32

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 (51) hide show
  1. package/dist/cli/switchroom.js +1121 -584
  2. package/dist/host-control/main.js +151 -73
  3. package/package.json +1 -1
  4. package/profiles/_base/cron-session.sh.hbs +5 -1
  5. package/profiles/_base/start.sh.hbs +15 -1
  6. package/telegram-plugin/dist/bridge/bridge.js +3 -0
  7. package/telegram-plugin/dist/gateway/gateway.js +1660 -677
  8. package/telegram-plugin/dist/server.js +3 -0
  9. package/telegram-plugin/edit-flood-fuse.ts +70 -20
  10. package/telegram-plugin/gateway/boot-beacon.ts +364 -0
  11. package/telegram-plugin/gateway/boot-sweep-gate.ts +20 -15
  12. package/telegram-plugin/gateway/gateway.ts +87 -88
  13. package/telegram-plugin/gateway/inbound-spool.ts +39 -0
  14. package/telegram-plugin/gateway/narrative-lane.ts +12 -0
  15. package/telegram-plugin/gateway/obligation-store.ts +28 -0
  16. package/telegram-plugin/gateway/stale-pin-sweep-store.ts +221 -0
  17. package/telegram-plugin/gateway/stale-pin-sweep-wiring.ts +211 -0
  18. package/telegram-plugin/gateway/stale-pin-sweep.test.ts +804 -0
  19. package/telegram-plugin/gateway/stale-pin-sweep.ts +1146 -0
  20. package/telegram-plugin/gateway/status-pin-retarget.ts +15 -2
  21. package/telegram-plugin/gateway/status-pin-store.ts +33 -11
  22. package/telegram-plugin/gateway/stream-render.ts +578 -321
  23. package/telegram-plugin/registry/turns-schema.ts +21 -1
  24. package/telegram-plugin/retry-api-call.ts +46 -21
  25. package/telegram-plugin/session-tail.ts +13 -0
  26. package/telegram-plugin/shared/bot-runtime.ts +61 -17
  27. package/telegram-plugin/shared/gw-trace-gate.ts +18 -2
  28. package/telegram-plugin/tests/activity-card-wiring.test.ts +7 -7
  29. package/telegram-plugin/tests/activity-drain-fuse-drop-not-failure.test.ts +324 -0
  30. package/telegram-plugin/tests/agent-card-result-footer.test.ts +193 -0
  31. package/telegram-plugin/tests/boot-beacon.test.ts +462 -0
  32. package/telegram-plugin/tests/boot-pin-sweep-wiring.test.ts +6 -6
  33. package/telegram-plugin/tests/boot-sweep-gate.test.ts +42 -31
  34. package/telegram-plugin/tests/inbound-delivery-machine-dispatch.test.ts +2 -0
  35. package/telegram-plugin/tests/inbound-spool-progress.test.ts +2 -0
  36. package/telegram-plugin/tests/inbound-spool.test.ts +134 -5
  37. package/telegram-plugin/tests/narrative-lane-golden.test.ts +28 -0
  38. package/telegram-plugin/tests/obligation-determinism.test.ts +2 -0
  39. package/telegram-plugin/tests/obligation-store.test.ts +67 -1
  40. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
  41. package/telegram-plugin/tests/status-pin-store.test.ts +26 -5
  42. package/telegram-plugin/tests/stream-render-golden.test.ts +20 -5
  43. package/telegram-plugin/tests/tg-post-logger-error-shape.test.ts +161 -0
  44. package/telegram-plugin/tests/turn-mint-defers-until-dequeue.test.ts +196 -0
  45. package/telegram-plugin/tests/turn-mint-harness.ts +155 -0
  46. package/telegram-plugin/tests/turn-supersede-finalizes-prior-card.test.ts +124 -0
  47. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +30 -0
  48. package/telegram-plugin/tool-activity-summary.ts +104 -38
  49. package/telegram-plugin/worker-activity-feed.ts +33 -16
  50. package/telegram-plugin/gateway/dm-pin-sweep.test.ts +0 -251
  51. package/telegram-plugin/gateway/dm-pin-sweep.ts +0 -178
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.19.30", COMMIT_SHA = "efe8e2a7";
2123
+ var VERSION = "0.19.32", COMMIT_SHA = "5ccce0a7";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -16154,6 +16154,7 @@ var init_hindsight_context_budget = __esm(() => {
16154
16154
  var exports_atomic = {};
16155
16155
  __export(exports_atomic, {
16156
16156
  writeConfigFileSync: () => writeConfigFileSync,
16157
+ fsyncPathSync: () => fsyncPathSync,
16157
16158
  atomicWriteJsonSync: () => atomicWriteJsonSync,
16158
16159
  atomicWriteFileSync: () => atomicWriteFileSync
16159
16160
  });
@@ -16195,6 +16196,14 @@ function atomicWriteFileSync(destPath, contents, modeOrOpts = 384) {
16195
16196
  throw err;
16196
16197
  }
16197
16198
  }
16199
+ function fsyncPathSync(path) {
16200
+ const fd = openSync2(path, constants.O_RDONLY);
16201
+ try {
16202
+ fsyncSync(fd);
16203
+ } finally {
16204
+ closeSync2(fd);
16205
+ }
16206
+ }
16198
16207
  function atomicWriteJsonSync(destPath, value, mode = 384) {
16199
16208
  atomicWriteFileSync(destPath, JSON.stringify(value, null, 2) + `
16200
16209
  `, mode);
@@ -32499,8 +32508,41 @@ import {
32499
32508
  realpathSync as realpathSync3,
32500
32509
  statSync as statSync9
32501
32510
  } from "node:fs";
32511
+ import { homedir as homedir7 } from "node:os";
32502
32512
  import { join as join15 } from "node:path";
32503
- function resolveOperatorUid() {
32513
+ function operatorHomeCandidates(env2, home2) {
32514
+ const roots = [env2.SWITCHROOM_HOST_HOME?.trim(), home2, CONTAINER_OPERATOR_HOME];
32515
+ const out = [];
32516
+ for (const r of roots) {
32517
+ if (!r || r.length === 0)
32518
+ continue;
32519
+ const dir = join15(r, ".switchroom");
32520
+ if (!out.includes(dir))
32521
+ out.push(dir);
32522
+ }
32523
+ return out;
32524
+ }
32525
+ function resolveOperatorUidFromOwnership(deps = {}) {
32526
+ const env2 = deps.env ?? process.env;
32527
+ const home2 = deps.home ?? homedir7();
32528
+ const exists = deps.exists ?? ((p) => existsSync20(p));
32529
+ const statUid = deps.statUid ?? ((p) => {
32530
+ try {
32531
+ return statSync9(p).uid;
32532
+ } catch {
32533
+ return;
32534
+ }
32535
+ });
32536
+ for (const dir of operatorHomeCandidates(env2, home2)) {
32537
+ if (!exists(join15(dir, SWITCHROOM_HOME_MARKER)))
32538
+ continue;
32539
+ const uid = statUid(dir);
32540
+ if (uid !== undefined && Number.isFinite(uid) && uid > 0)
32541
+ return uid;
32542
+ }
32543
+ return;
32544
+ }
32545
+ function resolveOperatorUid(ownershipDeps = {}) {
32504
32546
  const sudoUid = process.env.SUDO_UID;
32505
32547
  if (sudoUid !== undefined) {
32506
32548
  const parsed = parseInt(sudoUid, 10);
@@ -32518,7 +32560,7 @@ function resolveOperatorUid() {
32518
32560
  if (Number.isFinite(parsed) && parsed > 0)
32519
32561
  return parsed;
32520
32562
  }
32521
- return;
32563
+ return resolveOperatorUidFromOwnership(ownershipDeps);
32522
32564
  }
32523
32565
  function operatorOwnedPaths(home2) {
32524
32566
  const root = join15(home2, ".switchroom");
@@ -32597,12 +32639,13 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
32597
32639
  }
32598
32640
  return chowned;
32599
32641
  }
32642
+ var CONTAINER_OPERATOR_HOME = "/host-home", SWITCHROOM_HOME_MARKER = "switchroom.yaml";
32600
32643
  var init_operator_uid = () => {};
32601
32644
 
32602
32645
  // src/cli/write-compose.ts
32603
32646
  import { chownSync as chownSync2 } from "node:fs";
32604
32647
  import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
32605
- import { homedir as homedir7 } from "node:os";
32648
+ import { homedir as homedir8 } from "node:os";
32606
32649
  import { basename as basename4, dirname as dirname9, join as join16 } from "node:path";
32607
32650
  function agentHadLiteLLMRouting(composeContent, agentName) {
32608
32651
  const lines = composeContent.split(`
@@ -32713,7 +32756,7 @@ function resolveHostHomeForCompose() {
32713
32756
 
32714
32757
  ` + `Recovery: run \`switchroom apply\`/\`update\` from the HOST shell, or via hostd (both set SWITCHROOM_HOST_HOME). Do NOT deploy via an ad-hoc ` + `\`docker run <agent-image> switchroom \u2026\` that mounts the operator home.`);
32715
32758
  }
32716
- return homedir7();
32759
+ return homedir8();
32717
32760
  }
32718
32761
  async function computeComposeContent(opts) {
32719
32762
  const release = resolveRelease({ override: opts.releaseOverride, root: opts.config.release });
@@ -32734,7 +32777,7 @@ async function computeComposeContent(opts) {
32734
32777
  buildMode: opts.buildMode ?? "pull",
32735
32778
  buildContext: opts.buildContext,
32736
32779
  homeDir: resolveHostHomeForCompose(),
32737
- probeHomeDir: homedir7(),
32780
+ probeHomeDir: homedir8(),
32738
32781
  switchroomConfigPath: resolvedConfigPath,
32739
32782
  operatorUid
32740
32783
  });
@@ -32852,7 +32895,7 @@ var init_compose_env = () => {};
32852
32895
  // src/agents/docker-fleet.ts
32853
32896
  import { resolve as resolve14 } from "node:path";
32854
32897
  import { mkdirSync as mkdirSync14, writeFileSync as writeFileSync7 } from "node:fs";
32855
- import { homedir as homedir8 } from "node:os";
32898
+ import { homedir as homedir9 } from "node:os";
32856
32899
  import { execFileSync as execFileSync8 } from "node:child_process";
32857
32900
  function resolveSwitchroomHome(explicit) {
32858
32901
  if (explicit && explicit.length > 0)
@@ -32876,7 +32919,7 @@ function bringUpAgentService(opts) {
32876
32919
  }
32877
32920
  const compose = opts.generateComposeContent?.() ?? generateCompose({
32878
32921
  config: opts.config,
32879
- homeDir: homedir8(),
32922
+ homeDir: homedir9(),
32880
32923
  switchroomConfigPath
32881
32924
  });
32882
32925
  const composePath = resolve14(composeDir, "docker-compose.yml");
@@ -35074,7 +35117,7 @@ __export(exports_client2, {
35074
35117
  AuthBrokerClient: () => AuthBrokerClient
35075
35118
  });
35076
35119
  import * as net2 from "node:net";
35077
- import { homedir as homedir10 } from "node:os";
35120
+ import { homedir as homedir11 } from "node:os";
35078
35121
  import { randomUUID } from "node:crypto";
35079
35122
  import { join as join25 } from "node:path";
35080
35123
  function reviveDate(v) {
@@ -35085,7 +35128,7 @@ function reviveDate(v) {
35085
35128
  const d = new Date(v);
35086
35129
  return Number.isNaN(d.getTime()) ? null : d;
35087
35130
  }
35088
- function operatorSocketPath(home2 = homedir10()) {
35131
+ function operatorSocketPath(home2 = homedir11()) {
35089
35132
  return join25(home2, ".switchroom", "state", "auth-broker-operator", "sock");
35090
35133
  }
35091
35134
  function resolveAuthBrokerSocketPath(opts) {
@@ -35466,7 +35509,7 @@ import {
35466
35509
  rmSync as rmSync11,
35467
35510
  statSync as statSync19
35468
35511
  } from "node:fs";
35469
- import { homedir as homedir12 } from "node:os";
35512
+ import { homedir as homedir13 } from "node:os";
35470
35513
  import { join as join27, resolve as resolve24 } from "node:path";
35471
35514
  function accountsRootOverride() {
35472
35515
  const v = process.env.SWITCHROOM_ACCOUNTS_DIR;
@@ -35474,19 +35517,19 @@ function accountsRootOverride() {
35474
35517
  return v;
35475
35518
  return;
35476
35519
  }
35477
- function accountsRoot(home2 = homedir12()) {
35520
+ function accountsRoot(home2 = homedir13()) {
35478
35521
  return accountsRootOverride() ?? resolve24(home2, ".switchroom", "accounts");
35479
35522
  }
35480
- function accountDir(label, home2 = homedir12()) {
35523
+ function accountDir(label, home2 = homedir13()) {
35481
35524
  return join27(accountsRoot(home2), label);
35482
35525
  }
35483
- function accountCredentialsPath(label, home2 = homedir12()) {
35526
+ function accountCredentialsPath(label, home2 = homedir13()) {
35484
35527
  return join27(accountDir(label, home2), "credentials.json");
35485
35528
  }
35486
- function accountMetaPath(label, home2 = homedir12()) {
35529
+ function accountMetaPath(label, home2 = homedir13()) {
35487
35530
  return join27(accountDir(label, home2), "meta.json");
35488
35531
  }
35489
- function listAccounts(home2 = homedir12()) {
35532
+ function listAccounts(home2 = homedir13()) {
35490
35533
  const root = accountsRoot(home2);
35491
35534
  if (!existsSync34(root))
35492
35535
  return [];
@@ -35502,7 +35545,7 @@ function listAccounts(home2 = homedir12()) {
35502
35545
  return [];
35503
35546
  }
35504
35547
  }
35505
- function readAccountCredentials(label, home2 = homedir12()) {
35548
+ function readAccountCredentials(label, home2 = homedir13()) {
35506
35549
  const p = accountCredentialsPath(label, home2);
35507
35550
  if (!existsSync34(p))
35508
35551
  return null;
@@ -35512,7 +35555,7 @@ function readAccountCredentials(label, home2 = homedir12()) {
35512
35555
  return null;
35513
35556
  }
35514
35557
  }
35515
- function readAccountMeta(label, home2 = homedir12()) {
35558
+ function readAccountMeta(label, home2 = homedir13()) {
35516
35559
  const p = accountMetaPath(label, home2);
35517
35560
  if (!existsSync34(p))
35518
35561
  return null;
@@ -35522,7 +35565,7 @@ function readAccountMeta(label, home2 = homedir12()) {
35522
35565
  return null;
35523
35566
  }
35524
35567
  }
35525
- function accountHealth(label, now = Date.now(), home2 = homedir12()) {
35568
+ function accountHealth(label, now = Date.now(), home2 = homedir13()) {
35526
35569
  const creds = readAccountCredentials(label, home2);
35527
35570
  if (!creds?.claudeAiOauth?.accessToken)
35528
35571
  return "missing-credentials";
@@ -35538,7 +35581,7 @@ function accountHealth(label, now = Date.now(), home2 = homedir12()) {
35538
35581
  }
35539
35582
  return "healthy";
35540
35583
  }
35541
- function getAccountInfos(now = Date.now(), home2 = homedir12()) {
35584
+ function getAccountInfos(now = Date.now(), home2 = homedir13()) {
35542
35585
  return listAccounts(home2).map((label) => {
35543
35586
  const creds = readAccountCredentials(label, home2);
35544
35587
  const meta = readAccountMeta(label, home2);
@@ -36182,12 +36225,12 @@ var init_disconnect = __esm(() => {
36182
36225
 
36183
36226
  // src/vault/approvals/client.ts
36184
36227
  import { existsSync as existsSync35 } from "node:fs";
36185
- import { homedir as homedir13 } from "node:os";
36228
+ import { homedir as homedir14 } from "node:os";
36186
36229
  import { join as join28 } from "node:path";
36187
- function kernelOperatorSocketPath(home2 = homedir13()) {
36230
+ function kernelOperatorSocketPath(home2 = homedir14()) {
36188
36231
  return join28(home2, ".switchroom", "state", "kernel-operator", "sock");
36189
36232
  }
36190
- function resolveKernelOperatorSocket(home2 = homedir13()) {
36233
+ function resolveKernelOperatorSocket(home2 = homedir14()) {
36191
36234
  const p = kernelOperatorSocketPath(home2);
36192
36235
  return existsSync35(p) ? p : null;
36193
36236
  }
@@ -37999,7 +38042,7 @@ var init_audit_hashchain = () => {};
37999
38042
 
38000
38043
  // src/vault/broker/test-isolation-guard.ts
38001
38044
  import { mkdtempSync as mkdtempSync4 } from "node:fs";
38002
- import { homedir as homedir15, tmpdir as tmpdir3 } from "node:os";
38045
+ import { homedir as homedir16, tmpdir as tmpdir3 } from "node:os";
38003
38046
  import { join as join34, resolve as resolve28, sep } from "node:path";
38004
38047
  function isTestRuntime() {
38005
38048
  return process.env.VITEST !== undefined;
@@ -38008,7 +38051,7 @@ function escapeHatchSet() {
38008
38051
  return process.env.SWITCHROOM_ALLOW_PROD_VAULT_IN_TEST === "1";
38009
38052
  }
38010
38053
  function realSwitchroomHome() {
38011
- return resolve28(homedir15(), ".switchroom");
38054
+ return resolve28(homedir16(), ".switchroom");
38012
38055
  }
38013
38056
  function isUnderRealSwitchroomHome(p) {
38014
38057
  const home2 = realSwitchroomHome();
@@ -39190,9 +39233,9 @@ var init_audit_rotation_config = __esm(() => {
39190
39233
 
39191
39234
  // src/host-control/audit-reader.ts
39192
39235
  import { closeSync as closeSync12, existsSync as existsSync51, openSync as openSync12, readSync as readSync3, statSync as statSync33 } from "node:fs";
39193
- import { homedir as homedir22 } from "node:os";
39236
+ import { homedir as homedir23 } from "node:os";
39194
39237
  import { join as join47 } from "node:path";
39195
- function defaultAuditLogPath2(home2 = homedir22()) {
39238
+ function defaultAuditLogPath2(home2 = homedir23()) {
39196
39239
  return join47(home2, ".switchroom", "host-control-audit.log");
39197
39240
  }
39198
39241
  function auditRotation() {
@@ -39351,6 +39394,9 @@ function parseAuditLine2(line) {
39351
39394
  entry.failed_step = o.failed_step;
39352
39395
  if (typeof o.failed_agent === "string")
39353
39396
  entry.failed_agent = o.failed_agent;
39397
+ if (Array.isArray(o.drifted) && o.drifted.every((x) => typeof x === "string")) {
39398
+ entry.drifted = o.drifted;
39399
+ }
39354
39400
  if (typeof o.agent === "string")
39355
39401
  entry.agent = o.agent;
39356
39402
  if (typeof o.n === "number")
@@ -44237,8 +44283,8 @@ var init_client5 = __esm(() => {
44237
44283
  // src/web/fleet-health-read.ts
44238
44284
  import { readFileSync as readFileSync51 } from "node:fs";
44239
44285
  import { resolve as resolve32 } from "node:path";
44240
- import { homedir as homedir25 } from "node:os";
44241
- function fleetHealthLedgerPath(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir25()) {
44286
+ import { homedir as homedir26 } from "node:os";
44287
+ function fleetHealthLedgerPath(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir26()) {
44242
44288
  return resolve32(home2, ".switchroom", "fleet-health", "ledger.json");
44243
44289
  }
44244
44290
  function readFleetHealth(path6) {
@@ -45484,6 +45530,241 @@ var init_doctor_hnsw_index = __esm(() => {
45484
45530
  ];
45485
45531
  });
45486
45532
 
45533
+ // src/cli/doctor-observation-scopes.ts
45534
+ function computeScopeSaturation(scopes, cap, warnRatio = OBSERVATION_SCOPE_WARN_RATIO) {
45535
+ if (cap < 0)
45536
+ return [];
45537
+ const sets = scopes.map((s) => ({ tags: s.tags, set: new Set(s.tags), count: s.count }));
45538
+ const postings = new Map;
45539
+ sets.forEach((entry, i) => {
45540
+ for (const tag of entry.set) {
45541
+ const list = postings.get(tag);
45542
+ if (list)
45543
+ list.push(i);
45544
+ else
45545
+ postings.set(tag, [i]);
45546
+ }
45547
+ });
45548
+ const out = [];
45549
+ for (const entry of sets) {
45550
+ if (entry.set.size === 0)
45551
+ continue;
45552
+ let candidates;
45553
+ for (const tag of entry.set) {
45554
+ const list = postings.get(tag);
45555
+ if (list && (candidates === undefined || list.length < candidates.length)) {
45556
+ candidates = list;
45557
+ }
45558
+ }
45559
+ let effective = 0;
45560
+ for (const idx of candidates ?? []) {
45561
+ const other = sets[idx];
45562
+ if (other === undefined)
45563
+ continue;
45564
+ let contains = true;
45565
+ for (const tag of entry.set) {
45566
+ if (!other.set.has(tag)) {
45567
+ contains = false;
45568
+ break;
45569
+ }
45570
+ }
45571
+ if (contains)
45572
+ effective += other.count;
45573
+ }
45574
+ const status = cap > 0 && effective >= cap ? "fail" : cap > 0 && effective >= cap * warnRatio ? "warn" : "ok";
45575
+ out.push({ tags: [...entry.tags], exact: entry.count, effective, cap, status });
45576
+ }
45577
+ out.sort((a, b) => b.effective - a.effective);
45578
+ return out;
45579
+ }
45580
+ function describeScope(bankId, s) {
45581
+ return `${bankId}/${s.tags.join(",")} (${s.effective}/${s.cap})`;
45582
+ }
45583
+ function classifyObservationScopeSaturation(reports) {
45584
+ const name = OBSERVATION_SCOPE_CHECK_NAME;
45585
+ if (reports.length === 0) {
45586
+ return { name, status: "skip", detail: "no banks to inspect" };
45587
+ }
45588
+ const unreadable = reports.filter((r) => !r.ok);
45589
+ const readable = reports.filter((r) => r.ok);
45590
+ if (readable.length === 0) {
45591
+ return {
45592
+ name,
45593
+ status: "skip",
45594
+ detail: `could not read observation scopes for ${unreadable.length} bank(s): ` + unreadable.map((r) => `${r.bankId} (${r.reason ?? "unknown"})`).join(", ")
45595
+ };
45596
+ }
45597
+ const unjudged = readable.filter((r) => r.hasScopeLimitRules || !r.observationsEnabled);
45598
+ const judged = readable.filter((r) => !r.hasScopeLimitRules && r.observationsEnabled);
45599
+ const notes = [];
45600
+ if (unreadable.length > 0) {
45601
+ notes.push(`${unreadable.length} bank(s) unreadable: ` + unreadable.map((r) => `${r.bankId} (${r.reason ?? "unknown"})`).join(", "));
45602
+ }
45603
+ const withRules = unjudged.filter((r) => r.hasScopeLimitRules);
45604
+ if (withRules.length > 0) {
45605
+ notes.push(`${withRules.length} bank(s) not judged \u2014 observation_scope_limits sets a ` + `per-scope cap this check does not resolve: ${withRules.map((r) => r.bankId).join(", ")}`);
45606
+ }
45607
+ const disabled = unjudged.filter((r) => !r.hasScopeLimitRules && !r.observationsEnabled);
45608
+ if (disabled.length > 0) {
45609
+ notes.push(`${disabled.length} bank(s) have observations disabled: ` + disabled.map((r) => r.bankId).join(", "));
45610
+ }
45611
+ const suffix = notes.length > 0 ? ` \u00b7 ${notes.join(" \u00b7 ")}` : "";
45612
+ if (judged.length === 0) {
45613
+ return { name, status: "skip", detail: `no bank could be judged${suffix}` };
45614
+ }
45615
+ const closed = judged.filter((r) => r.cap === 0);
45616
+ const unlimited = judged.filter((r) => r.cap < 0);
45617
+ const capped = judged.filter((r) => r.cap > 0);
45618
+ const failing = capped.flatMap((r) => r.saturated.filter((s) => s.status === "fail").map((s) => ({ report: r, scope: s })));
45619
+ const warning = capped.flatMap((r) => r.saturated.filter((s) => s.status === "warn").map((s) => ({ report: r, scope: s })));
45620
+ failing.sort((a, b) => b.scope.effective - a.scope.effective);
45621
+ warning.sort((a, b) => b.scope.effective - a.scope.effective);
45622
+ const backlog = (rows) => {
45623
+ const seen = new Map;
45624
+ for (const { report } of rows) {
45625
+ if (report.pendingConsolidation != null && !seen.has(report.bankId)) {
45626
+ seen.set(report.bankId, report.pendingConsolidation);
45627
+ }
45628
+ }
45629
+ if (seen.size === 0)
45630
+ return "";
45631
+ const parts = [...seen].sort((a, b) => b[1] - a[1]).map(([bank, pending]) => `${bank} ${pending.toLocaleString()}`);
45632
+ return ` \u00b7 memories pending consolidation: ${parts.join(", ")}`;
45633
+ };
45634
+ const list = (rows) => {
45635
+ const shown = rows.slice(0, OBSERVATION_SCOPE_MAX_LISTED).map(({ report, scope }) => describeScope(report.bankId, scope)).join("; ");
45636
+ const rest = rows.length - Math.min(rows.length, OBSERVATION_SCOPE_MAX_LISTED);
45637
+ return rest > 0 ? `${shown}; +${rest} more` : shown;
45638
+ };
45639
+ const fix = "Consolidation keeps running against a full scope \u2014 it recalls, spends an " + "extraction-model call, and can only apply updates/deletes " + "(engine/consolidation/consolidator.py:1604-1613), so every pass on that " + "scope is paid for and its creates are discarded. Remedy: raise the cap, " + "via `hindsight.env.HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE` in " + "switchroom.yaml (fleet-wide) or `max_observations_per_scope` on the bank " + "(PATCH /v1/default/banks/<bank>/config). Confirm the counts first with " + "GET /v1/default/banks/<bank>/observations/scopes \u2014 but note they are EXACT " + "tag sets, while the engine counts by CONTAINMENT (`tags @> scope`), so a " + "scope's child scopes count toward it and its true number is higher than " + "that endpoint shows. Retiring a scope instead is NOT a single call: " + "DELETE /v1/default/banks/<bank>/observations clears the WHOLE bank's " + "observations, so enumerate the scope first " + "(GET /graph?type=observation&tags=<scope>&tags_match=exact) and delete " + "per memory.";
45640
+ if (failing.length > 0) {
45641
+ return {
45642
+ name,
45643
+ status: "fail",
45644
+ detail: `${failing.length} observation scope(s) at or over the cap \u2014 consolidation ` + `output is being discarded on them right now: ${list(failing)}` + (warning.length > 0 ? ` \u00b7 ${warning.length} more above ${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}%` : "") + backlog([...failing, ...warning]) + suffix,
45645
+ fix
45646
+ };
45647
+ }
45648
+ if (warning.length > 0) {
45649
+ return {
45650
+ name,
45651
+ status: "warn",
45652
+ detail: `${warning.length} observation scope(s) above ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap and still writable: ` + `${list(warning)}` + backlog(warning) + suffix,
45653
+ fix
45654
+ };
45655
+ }
45656
+ const totalScopes = capped.reduce((n, r) => n + r.scopeCount, 0);
45657
+ const detailBase = capped.length === 0 ? `no bank has a positive observation cap` : `${totalScopes} scope(s) across ${capped.length} bank(s) below ` + `${Math.round(OBSERVATION_SCOPE_WARN_RATIO * 100)}% of the cap`;
45658
+ const extra = [];
45659
+ if (unlimited.length > 0) {
45660
+ extra.push(`${unlimited.length} bank(s) uncapped (-1)`);
45661
+ }
45662
+ if (closed.length > 0) {
45663
+ extra.push(`${closed.length} bank(s) deliberately closed to new observations (cap 0): ` + closed.map((r) => r.bankId).join(", "));
45664
+ }
45665
+ return {
45666
+ name,
45667
+ status: "ok",
45668
+ detail: detailBase + (extra.length > 0 ? ` \u00b7 ${extra.join(" \u00b7 ")}` : "") + suffix
45669
+ };
45670
+ }
45671
+ async function getJson2(url, opts) {
45672
+ const fetchImpl = opts?.fetchImpl ?? fetch;
45673
+ const timeoutMs = opts?.timeoutMs ?? 15000;
45674
+ const controller = new AbortController;
45675
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
45676
+ try {
45677
+ const resp = await fetchImpl(url, { signal: controller.signal });
45678
+ clearTimeout(timeout);
45679
+ if (!resp.ok)
45680
+ return { ok: false, reason: `HTTP ${resp.status}` };
45681
+ return { ok: true, data: await resp.json() };
45682
+ } catch (err) {
45683
+ clearTimeout(timeout);
45684
+ if (err.name === "AbortError")
45685
+ return { ok: false, reason: "Timeout" };
45686
+ return { ok: false, reason: String(err.message ?? err) };
45687
+ }
45688
+ }
45689
+ async function inspectBankScopes(apiUrl, bankId, opts) {
45690
+ const base = hindsightRestBase(apiUrl);
45691
+ const bank = encodeURIComponent(bankId);
45692
+ const fail4 = (reason) => ({
45693
+ bankId,
45694
+ ok: false,
45695
+ reason,
45696
+ cap: -1,
45697
+ observationsEnabled: false,
45698
+ hasScopeLimitRules: false,
45699
+ scopeCount: 0,
45700
+ saturated: [],
45701
+ pendingConsolidation: null
45702
+ });
45703
+ const cfg = await getJson2(`${base}/v1/default/banks/${bank}/config`, opts);
45704
+ if (!cfg.ok)
45705
+ return fail4(cfg.reason);
45706
+ const config = cfg.data?.config;
45707
+ if (config == null || typeof config !== "object")
45708
+ return fail4("Unexpected shape");
45709
+ const rawCap = config["max_observations_per_scope"];
45710
+ if (typeof rawCap !== "number" || !Number.isFinite(rawCap)) {
45711
+ return fail4("Unexpected shape");
45712
+ }
45713
+ const rules = config["observation_scope_limits"];
45714
+ const hasScopeLimitRules = Array.isArray(rules) && rules.length > 0;
45715
+ const observationsEnabled = config["enable_observations"] !== false;
45716
+ const scopesResp = await getJson2(`${base}/v1/default/banks/${bank}/observations/scopes`, opts);
45717
+ if (!scopesResp.ok)
45718
+ return fail4(scopesResp.reason);
45719
+ const rawScopes = scopesResp.data?.scopes;
45720
+ if (!Array.isArray(rawScopes))
45721
+ return fail4("Unexpected shape");
45722
+ const scopes = [];
45723
+ for (const entry of rawScopes) {
45724
+ const tags = entry?.tags;
45725
+ const count = entry?.count;
45726
+ if (!Array.isArray(tags) || typeof count !== "number")
45727
+ continue;
45728
+ if (!tags.every((t) => typeof t === "string"))
45729
+ continue;
45730
+ scopes.push({ tags, count });
45731
+ }
45732
+ const saturated = computeScopeSaturation(scopes, rawCap).filter((s) => s.status !== "ok");
45733
+ let pendingConsolidation = null;
45734
+ if (saturated.length > 0 && !hasScopeLimitRules && observationsEnabled) {
45735
+ const stats = await getJson2(`${base}/v1/default/banks/${bank}/stats`, opts);
45736
+ if (stats.ok && typeof stats.data?.pending_consolidation === "number") {
45737
+ pendingConsolidation = stats.data.pending_consolidation;
45738
+ }
45739
+ }
45740
+ return {
45741
+ bankId,
45742
+ ok: true,
45743
+ cap: rawCap,
45744
+ observationsEnabled,
45745
+ hasScopeLimitRules,
45746
+ scopeCount: scopes.length,
45747
+ saturated,
45748
+ pendingConsolidation
45749
+ };
45750
+ }
45751
+ async function checkObservationScopeSaturation(config, apiUrl, opts) {
45752
+ const banks = new Set;
45753
+ for (const [agentName, agentConfig] of Object.entries(config.agents ?? {})) {
45754
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
45755
+ banks.add(resolved.memory?.collection ?? agentName);
45756
+ }
45757
+ for (const bank of collectProfileBanks(config))
45758
+ banks.add(bank);
45759
+ const reports = await Promise.all([...banks].map((bankId) => inspectBankScopes(apiUrl, bankId, opts)));
45760
+ return classifyObservationScopeSaturation(reports);
45761
+ }
45762
+ var OBSERVATION_SCOPE_CHECK_NAME = "hindsight observation scopes", OBSERVATION_SCOPE_WARN_RATIO = 0.8, OBSERVATION_SCOPE_MAX_LISTED = 6;
45763
+ var init_doctor_observation_scopes = __esm(() => {
45764
+ init_merge();
45765
+ init_hindsight2();
45766
+ });
45767
+
45487
45768
  // src/hindsight-watch/install-cron.ts
45488
45769
  import { existsSync as existsSync63, mkdirSync as mkdirSync35, readFileSync as readFileSync60, renameSync as renameSync15, writeFileSync as writeFileSync22 } from "node:fs";
45489
45770
  import { dirname as dirname22 } from "node:path";
@@ -45538,9 +45819,9 @@ var init_types5 = () => {};
45538
45819
 
45539
45820
  // src/hindsight-watch/state.ts
45540
45821
  import { mkdirSync as mkdirSync36, readFileSync as readFileSync61, renameSync as renameSync16, writeFileSync as writeFileSync23 } from "node:fs";
45541
- import { homedir as homedir30 } from "node:os";
45822
+ import { homedir as homedir31 } from "node:os";
45542
45823
  import { dirname as dirname23, resolve as resolve36 } from "node:path";
45543
- function defaultStatePath2(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir30()) {
45824
+ function defaultStatePath2(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir31()) {
45544
45825
  return resolve36(home2, ".switchroom", "hindsight-watch", "state.json");
45545
45826
  }
45546
45827
  function loadState(path6) {
@@ -46434,12 +46715,121 @@ var init_key_allowlist_check = __esm(() => {
46434
46715
  init_key_allowlist();
46435
46716
  });
46436
46717
 
46718
+ // src/cli/doctor-routing-mode.ts
46719
+ import { readFileSync as readFileSync62 } from "node:fs";
46720
+ import { join as join59 } from "node:path";
46721
+ function routingModePath(agentDir) {
46722
+ return join59(agentDir, ROUTING_MODE_FILENAME);
46723
+ }
46724
+ function parseRoutingModeRecord(text) {
46725
+ const line = text.split(`
46726
+ `).find((l) => l.trim().length > 0);
46727
+ if (!line)
46728
+ return null;
46729
+ const fields = {};
46730
+ for (const tok of line.trim().split(/\s+/)) {
46731
+ const eq = tok.indexOf("=");
46732
+ if (eq <= 0)
46733
+ continue;
46734
+ fields[tok.slice(0, eq)] = tok.slice(eq + 1);
46735
+ }
46736
+ if (!fields.mode || !fields.declared)
46737
+ return null;
46738
+ return {
46739
+ mode: fields.mode,
46740
+ base: fields.base ?? "",
46741
+ model: fields.model ?? "",
46742
+ litellmOk: fields.litellm_ok ?? "",
46743
+ declared: fields.declared,
46744
+ ts: fields.ts ?? ""
46745
+ };
46746
+ }
46747
+ function classifyRoutingMode(rec) {
46748
+ if (!LITELLM_DECLARED_MODES.has(rec.declared))
46749
+ return null;
46750
+ if (rec.mode !== UNTRACKED_MODE)
46751
+ return null;
46752
+ const when = rec.ts ? ` at its last boot (${rec.ts})` : "";
46753
+ return {
46754
+ status: "warn",
46755
+ detail: `landed on direct-oauth${when} but its declared routing is ` + `\`${rec.declared}\` \u2014 start.sh's missing-key fail-open stripped the ` + `LiteLLM routing env, so this agent runs UNTRACKED on direct Anthropic ` + `OAuth: no spend attribution, no guardrails` + (rec.model ? ` (model \`${rec.model}\`)` : "") + `.`
46756
+ };
46757
+ }
46758
+ function runRoutingModeChecks(config, deps = {}) {
46759
+ const results = [];
46760
+ const agents = Object.keys(config.agents ?? {});
46761
+ if (agents.length === 0)
46762
+ return results;
46763
+ const readFile2 = deps.readFile ?? ((p) => readFileSync62(p, "utf-8"));
46764
+ let agentsDir = deps.agentsDir;
46765
+ if (agentsDir === undefined) {
46766
+ try {
46767
+ agentsDir = resolveAgentsDir(config);
46768
+ } catch {
46769
+ agentsDir = undefined;
46770
+ }
46771
+ }
46772
+ if (agentsDir === undefined) {
46773
+ return [
46774
+ {
46775
+ name: "litellm routing",
46776
+ status: "warn",
46777
+ detail: "could not resolve the agents directory (no switchroom block) \u2014 " + "skipped the boot routing-mode check"
46778
+ }
46779
+ ];
46780
+ }
46781
+ for (const name of agents) {
46782
+ const path6 = routingModePath(join59(agentsDir, name));
46783
+ let text;
46784
+ try {
46785
+ text = readFile2(path6);
46786
+ } catch (err) {
46787
+ const code = err.code;
46788
+ if (code === "ENOENT")
46789
+ continue;
46790
+ results.push({
46791
+ name: `litellm routing: ${name}`,
46792
+ status: "warn",
46793
+ detail: `could not read the boot routing record at ${path6} ` + `(${code ?? err.message}) \u2014 an untracked boot would go ` + `unreported here.`,
46794
+ fix: `Check the file's ownership and mode (start.sh writes it 0644 before \`exec claude\`).`
46795
+ });
46796
+ continue;
46797
+ }
46798
+ const rec = parseRoutingModeRecord(text);
46799
+ if (!rec) {
46800
+ results.push({
46801
+ name: `litellm routing: ${name}`,
46802
+ status: "warn",
46803
+ detail: `the boot routing record at ${path6} is malformed (no \`mode=\`/` + `\`declared=\` fields) \u2014 an untracked boot would go unreported here.`,
46804
+ fix: `Delete it and restart the agent to have start.sh rewrite it: rm ${path6}`
46805
+ });
46806
+ continue;
46807
+ }
46808
+ const verdict = classifyRoutingMode(rec);
46809
+ if (!verdict)
46810
+ continue;
46811
+ results.push({
46812
+ name: `litellm routing: ${name}`,
46813
+ status: verdict.status,
46814
+ detail: verdict.detail,
46815
+ fix: FIX2
46816
+ });
46817
+ }
46818
+ return results;
46819
+ }
46820
+ var LITELLM_DECLARED_MODES, UNTRACKED_MODE = "direct-oauth", ROUTING_MODE_FILENAME = ".routing-mode", FIX2;
46821
+ var init_doctor_routing_mode = __esm(() => {
46822
+ init_loader();
46823
+ LITELLM_DECLARED_MODES = new Set(["passthrough", "router-root"]);
46824
+ FIX2 = "start.sh could not fetch this agent's `litellm/<agent>/api-key` from the " + "vault-broker. Re-run `switchroom apply` (it provisions the per-agent " + "virtual key idempotently and re-grants the agent's `secrets:` ACL), then " + "RESTART the agent \u2014 the strip is decided once at boot and is never " + "re-evaluated for the life of the session, so a fixed key does not take " + "effect until the next boot. Confirm from the agent's `.routing-mode` line " + "afterwards.";
46825
+ });
46826
+
46437
46827
  // src/cli/doctor-auth-broker.ts
46438
- import { existsSync as existsSync65, readFileSync as readFileSync62 } from "node:fs";
46828
+ import { existsSync as existsSync65, readFileSync as readFileSync63 } from "node:fs";
46439
46829
  import { createHash as createHash14 } from "node:crypto";
46440
46830
  import { spawnSync as spawnSync10 } from "node:child_process";
46441
- import { homedir as homedir31 } from "node:os";
46442
- import { join as join59 } from "node:path";
46831
+ import { homedir as homedir32 } from "node:os";
46832
+ import { join as join60 } from "node:path";
46443
46833
  function defaultDockerInspect(container, format) {
46444
46834
  try {
46445
46835
  const r = spawnSync10("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
@@ -46539,7 +46929,7 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
46539
46929
  }
46540
46930
  function checkAuthBrokerDrift(deps = {}) {
46541
46931
  const stateDir = resolveStateDir(deps);
46542
- const indexPath = join59(stateDir, "sha-index.json");
46932
+ const indexPath = join60(stateDir, "sha-index.json");
46543
46933
  if (!existsSync65(indexPath)) {
46544
46934
  return {
46545
46935
  name: "auth-broker: drift",
@@ -46549,7 +46939,7 @@ function checkAuthBrokerDrift(deps = {}) {
46549
46939
  }
46550
46940
  let index;
46551
46941
  try {
46552
- index = JSON.parse(readFileSync62(indexPath, "utf-8"));
46942
+ index = JSON.parse(readFileSync63(indexPath, "utf-8"));
46553
46943
  } catch (err) {
46554
46944
  return {
46555
46945
  name: "auth-broker: drift",
@@ -46558,7 +46948,7 @@ function checkAuthBrokerDrift(deps = {}) {
46558
46948
  fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
46559
46949
  };
46560
46950
  }
46561
- const home2 = deps.home ?? homedir31();
46951
+ const home2 = deps.home ?? homedir32();
46562
46952
  const divergent = [];
46563
46953
  const missingOnDisk = [];
46564
46954
  for (const [label, expected] of Object.entries(index)) {
@@ -46569,7 +46959,7 @@ function checkAuthBrokerDrift(deps = {}) {
46569
46959
  }
46570
46960
  let got;
46571
46961
  try {
46572
- got = sha256Hex(readFileSync62(credsPath, "utf-8"));
46962
+ got = sha256Hex(readFileSync63(credsPath, "utf-8"));
46573
46963
  } catch (err) {
46574
46964
  divergent.push(`${label} (read failed: ${err.message})`);
46575
46965
  continue;
@@ -46600,7 +46990,7 @@ function checkAuthBrokerDrift(deps = {}) {
46600
46990
  }
46601
46991
  function checkAuthBrokerThresholdViolations(deps = {}) {
46602
46992
  const stateDir = resolveStateDir(deps);
46603
- const path6 = join59(stateDir, "threshold-violations.json");
46993
+ const path6 = join60(stateDir, "threshold-violations.json");
46604
46994
  if (!existsSync65(path6)) {
46605
46995
  return {
46606
46996
  name: "auth-broker: threshold violations",
@@ -46610,7 +47000,7 @@ function checkAuthBrokerThresholdViolations(deps = {}) {
46610
47000
  }
46611
47001
  let violations;
46612
47002
  try {
46613
- violations = JSON.parse(readFileSync62(path6, "utf-8"));
47003
+ violations = JSON.parse(readFileSync63(path6, "utf-8"));
46614
47004
  } catch (err) {
46615
47005
  return {
46616
47006
  name: "auth-broker: threshold violations",
@@ -46644,7 +47034,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
46644
47034
  fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
46645
47035
  };
46646
47036
  }
46647
- const home2 = deps.home ?? homedir31();
47037
+ const home2 = deps.home ?? homedir32();
46648
47038
  const dir = accountDir(active, home2);
46649
47039
  if (!existsSync65(dir)) {
46650
47040
  return {
@@ -46796,8 +47186,8 @@ import {
46796
47186
  realpathSync as realpathSync5,
46797
47187
  statSync as statSync39
46798
47188
  } from "node:fs";
46799
- import { userInfo, homedir as homedir32 } from "node:os";
46800
- import { join as join60 } from "node:path";
47189
+ import { userInfo, homedir as homedir33 } from "node:os";
47190
+ import { join as join61 } from "node:path";
46801
47191
  function resolveVaultPath2(config) {
46802
47192
  return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
46803
47193
  }
@@ -46946,7 +47336,7 @@ async function runSecretAccessChecks(config, deps = {}) {
46946
47336
  }
46947
47337
  }
46948
47338
  const statPath = deps.statPath ?? statVault;
46949
- const home2 = deps.operatorHome ?? homedir32();
47339
+ const home2 = deps.operatorHome ?? homedir33();
46950
47340
  const ownedRoots = deps.ownedRoots ?? operatorOwnedPaths(home2);
46951
47341
  const walkOwned = deps.walkOwned ?? ((root) => walkOperatorOwnedTargets(home2, {}, [root]));
46952
47342
  for (const root of ownedRoots) {
@@ -46969,7 +47359,7 @@ async function runSecretAccessChecks(config, deps = {}) {
46969
47359
  };
46970
47360
  const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
46971
47361
  if (!passphrase) {
46972
- const sock = deps.brokerOperatorSocket ?? join60(homedir32(), ".switchroom", "broker-operator", "sock");
47362
+ const sock = deps.brokerOperatorSocket ?? join61(homedir33(), ".switchroom", "broker-operator", "sock");
46973
47363
  const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
46974
47364
  for (const name of Object.keys(config.agents ?? {})) {
46975
47365
  const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
@@ -47061,8 +47451,8 @@ import {
47061
47451
  existsSync as realExistsSync,
47062
47452
  readFileSync as realReadFileSync
47063
47453
  } from "node:fs";
47064
- import { join as join61, resolve as resolve37 } from "node:path";
47065
- import { homedir as homedir33 } from "node:os";
47454
+ import { join as join62, resolve as resolve37 } from "node:path";
47455
+ import { homedir as homedir34 } from "node:os";
47066
47456
  function resolveDeps(config, deps) {
47067
47457
  let agentsDir = deps.agentsDir;
47068
47458
  if (agentsDir === undefined) {
@@ -47185,8 +47575,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
47185
47575
  });
47186
47576
  continue;
47187
47577
  }
47188
- const mcpPath = join61(agentDir, ".mcp.json");
47189
- const claudeJsonPath = join61(agentDir, ".claude", ".claude.json");
47578
+ const mcpPath = join62(agentDir, ".mcp.json");
47579
+ const claudeJsonPath = join62(agentDir, ".claude", ".claude.json");
47190
47580
  const mcpRead = readJson(d, mcpPath);
47191
47581
  const trustRead = readJson(d, claudeJsonPath);
47192
47582
  if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
@@ -47299,7 +47689,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
47299
47689
  }
47300
47690
  ];
47301
47691
  }
47302
- const sock = deps.brokerOperatorSocket ?? join61(homedir33(), ".switchroom", "broker-operator", "sock");
47692
+ const sock = deps.brokerOperatorSocket ?? join62(homedir34(), ".switchroom", "broker-operator", "sock");
47303
47693
  const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
47304
47694
  const results = [];
47305
47695
  for (const agent of driveAgents) {
@@ -47353,8 +47743,8 @@ import {
47353
47743
  readSync as realReadSync,
47354
47744
  closeSync as realCloseSync
47355
47745
  } from "node:fs";
47356
- import { join as join62 } from "node:path";
47357
- import { homedir as homedir34 } from "node:os";
47746
+ import { join as join63 } from "node:path";
47747
+ import { homedir as homedir35 } from "node:os";
47358
47748
  function defaultReadHead(p, n) {
47359
47749
  let fd;
47360
47750
  try {
@@ -47382,7 +47772,7 @@ function resolveDeps2(config, deps) {
47382
47772
  }
47383
47773
  }
47384
47774
  return {
47385
- homeDir: deps.homeDir ?? homedir34(),
47775
+ homeDir: deps.homeDir ?? homedir35(),
47386
47776
  agentsDir,
47387
47777
  existsSync: deps.existsSync ?? ((p) => realExistsSync2(p)),
47388
47778
  readFileSync: deps.readFileSync ?? ((p) => realReadFileSync2(p, "utf-8")),
@@ -47411,7 +47801,7 @@ function runWebkiteChecks(config, deps = {}) {
47411
47801
  return [];
47412
47802
  const d = resolveDeps2(config, deps);
47413
47803
  const results = [];
47414
- const binPath = join62(d.homeDir, ".switchroom", "bin", "webkite");
47804
+ const binPath = join63(d.homeDir, ".switchroom", "bin", "webkite");
47415
47805
  if (!d.existsSync(binPath)) {
47416
47806
  results.push({
47417
47807
  name: "webkite: binary",
@@ -47441,14 +47831,14 @@ function runWebkiteChecks(config, deps = {}) {
47441
47831
  });
47442
47832
  }
47443
47833
  }
47444
- const sharedCloakDir = join62(d.homeDir, ".switchroom", "cloakbrowser");
47445
- const legacyCloakDir = join62(d.homeDir, ".cloakbrowser");
47834
+ const sharedCloakDir = join63(d.homeDir, ".switchroom", "cloakbrowser");
47835
+ const legacyCloakDir = join63(d.homeDir, ".cloakbrowser");
47446
47836
  const hasChromium = (dir) => {
47447
47837
  if (!d.existsSync(dir))
47448
47838
  return false;
47449
47839
  try {
47450
47840
  for (const entry of d.readdirSync(dir)) {
47451
- if (entry.startsWith("chromium-") && d.existsSync(join62(dir, entry, "chrome"))) {
47841
+ if (entry.startsWith("chromium-") && d.existsSync(join63(dir, entry, "chrome"))) {
47452
47842
  return true;
47453
47843
  }
47454
47844
  }
@@ -47485,9 +47875,9 @@ function runWebkiteChecks(config, deps = {}) {
47485
47875
  return results;
47486
47876
  }
47487
47877
  for (const agent of enabledAgents) {
47488
- const agentDir = join62(d.agentsDir, agent);
47489
- const settingsPath = join62(agentDir, ".claude", "settings.json");
47490
- const mcpPath = join62(agentDir, ".mcp.json");
47878
+ const agentDir = join63(d.agentsDir, agent);
47879
+ const settingsPath = join63(agentDir, ".claude", "settings.json");
47880
+ const mcpPath = join63(agentDir, ".mcp.json");
47491
47881
  if (!d.existsSync(settingsPath) && !d.existsSync(mcpPath)) {
47492
47882
  continue;
47493
47883
  }
@@ -47787,10 +48177,10 @@ var init_doctor_cron_session = __esm(() => {
47787
48177
  });
47788
48178
 
47789
48179
  // telegram-plugin/flood-429-ledger.ts
47790
- import { readFileSync as readFileSync63, writeFileSync as writeFileSync24, mkdirSync as mkdirSync37, chmodSync as chmodSync11, unlinkSync as unlinkSync14 } from "node:fs";
47791
- import { dirname as dirname24, join as join63 } from "node:path";
48180
+ import { readFileSync as readFileSync64, writeFileSync as writeFileSync24, mkdirSync as mkdirSync37, chmodSync as chmodSync11, unlinkSync as unlinkSync14 } from "node:fs";
48181
+ import { dirname as dirname24, join as join64 } from "node:path";
47792
48182
  function flood429LedgerPath(stateDir) {
47793
- return join63(stateDir, FLOOD_429_LEDGER_FILE);
48183
+ return join64(stateDir, FLOOD_429_LEDGER_FILE);
47794
48184
  }
47795
48185
  function parseEpisode(raw) {
47796
48186
  if (typeof raw !== "object" || raw === null)
@@ -47810,7 +48200,7 @@ function parseEpisode(raw) {
47810
48200
  count: num2(r.count) ?? 1
47811
48201
  };
47812
48202
  }
47813
- function readFlood429LedgerResult(path6, readFile2 = (p) => readFileSync63(p, "utf-8")) {
48203
+ function readFlood429LedgerResult(path6, readFile2 = (p) => readFileSync64(p, "utf-8")) {
47814
48204
  let text;
47815
48205
  try {
47816
48206
  text = readFile2(path6);
@@ -47920,14 +48310,14 @@ var init_flood_429_ledger = __esm(() => {
47920
48310
  });
47921
48311
 
47922
48312
  // src/cli/doctor-flood-pressure.ts
47923
- import { readFileSync as readFileSync64 } from "node:fs";
47924
- import { join as join64 } from "node:path";
48313
+ import { readFileSync as readFileSync65 } from "node:fs";
48314
+ import { join as join65 } from "node:path";
47925
48315
  function runFloodPressureChecks(config, deps = {}) {
47926
48316
  const results = [];
47927
48317
  const agents = Object.keys(config.agents ?? {});
47928
48318
  if (agents.length === 0)
47929
48319
  return results;
47930
- const readFile2 = deps.readFile ?? ((p) => readFileSync64(p, "utf-8"));
48320
+ const readFile2 = deps.readFile ?? ((p) => readFileSync65(p, "utf-8"));
47931
48321
  const now = deps.now ?? Date.now();
47932
48322
  let agentsDir = deps.agentsDir;
47933
48323
  if (agentsDir === undefined) {
@@ -47947,7 +48337,7 @@ function runFloodPressureChecks(config, deps = {}) {
47947
48337
  ];
47948
48338
  }
47949
48339
  for (const name of agents) {
47950
- const path6 = flood429LedgerPath(join64(agentsDir, name, "telegram"));
48340
+ const path6 = flood429LedgerPath(join65(agentsDir, name, "telegram"));
47951
48341
  const read = readFlood429LedgerResult(path6, readFile2);
47952
48342
  if (read.status === "unreadable" || read.status === "corrupt") {
47953
48343
  results.push({
@@ -47967,16 +48357,16 @@ function runFloodPressureChecks(config, deps = {}) {
47967
48357
  name: `429 flood pressure: ${name}`,
47968
48358
  status: verdict.status,
47969
48359
  detail: verdict.detail,
47970
- fix: FIX2
48360
+ fix: FIX3
47971
48361
  });
47972
48362
  }
47973
48363
  return results;
47974
48364
  }
47975
- var FIX2;
48365
+ var FIX3;
47976
48366
  var init_doctor_flood_pressure = __esm(() => {
47977
48367
  init_loader();
47978
48368
  init_flood_429_ledger();
47979
- FIX2 = "A flood ban is server-side and cannot be cleared early \u2014 cut the outbound " + "rate that earns it. Check the gateway's `send-gate stats` and " + "`edit-flood-fuse` lines for the class doing the sending (progress cards and " + "the worker feed are the usual culprits), and see " + "`telegram-plugin/edit-flood-fuse.ts` for the per-class ceilings.";
48369
+ FIX3 = "A flood ban is server-side and cannot be cleared early \u2014 cut the outbound " + "rate that earns it. Check the gateway's `send-gate stats` and " + "`edit-flood-fuse` lines for the class doing the sending (progress cards and " + "the worker feed are the usual culprits), and see " + "`telegram-plugin/edit-flood-fuse.ts` for the per-class ceilings.";
47980
48370
  });
47981
48371
 
47982
48372
  // src/litellm/budget.ts
@@ -48680,7 +49070,7 @@ async function runMcpSecretChecks(config, deps = {}) {
48680
49070
 
48681
49071
  // src/agents/connection-health.ts
48682
49072
  import { mkdirSync as mkdirSync38, writeFileSync as writeFileSync25 } from "node:fs";
48683
- import { join as join65 } from "node:path";
49073
+ import { join as join66 } from "node:path";
48684
49074
  async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
48685
49075
  const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
48686
49076
  if (reqs.length === 0)
@@ -48737,8 +49127,8 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
48737
49127
  return issues;
48738
49128
  }
48739
49129
  function writeConnectionHealthFile(agentDir, health, deps) {
48740
- const dir = join65(agentDir, ".claude");
48741
- const path6 = join65(dir, CONNECTION_HEALTH_FILENAME);
49130
+ const dir = join66(agentDir, ".claude");
49131
+ const path6 = join66(dir, CONNECTION_HEALTH_FILENAME);
48742
49132
  (deps?.mkdir ?? ((p, o) => mkdirSync38(p, o)))(dir, { recursive: true });
48743
49133
  (deps?.writeFile ?? ((p, d) => writeFileSync25(p, d)))(path6, JSON.stringify(health, null, 2) + `
48744
49134
  `);
@@ -48761,10 +49151,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
48761
49151
  var init_connection_health = () => {};
48762
49152
 
48763
49153
  // src/cli/update-prompt-hook.ts
48764
- import { existsSync as existsSync67, readFileSync as readFileSync65, writeFileSync as writeFileSync26, chmodSync as chmodSync12, mkdirSync as mkdirSync39 } from "node:fs";
48765
- import { join as join66 } from "node:path";
49154
+ import { existsSync as existsSync67, readFileSync as readFileSync66, writeFileSync as writeFileSync26, chmodSync as chmodSync12, mkdirSync as mkdirSync39 } from "node:fs";
49155
+ import { join as join67 } from "node:path";
48766
49156
  function containerHookCommand() {
48767
- return join66(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
49157
+ return join67(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
48768
49158
  }
48769
49159
  function updatePromptHookScript() {
48770
49160
  return `#!/bin/bash
@@ -48830,12 +49220,12 @@ exit 0
48830
49220
  `;
48831
49221
  }
48832
49222
  function installUpdatePromptHook(agentDir) {
48833
- const hooksDir = join66(agentDir, ".claude", "hooks");
49223
+ const hooksDir = join67(agentDir, ".claude", "hooks");
48834
49224
  mkdirSync39(hooksDir, { recursive: true });
48835
- const scriptPath = join66(hooksDir, HOOK_FILENAME);
49225
+ const scriptPath = join67(hooksDir, HOOK_FILENAME);
48836
49226
  const desired = updatePromptHookScript();
48837
49227
  let installed = false;
48838
- const existing = existsSync67(scriptPath) ? readFileSync65(scriptPath, "utf-8") : "";
49228
+ const existing = existsSync67(scriptPath) ? readFileSync66(scriptPath, "utf-8") : "";
48839
49229
  if (existing !== desired) {
48840
49230
  writeFileSync26(scriptPath, desired, { mode: 493 });
48841
49231
  chmodSync12(scriptPath, 493);
@@ -48845,11 +49235,11 @@ function installUpdatePromptHook(agentDir) {
48845
49235
  chmodSync12(scriptPath, 493);
48846
49236
  } catch {}
48847
49237
  }
48848
- const settingsPath = join66(agentDir, ".claude", "settings.json");
49238
+ const settingsPath = join67(agentDir, ".claude", "settings.json");
48849
49239
  if (!existsSync67(settingsPath)) {
48850
49240
  return { scriptPath, settingsPath, installed };
48851
49241
  }
48852
- const raw = readFileSync65(settingsPath, "utf-8");
49242
+ const raw = readFileSync66(settingsPath, "utf-8");
48853
49243
  let parsed;
48854
49244
  try {
48855
49245
  parsed = JSON.parse(raw);
@@ -48975,7 +49365,7 @@ __export(exports_voice_sidecar_token, {
48975
49365
  VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
48976
49366
  });
48977
49367
  import { randomBytes as randomBytes12 } from "node:crypto";
48978
- import { chmodSync as chmodSync13, chownSync as chownSync6, existsSync as existsSync69, mkdirSync as mkdirSync40, readFileSync as readFileSync66, rmSync as rmSync12, writeFileSync as writeFileSync27 } from "node:fs";
49368
+ import { chmodSync as chmodSync13, chownSync as chownSync6, existsSync as existsSync69, mkdirSync as mkdirSync40, readFileSync as readFileSync67, rmSync as rmSync12, writeFileSync as writeFileSync27 } from "node:fs";
48979
49369
  import { dirname as dirname27 } from "node:path";
48980
49370
  async function defaultResolveOrSeedToken(home2, writeErr) {
48981
49371
  const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
@@ -49011,7 +49401,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
49011
49401
  if (engine !== "local") {
49012
49402
  try {
49013
49403
  if (existsSync69(envPath)) {
49014
- const body = readFileSync66(envPath, "utf-8");
49404
+ const body = readFileSync67(envPath, "utf-8");
49015
49405
  if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
49016
49406
  rmSync12(envPath);
49017
49407
  }
@@ -49034,7 +49424,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
49034
49424
  let body = "";
49035
49425
  try {
49036
49426
  if (existsSync69(envPath))
49037
- body = readFileSync66(envPath, "utf-8");
49427
+ body = readFileSync67(envPath, "utf-8");
49038
49428
  } catch {}
49039
49429
  const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
49040
49430
  const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
@@ -49096,12 +49486,12 @@ __export(exports_apply, {
49096
49486
  DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH,
49097
49487
  COMPOSE_PROJECT: () => COMPOSE_PROJECT2
49098
49488
  });
49099
- import { accessSync as accessSync2, chmodSync as chmodSync14, chownSync as chownSync7, constants as fsConstants5, copyFileSync as copyFileSync10, existsSync as existsSync70, mkdirSync as mkdirSync41, readFileSync as readFileSync67, readdirSync as readdirSync24, renameSync as renameSync17, statSync as statSync40, writeFileSync as writeFileSync28 } from "node:fs";
49489
+ import { accessSync as accessSync2, chmodSync as chmodSync14, chownSync as chownSync7, constants as fsConstants5, copyFileSync as copyFileSync10, existsSync as existsSync70, mkdirSync as mkdirSync41, readFileSync as readFileSync68, readdirSync as readdirSync24, renameSync as renameSync17, statSync as statSync40, writeFileSync as writeFileSync28 } from "node:fs";
49100
49490
  import { mkdir as mkdir2 } from "node:fs/promises";
49101
49491
  import { spawnSync as childSpawnSync } from "node:child_process";
49102
49492
  import readline from "node:readline";
49103
- import { dirname as dirname28, join as join68, resolve as resolve40 } from "node:path";
49104
- import { homedir as homedir36 } from "node:os";
49493
+ import { dirname as dirname28, join as join69, resolve as resolve40 } from "node:path";
49494
+ import { homedir as homedir37 } from "node:os";
49105
49495
  import { execFileSync as execFileSync20 } from "node:child_process";
49106
49496
  function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
49107
49497
  return agentResolvedLitellm?.enabled ?? config.litellm?.enabled ?? false;
@@ -49112,7 +49502,7 @@ async function resolveOperatorVaultPassphrase(home2) {
49112
49502
  return envPass;
49113
49503
  try {
49114
49504
  const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
49115
- const blobPath = join68(home2, ".switchroom", "vault-auto-unlock");
49505
+ const blobPath = join69(home2, ".switchroom", "vault-auto-unlock");
49116
49506
  const pass = readAutoUnlockFile2(blobPath);
49117
49507
  return pass && pass.length > 0 ? pass : null;
49118
49508
  } catch {
@@ -49121,9 +49511,9 @@ async function resolveOperatorVaultPassphrase(home2) {
49121
49511
  }
49122
49512
  function materializeLitellmMasterKeyForBroker(masterKey, home2 = process.env.HOME ?? "/root") {
49123
49513
  try {
49124
- const stateDir = join68(home2, ".switchroom", "state", "auth-broker");
49514
+ const stateDir = join69(home2, ".switchroom", "state", "auth-broker");
49125
49515
  mkdirSync41(stateDir, { recursive: true, mode: 448 });
49126
- const path7 = join68(stateDir, LITELLM_MASTER_KEY_STATE_BASENAME);
49516
+ const path7 = join69(stateDir, LITELLM_MASTER_KEY_STATE_BASENAME);
49127
49517
  writeFileSync28(path7, masterKey.trim() + `
49128
49518
  `, { mode: 384 });
49129
49519
  try {
@@ -49159,7 +49549,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49159
49549
  Promise.resolve().then(() => (init_provision(), exports_provision)),
49160
49550
  Promise.resolve().then(() => (init_telegram_yaml(), exports_telegram_yaml))
49161
49551
  ]);
49162
- const passphrase = await resolveOperatorVaultPassphrase(ctx.home ?? homedir36());
49552
+ const passphrase = await resolveOperatorVaultPassphrase(ctx.home ?? homedir37());
49163
49553
  if (passphrase === null) {
49164
49554
  const { getViaBrokerStructured: getViaBrokerStructured3 } = await Promise.resolve().then(() => (init_client(), exports_client));
49165
49555
  const alreadyProvisioned = [];
@@ -49222,7 +49612,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49222
49612
  let configText = null;
49223
49613
  if (switchroomConfigPath && existsSync70(switchroomConfigPath)) {
49224
49614
  try {
49225
- configText = readFileSync67(switchroomConfigPath, "utf-8");
49615
+ configText = readFileSync68(switchroomConfigPath, "utf-8");
49226
49616
  } catch (err) {
49227
49617
  ctx.writeErr(source_default.yellow(` ! litellm: could not read config for ACL grants (${err.message}); keys will be provisioned but agents may lack read-ACL.
49228
49618
  `));
@@ -49269,7 +49659,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49269
49659
  }
49270
49660
  }
49271
49661
  if (masterKey && !brokerKeyMaterialized) {
49272
- const mat = materializeLitellmMasterKeyForBroker(masterKey, ctx.home ?? homedir36());
49662
+ const mat = materializeLitellmMasterKeyForBroker(masterKey, ctx.home ?? homedir37());
49273
49663
  brokerKeyMaterialized = true;
49274
49664
  if (!mat.ok) {
49275
49665
  ctx.writeErr(source_default.yellow(` ! litellm: could not materialize master key for auth-broker external-spend (${mat.error})
@@ -49552,7 +49942,7 @@ function resolveVaultBindMountDir(homeDir, ctx) {
49552
49942
  if (isCustomPath && ctx.customVaultPath) {
49553
49943
  return dirname28(ctx.customVaultPath);
49554
49944
  }
49555
- return join68(homeDir, ".switchroom", "vault");
49945
+ return join69(homeDir, ".switchroom", "vault");
49556
49946
  }
49557
49947
  function inspectVaultBindMountDir(vaultDir) {
49558
49948
  if (!existsSync70(vaultDir))
@@ -49583,41 +49973,41 @@ function hasVaultRefs(value) {
49583
49973
  async function ensureHostMountSources(config) {
49584
49974
  const home2 = resolveHostHomeForCompose();
49585
49975
  const dirs = [
49586
- join68(home2, ".switchroom", "approvals"),
49587
- join68(home2, ".switchroom", "scheduler"),
49588
- join68(home2, ".switchroom", "logs"),
49589
- join68(home2, ".switchroom", "compose"),
49590
- join68(home2, ".switchroom", "broker-operator")
49976
+ join69(home2, ".switchroom", "approvals"),
49977
+ join69(home2, ".switchroom", "scheduler"),
49978
+ join69(home2, ".switchroom", "logs"),
49979
+ join69(home2, ".switchroom", "compose"),
49980
+ join69(home2, ".switchroom", "broker-operator")
49591
49981
  ];
49592
49982
  for (const name of Object.keys(config.agents)) {
49593
- dirs.push(join68(home2, ".switchroom", "agents", name));
49594
- dirs.push(join68(home2, ".switchroom", "logs", name));
49595
- dirs.push(join68(home2, ".claude", "projects", name));
49596
- dirs.push(join68(home2, ".switchroom", "audit", name));
49597
- if (existsSync70(join68(home2, ".switchroom-config"))) {
49598
- dirs.push(join68(home2, ".switchroom-config", "agents", name, "personal-skills"));
49983
+ dirs.push(join69(home2, ".switchroom", "agents", name));
49984
+ dirs.push(join69(home2, ".switchroom", "logs", name));
49985
+ dirs.push(join69(home2, ".claude", "projects", name));
49986
+ dirs.push(join69(home2, ".switchroom", "audit", name));
49987
+ if (existsSync70(join69(home2, ".switchroom-config"))) {
49988
+ dirs.push(join69(home2, ".switchroom-config", "agents", name, "personal-skills"));
49599
49989
  }
49600
49990
  }
49601
49991
  for (const dir of dirs) {
49602
49992
  await mkdir2(dir, { recursive: true });
49603
49993
  }
49604
- const autoUnlockPath = join68(home2, ".switchroom", "vault-auto-unlock");
49994
+ const autoUnlockPath = join69(home2, ".switchroom", "vault-auto-unlock");
49605
49995
  if (!existsSync70(autoUnlockPath)) {
49606
49996
  writeFileSync28(autoUnlockPath, "", { mode: 384 });
49607
49997
  }
49608
- const auditLogPath = join68(home2, ".switchroom", "vault-audit.log");
49998
+ const auditLogPath = join69(home2, ".switchroom", "vault-audit.log");
49609
49999
  if (!existsSync70(auditLogPath)) {
49610
50000
  writeFileSync28(auditLogPath, "", { mode: 420 });
49611
50001
  }
49612
50002
  const grantsDbDir = getGrantsDbDir(home2);
49613
50003
  mkdirSync41(grantsDbDir, { recursive: true, mode: 448 });
49614
50004
  migrateLegacyGrantsDbLocation(getGrantsDbPath(home2));
49615
- const hostdAuditLogPath = join68(home2, ".switchroom", "host-control-audit.log");
50005
+ const hostdAuditLogPath = join69(home2, ".switchroom", "host-control-audit.log");
49616
50006
  if (!existsSync70(hostdAuditLogPath)) {
49617
50007
  writeFileSync28(hostdAuditLogPath, "", { mode: 420 });
49618
50008
  }
49619
50009
  for (const name of Object.keys(config.agents)) {
49620
- const tokenPath = join68(home2, ".switchroom", "agents", name, ".vault-token");
50010
+ const tokenPath = join69(home2, ".switchroom", "agents", name, ".vault-token");
49621
50011
  if (!existsSync70(tokenPath)) {
49622
50012
  writeFileSync28(tokenPath, "", { mode: 384 });
49623
50013
  }
@@ -49626,15 +50016,15 @@ async function ensureHostMountSources(config) {
49626
50016
  chownSync7(tokenPath, uid, uid);
49627
50017
  } catch {}
49628
50018
  }
49629
- const fleetDir = join68(home2, ".switchroom", "fleet");
50019
+ const fleetDir = join69(home2, ".switchroom", "fleet");
49630
50020
  await mkdir2(fleetDir, { recursive: true });
49631
- const invariantsPath = join68(fleetDir, "switchroom-invariants.md");
50021
+ const invariantsPath = join69(fleetDir, "switchroom-invariants.md");
49632
50022
  const invariantsCanonical = renderFleetInvariants();
49633
- const invariantsCurrent = existsSync70(invariantsPath) ? readFileSync67(invariantsPath, "utf-8") : null;
50023
+ const invariantsCurrent = existsSync70(invariantsPath) ? readFileSync68(invariantsPath, "utf-8") : null;
49634
50024
  if (invariantsCurrent !== invariantsCanonical) {
49635
50025
  writeFileSync28(invariantsPath, invariantsCanonical, { mode: 420 });
49636
50026
  }
49637
- const fleetClaudePath = join68(fleetDir, "CLAUDE.md");
50027
+ const fleetClaudePath = join69(fleetDir, "CLAUDE.md");
49638
50028
  if (!existsSync70(fleetClaudePath)) {
49639
50029
  writeFileSync28(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
49640
50030
  mode: 420
@@ -49718,10 +50108,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
49718
50108
  `));
49719
50109
  }
49720
50110
  }
49721
- function writeInstallTypeCache(homeDir = homedir36()) {
50111
+ function writeInstallTypeCache(homeDir = homedir37()) {
49722
50112
  const ctx = detectInstallType();
49723
- const dir = join68(homeDir, ".switchroom");
49724
- const out = join68(dir, "install-type.json");
50113
+ const dir = join69(homeDir, ".switchroom");
50114
+ const out = join69(dir, "install-type.json");
49725
50115
  const tmp = `${out}.tmp`;
49726
50116
  mkdirSync41(dir, { recursive: true });
49727
50117
  const payload = {
@@ -49788,17 +50178,17 @@ Applying switchroom config...
49788
50178
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
49789
50179
  `));
49790
50180
  try {
49791
- installUpdatePromptHook(join68(agentsDir, name));
50181
+ installUpdatePromptHook(join69(agentsDir, name));
49792
50182
  } catch (hookErr) {
49793
50183
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
49794
50184
  `));
49795
50185
  }
49796
- await refreshAgentConnectionHealth(config, name, join68(agentsDir, name), {
50186
+ await refreshAgentConnectionHealth(config, name, join69(agentsDir, name), {
49797
50187
  vaultAclReader: connHealthVaultAclReader
49798
50188
  });
49799
50189
  try {
49800
50190
  const uid = allocateAgentUid(name);
49801
- alignAgentUid(name, join68(agentsDir, name), uid, {
50191
+ alignAgentUid(name, join69(agentsDir, name), uid, {
49802
50192
  confirm: !options.nonInteractive,
49803
50193
  writeOut
49804
50194
  });
@@ -49842,7 +50232,7 @@ Applying switchroom config...
49842
50232
  writeOut,
49843
50233
  writeErr,
49844
50234
  failures,
49845
- home: homedir36()
50235
+ home: homedir37()
49846
50236
  });
49847
50237
  }
49848
50238
  await ensureHostMountSources(config);
@@ -49850,7 +50240,7 @@ Applying switchroom config...
49850
50240
  for (const name of agentNames) {
49851
50241
  try {
49852
50242
  const uid = allocateAgentUid(name);
49853
- alignAgentUid(name, join68(agentsDir, name), uid, {
50243
+ alignAgentUid(name, join69(agentsDir, name), uid, {
49854
50244
  confirm: !options.nonInteractive,
49855
50245
  writeOut
49856
50246
  });
@@ -49864,7 +50254,7 @@ Applying switchroom config...
49864
50254
  }
49865
50255
  const vaultPathConfigured = config.vault?.path;
49866
50256
  const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
49867
- const migrationResult = migrateVaultLayout(homedir36(), {
50257
+ const migrationResult = migrateVaultLayout(homedir37(), {
49868
50258
  customVaultPath
49869
50259
  });
49870
50260
  switch (migrationResult.kind) {
@@ -49890,7 +50280,7 @@ Applying switchroom config...
49890
50280
  writeErr(formatDivergentRecoveryMessage(migrationResult.details));
49891
50281
  process.exit(4);
49892
50282
  }
49893
- const postMigrationInspect = inspectVaultLayout(homedir36());
50283
+ const postMigrationInspect = inspectVaultLayout(homedir37());
49894
50284
  const acceptable = [
49895
50285
  "no-vault",
49896
50286
  "already-migrated",
@@ -49905,7 +50295,7 @@ Expected one of: ${acceptable.join(", ")}
49905
50295
  `));
49906
50296
  process.exit(5);
49907
50297
  }
49908
- const vaultDir = resolveVaultBindMountDir(homedir36(), {
50298
+ const vaultDir = resolveVaultBindMountDir(homedir37(), {
49909
50299
  migrationKind: migrationResult.kind,
49910
50300
  customVaultPath
49911
50301
  });
@@ -49936,7 +50326,7 @@ vault.enc.lock (PID-file flock from saveVault), and
49936
50326
  });
49937
50327
  if (!skipScaffold) {
49938
50328
  const { provisionVoiceSidecarToken: provisionVoiceSidecarToken2 } = await Promise.resolve().then(() => (init_voice_sidecar_token(), exports_voice_sidecar_token));
49939
- await provisionVoiceSidecarToken2(composePath, homedir36(), {
50329
+ await provisionVoiceSidecarToken2(composePath, homedir37(), {
49940
50330
  writeOut,
49941
50331
  writeErr,
49942
50332
  operatorUid
@@ -49952,7 +50342,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
49952
50342
  writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
49953
50343
  `));
49954
50344
  if (process.geteuid?.() === 0 && operatorUid !== undefined) {
49955
- const restored = restoreOperatorOwnership(homedir36(), operatorUid);
50345
+ const restored = restoreOperatorOwnership(homedir37(), operatorUid);
49956
50346
  if (restored.length > 0) {
49957
50347
  writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
49958
50348
  `));
@@ -50057,7 +50447,7 @@ Dry-run: validating switchroom config (no changes will be written)...
50057
50447
  }
50058
50448
  }
50059
50449
  try {
50060
- const probe2 = await probeVaultProvisioning(config, agentNames, homedir36());
50450
+ const probe2 = await probeVaultProvisioning(config, agentNames, homedir37());
50061
50451
  if (probe2.alreadyProvisioned.length > 0) {
50062
50452
  info.push(`litellm: ${probe2.alreadyProvisioned.length} agent(s) already provisioned (${probe2.alreadyProvisioned.join(", ")}).`);
50063
50453
  }
@@ -50210,7 +50600,7 @@ function findUnwritableAgentDirs(config, opts) {
50210
50600
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
50211
50601
  const unwritable = [];
50212
50602
  for (const name of targets) {
50213
- const startSh = join68(agentsDir, name, "start.sh");
50603
+ const startSh = join69(agentsDir, name, "start.sh");
50214
50604
  if (!existsSync70(startSh))
50215
50605
  continue;
50216
50606
  try {
@@ -50416,7 +50806,7 @@ var init_apply = __esm(() => {
50416
50806
  switchroom: switchroom_default,
50417
50807
  minimal: minimal_default
50418
50808
  };
50419
- DEFAULT_COMPOSE_PATH = join68(homedir36(), ".switchroom", "compose", "docker-compose.yml");
50809
+ DEFAULT_COMPOSE_PATH = join69(homedir37(), ".switchroom", "compose", "docker-compose.yml");
50420
50810
  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.
50421
50811
  ` + "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.";
50422
50812
  SELF_ELEVATE_PRESERVED_ENV = [
@@ -50434,12 +50824,12 @@ import { createHash as createHash15 } from "node:crypto";
50434
50824
  import {
50435
50825
  existsSync as existsSync71,
50436
50826
  lstatSync as lstatSync9,
50437
- readFileSync as readFileSync68,
50827
+ readFileSync as readFileSync69,
50438
50828
  readdirSync as readdirSync25,
50439
50829
  readlinkSync as readlinkSync7,
50440
50830
  writeFileSync as writeFileSync29
50441
50831
  } from "node:fs";
50442
- import { dirname as dirname29, isAbsolute as isAbsolute5, join as join69, resolve as resolve41 } from "node:path";
50832
+ import { dirname as dirname29, isAbsolute as isAbsolute5, join as join70, resolve as resolve41 } from "node:path";
50443
50833
  async function detectComposeDrift(opts) {
50444
50834
  let result;
50445
50835
  try {
@@ -50472,7 +50862,7 @@ function detectHooksSurfaceDrift(name, agentConfig, agentDir, config, configPath
50472
50862
  useSwitchroomPlugin: usesSwitchroomTelegramPlugin(agentConfig),
50473
50863
  configPath
50474
50864
  });
50475
- const settingsPath = join69(agentDir, ".claude", "settings.json");
50865
+ const settingsPath = join70(agentDir, ".claude", "settings.json");
50476
50866
  if (!existsSync71(settingsPath)) {
50477
50867
  return [
50478
50868
  {
@@ -50485,7 +50875,7 @@ function detectHooksSurfaceDrift(name, agentConfig, agentDir, config, configPath
50485
50875
  }
50486
50876
  let actual;
50487
50877
  try {
50488
- const raw = JSON.parse(readFileSync68(settingsPath, "utf-8"));
50878
+ const raw = JSON.parse(readFileSync69(settingsPath, "utf-8"));
50489
50879
  actual = raw.hooks ?? {};
50490
50880
  } catch {
50491
50881
  return [
@@ -50525,7 +50915,7 @@ function detectTemplateStampDrift(name, agentConfig, agentDir, opts = {}) {
50525
50915
  }
50526
50916
  function detectSkillsDrift(name, agentDir) {
50527
50917
  const findings = [];
50528
- const skillsDir = join69(agentDir, ".claude", "skills");
50918
+ const skillsDir = join70(agentDir, ".claude", "skills");
50529
50919
  const liveEntries = new Set;
50530
50920
  if (existsSync71(skillsDir)) {
50531
50921
  let entries = [];
@@ -50535,7 +50925,7 @@ function detectSkillsDrift(name, agentDir) {
50535
50925
  entries = [];
50536
50926
  }
50537
50927
  for (const entry of entries) {
50538
- const p = join69(skillsDir, entry);
50928
+ const p = join70(skillsDir, entry);
50539
50929
  let st;
50540
50930
  try {
50541
50931
  st = lstatSync9(p);
@@ -50562,7 +50952,7 @@ function detectSkillsDrift(name, agentDir) {
50562
50952
  liveEntries.add(entry);
50563
50953
  }
50564
50954
  }
50565
- const overlayDir = join69(agentDir, "skills.d");
50955
+ const overlayDir = join70(agentDir, "skills.d");
50566
50956
  if (existsSync71(overlayDir)) {
50567
50957
  let overlayEntries = [];
50568
50958
  try {
@@ -50589,8 +50979,8 @@ function detectSkillsDrift(name, agentDir) {
50589
50979
  function locateInstallBinDir() {
50590
50980
  let dir = import.meta.dirname;
50591
50981
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
50592
- if (existsSync71(join69(dir, "dependencies.json")) && existsSync71(join69(dir, "bin"))) {
50593
- return join69(dir, "bin");
50982
+ if (existsSync71(join70(dir, "dependencies.json")) && existsSync71(join70(dir, "bin"))) {
50983
+ return join70(dir, "bin");
50594
50984
  }
50595
50985
  dir = dirname29(dir);
50596
50986
  }
@@ -50610,7 +51000,7 @@ function detectHookScriptDrift(name, opts = {}) {
50610
51000
  }
50611
51001
  for (const f of names) {
50612
51002
  try {
50613
- expected.set(f, createHash15("sha256").update(readFileSync68(join69(binDir, f))).digest("hex"));
51003
+ expected.set(f, createHash15("sha256").update(readFileSync69(join70(binDir, f))).digest("hex"));
50614
51004
  } catch {}
50615
51005
  }
50616
51006
  if (expected.size === 0)
@@ -50662,17 +51052,17 @@ function detectHindsightRecallTunableDrift(name, agentConfig, agentDir, config)
50662
51052
  const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
50663
51053
  if (resolved.memory?.auto_recall === false)
50664
51054
  return [];
50665
- const pluginDir = join69(agentDir, ".claude", "plugins", "hindsight-memory");
51055
+ const pluginDir = join70(agentDir, ".claude", "plugins", "hindsight-memory");
50666
51056
  if (!existsSync71(pluginDir))
50667
51057
  return [];
50668
51058
  const expected = resolveHindsightRecallTunables(resolved.memory?.recall);
50669
51059
  const findings = [];
50670
51060
  const mismatches = [];
50671
- const hooksPath = join69(pluginDir, "hooks", "hooks.json");
51061
+ const hooksPath = join70(pluginDir, "hooks", "hooks.json");
50672
51062
  if (existsSync71(hooksPath)) {
50673
51063
  let installedHookTimeout = null;
50674
51064
  try {
50675
- installedHookTimeout = readHooksRecallTimeout(readFileSync68(hooksPath, "utf-8"));
51065
+ installedHookTimeout = readHooksRecallTimeout(readFileSync69(hooksPath, "utf-8"));
50676
51066
  } catch {
50677
51067
  installedHookTimeout = null;
50678
51068
  }
@@ -50682,11 +51072,11 @@ function detectHindsightRecallTunableDrift(name, agentConfig, agentDir, config)
50682
51072
  mismatches.push(`hook ceiling is ${installedHookTimeout}s, expected ` + `${expected.hookTimeoutSeconds}s (memory.recall.hook_timeout_seconds)`);
50683
51073
  }
50684
51074
  }
50685
- const settingsPath = join69(pluginDir, "settings.json");
51075
+ const settingsPath = join70(pluginDir, "settings.json");
50686
51076
  if (existsSync71(settingsPath)) {
50687
51077
  let settings = null;
50688
51078
  try {
50689
- settings = JSON.parse(readFileSync68(settingsPath, "utf-8"));
51079
+ settings = JSON.parse(readFileSync69(settingsPath, "utf-8"));
50690
51080
  } catch {
50691
51081
  settings = null;
50692
51082
  }
@@ -50740,7 +51130,7 @@ function writeDriftReport(agentDir, findings) {
50740
51130
  generatedAt: new Date().toISOString(),
50741
51131
  findings: findings.map(({ surface, detail }) => ({ surface, detail }))
50742
51132
  };
50743
- writeFileSync29(join69(agentDir, DRIFT_REPORT_FILE), JSON.stringify(report, null, 2) + `
51133
+ writeFileSync29(join70(agentDir, DRIFT_REPORT_FILE), JSON.stringify(report, null, 2) + `
50744
51134
  `, "utf-8");
50745
51135
  } catch {}
50746
51136
  }
@@ -50994,19 +51384,79 @@ var init_component_versions = __esm(() => {
50994
51384
  SEMVER_TAG_RE = /^v\d+\.\d+\.\d+$/;
50995
51385
  });
50996
51386
 
51387
+ // src/cli/component-scope.ts
51388
+ function imageRepoName(imageRef) {
51389
+ if (!imageRef)
51390
+ return null;
51391
+ const at = imageRef.indexOf("@");
51392
+ const withoutDigest = at >= 0 ? imageRef.slice(0, at) : imageRef;
51393
+ const slash = withoutDigest.lastIndexOf("/");
51394
+ if (slash < 0)
51395
+ return null;
51396
+ const afterSlash = withoutDigest.slice(slash + 1);
51397
+ const colon = afterSlash.indexOf(":");
51398
+ const repo = colon >= 0 ? afterSlash.slice(0, colon) : afterSlash;
51399
+ return /^switchroom-[a-z0-9-]+$/.test(repo) ? repo : null;
51400
+ }
51401
+ function classifyComponent(c) {
51402
+ if (c.kind === "cli")
51403
+ return { kind: "cli" };
51404
+ const repo = imageRepoName(c.imageRef);
51405
+ switch (repo) {
51406
+ case "switchroom-web":
51407
+ return { kind: "web" };
51408
+ case "switchroom-hostd":
51409
+ return { kind: "hostd" };
51410
+ case "switchroom-hindsight":
51411
+ return { kind: "hindsight" };
51412
+ case "switchroom-agent": {
51413
+ const agent = c.name.startsWith("switchroom-") ? c.name.slice("switchroom-".length) : c.name;
51414
+ return agent ? { kind: "fleet", agent } : { kind: "fleet" };
51415
+ }
51416
+ default:
51417
+ return { kind: "fleet" };
51418
+ }
51419
+ }
51420
+ function remediationFor(owner, target) {
51421
+ switch (owner.kind) {
51422
+ case "cli":
51423
+ return `switchroom update (self-updates the host CLI to ${target} first, then everything else)`;
51424
+ case "web":
51425
+ return `switchroom webd install --tag ${target} (separate compose project \u2014 regenerates + recreates switchroom-web)`;
51426
+ case "hostd":
51427
+ return `switchroom hostd install --tag ${target} (regenerates the hostd compose project \u2014 both hostd and the autoheal sidecar)`;
51428
+ case "hindsight":
51429
+ return `switchroom memory setup --recreate --tag ${target} (standalone container \u2014 not a compose service)`;
51430
+ case "fleet":
51431
+ return owner.agent ? `switchroom rollout --pin ${target} --agents ${owner.agent} (agent-fleet compose project)` : `switchroom rollout --pin ${target} (agent-fleet compose project \u2014 the singletons self-heal on the first agent restart)`;
51432
+ }
51433
+ }
51434
+ function gatedOwners(stepKinds, hostdContext) {
51435
+ const kinds = new Set(stepKinds);
51436
+ const owners = new Set(["fleet"]);
51437
+ if (kinds.has("refresh-web"))
51438
+ owners.add("web");
51439
+ if (kinds.has("refresh-hindsight"))
51440
+ owners.add("hindsight");
51441
+ if (kinds.has("refresh-hostd") || hostdContext)
51442
+ owners.add("hostd");
51443
+ return owners;
51444
+ }
51445
+ function partitionDrift(behind, opts) {
51446
+ const gated = [];
51447
+ const exempt = [];
51448
+ for (const c of behind) {
51449
+ const owner = classifyComponent(c);
51450
+ const inScope = owner.kind === "fleet" && owner.agent !== undefined ? opts.rolledAgents.has(owner.agent) : opts.owners.has(owner.kind);
51451
+ (inScope ? gated : exempt).push(c);
51452
+ }
51453
+ return { gated, exempt };
51454
+ }
51455
+
50997
51456
  // src/cli/doctor-component-versions.ts
50998
51457
  import { spawnSync as spawnSync12 } from "node:child_process";
50999
51458
  function fixFor(c, target) {
51000
- if (c.kind === "cli") {
51001
- return `switchroom update (self-updates the host CLI to ${target} first, then everything else)`;
51002
- }
51003
- if (c.name === "switchroom-web") {
51004
- return `switchroom webd install --tag ${target} (run host-side: webd install fails inside hostd when the hostd compose predates SWITCHROOM_HOSTD_OPERATOR_UID)`;
51005
- }
51006
- if (c.name === "switchroom-hindsight-autoheal" || c.name === "switchroom-hostd") {
51007
- return `switchroom hostd install --tag ${target} (regenerates the hostd compose project \u2014 both hostd and the autoheal sidecar)`;
51008
- }
51009
- return `switchroom update`;
51459
+ return remediationFor(classifyComponent(c), target);
51010
51460
  }
51011
51461
  function runComponentVersionChecks(config, opts = {}) {
51012
51462
  const exec = opts.exec ?? defaultExec2;
@@ -51064,7 +51514,7 @@ var init_doctor_component_versions = __esm(() => {
51064
51514
  });
51065
51515
 
51066
51516
  // src/cli/doctor-scaffold-wiring.ts
51067
- import { join as join70, resolve as resolve43 } from "node:path";
51517
+ import { join as join71, resolve as resolve43 } from "node:path";
51068
51518
  function readJson2(d, path7) {
51069
51519
  if (!d.existsSync(path7))
51070
51520
  return { kind: "absent" };
@@ -51107,8 +51557,8 @@ function checkIntegrationScaffoldWiring(args) {
51107
51557
  });
51108
51558
  continue;
51109
51559
  }
51110
- const mcpPath = join70(agentDir, ".mcp.json");
51111
- const claudeJsonPath = join70(agentDir, ".claude", ".claude.json");
51560
+ const mcpPath = join71(agentDir, ".mcp.json");
51561
+ const claudeJsonPath = join71(agentDir, ".claude", ".claude.json");
51112
51562
  const mcpRead = readJson2(deps, mcpPath);
51113
51563
  const trustRead = readJson2(deps, claudeJsonPath);
51114
51564
  if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
@@ -51172,14 +51622,14 @@ import {
51172
51622
  existsSync as realExistsSync3,
51173
51623
  readFileSync as realReadFileSync3
51174
51624
  } from "node:fs";
51175
- import { join as join71 } from "node:path";
51176
- import { homedir as homedir37 } from "node:os";
51625
+ import { join as join72 } from "node:path";
51626
+ import { homedir as homedir38 } from "node:os";
51177
51627
  function resolveDeps3(deps) {
51178
- const home2 = deps.homeDir?.() ?? homedir37();
51628
+ const home2 = deps.homeDir?.() ?? homedir38();
51179
51629
  return {
51180
51630
  existsSync: deps.existsSync ?? realExistsSync3,
51181
51631
  readFileSync: deps.readFileSync ?? realReadFileSync3,
51182
- agentsDir: join71(home2, ".switchroom", "agents"),
51632
+ agentsDir: join72(home2, ".switchroom", "agents"),
51183
51633
  now: deps.now ?? Date.now
51184
51634
  };
51185
51635
  }
@@ -51269,7 +51719,7 @@ function heartbeatFileName(config, agentName, account) {
51269
51719
  return "m365-launcher.heartbeat.json";
51270
51720
  }
51271
51721
  function readHeartbeatFile(d, agentName, fileName) {
51272
- const path7 = join71(d.agentsDir, agentName, fileName);
51722
+ const path7 = join72(d.agentsDir, agentName, fileName);
51273
51723
  if (!d.existsSync(path7)) {
51274
51724
  return { error: "heartbeat file missing \u2014 launcher has not yet started" };
51275
51725
  }
@@ -51375,15 +51825,15 @@ import {
51375
51825
  readFileSync as realReadFileSync4,
51376
51826
  statSync as realStatSync2
51377
51827
  } from "node:fs";
51378
- import { join as join72 } from "node:path";
51379
- import { homedir as homedir38 } from "node:os";
51828
+ import { join as join73 } from "node:path";
51829
+ import { homedir as homedir39 } from "node:os";
51380
51830
  function resolveDeps4(deps) {
51381
- const home2 = deps.homeDir?.() ?? homedir38();
51831
+ const home2 = deps.homeDir?.() ?? homedir39();
51382
51832
  return {
51383
51833
  existsSync: deps.existsSync ?? realExistsSync4,
51384
51834
  readFileSync: deps.readFileSync ?? realReadFileSync4,
51385
51835
  statSync: deps.statSync ?? realStatSync2,
51386
- agentsDir: join72(home2, ".switchroom", "agents"),
51836
+ agentsDir: join73(home2, ".switchroom", "agents"),
51387
51837
  now: deps.now ?? Date.now,
51388
51838
  vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
51389
51839
  };
@@ -51508,7 +51958,7 @@ function checkLauncherHeartbeat2(notionAgents, d) {
51508
51958
  return [];
51509
51959
  const results = [];
51510
51960
  for (const name of notionAgents) {
51511
- const heartbeatPath = join72(d.agentsDir, name, "notion-launcher.heartbeat.json");
51961
+ const heartbeatPath = join73(d.agentsDir, name, "notion-launcher.heartbeat.json");
51512
51962
  if (!d.existsSync(heartbeatPath)) {
51513
51963
  results.push({
51514
51964
  name: `notion:launcher-heartbeat:${name}`,
@@ -51623,10 +52073,10 @@ import {
51623
52073
  readdirSync as realReaddirSync2,
51624
52074
  statSync as realStatSync3
51625
52075
  } from "node:fs";
51626
- import { homedir as homedir39 } from "node:os";
51627
- import { join as join73 } from "node:path";
52076
+ import { homedir as homedir40 } from "node:os";
52077
+ import { join as join74 } from "node:path";
51628
52078
  function runCredentialsMigrationChecks(config, deps = {}) {
51629
- const credDir = deps.credentialsDir ?? join73(homedir39(), ".switchroom", "credentials");
52079
+ const credDir = deps.credentialsDir ?? join74(homedir40(), ".switchroom", "credentials");
51630
52080
  const existsSync73 = deps.existsSync ?? ((p) => realExistsSync5(p));
51631
52081
  const readdirSync26 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
51632
52082
  const isDirectory = deps.isDirectory ?? ((p) => {
@@ -51654,7 +52104,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
51654
52104
  const flat = [];
51655
52105
  const perAgentDirs = [];
51656
52106
  for (const e of entries) {
51657
- const full = join73(credDir, e);
52107
+ const full = join74(credDir, e);
51658
52108
  if (isDirectory(full) && agentNames.has(e)) {
51659
52109
  perAgentDirs.push(e);
51660
52110
  } else {
@@ -51683,14 +52133,14 @@ var init_doctor_credentials_migration = () => {};
51683
52133
 
51684
52134
  // src/cli/doctor-agent-dotfile-ownership.ts
51685
52135
  import { existsSync as existsSync73, lstatSync as lstatSync10, statSync as statSync41 } from "node:fs";
51686
- import { join as join74 } from "node:path";
52136
+ import { join as join75 } from "node:path";
51687
52137
  function agentDotfileCandidates(agentDir) {
51688
52138
  return [
51689
- join74(agentDir, ".claude", "settings.json"),
51690
- join74(agentDir, ".claude.json"),
51691
- join74(agentDir, ".claude", ".claude.json"),
51692
- join74(agentDir, ".mcp.json"),
51693
- join74(agentDir, ".claude-cron", ".mcp.json")
52139
+ join75(agentDir, ".claude", "settings.json"),
52140
+ join75(agentDir, ".claude.json"),
52141
+ join75(agentDir, ".claude", ".claude.json"),
52142
+ join75(agentDir, ".mcp.json"),
52143
+ join75(agentDir, ".claude-cron", ".mcp.json")
51694
52144
  ];
51695
52145
  }
51696
52146
  function runAgentDotfileOwnershipChecks(config, deps = {}) {
@@ -51718,7 +52168,7 @@ function runAgentDotfileOwnershipChecks(config, deps = {}) {
51718
52168
  ];
51719
52169
  }
51720
52170
  for (const name of agents) {
51721
- const agentDir = join74(agentsDir, name);
52171
+ const agentDir = join75(agentsDir, name);
51722
52172
  if (!exists(agentDir)) {
51723
52173
  continue;
51724
52174
  }
@@ -51846,7 +52296,7 @@ var init_doctor_inlined_secrets = __esm(() => {
51846
52296
  // src/cli/doctor-approval-attribution.ts
51847
52297
  import { execFileSync as execFileSync22 } from "node:child_process";
51848
52298
  import { readFileSync as fsReadFileSync2 } from "node:fs";
51849
- import { join as join75 } from "node:path";
52299
+ import { join as join76 } from "node:path";
51850
52300
  function parseApproverSet(canonical) {
51851
52301
  try {
51852
52302
  const arr = JSON.parse(canonical);
@@ -51965,7 +52415,7 @@ function loadLiveAllowFromAccessFiles(agentNames, agentsDir, readFile2 = (p) =>
51965
52415
  let readAny = false;
51966
52416
  for (const name of agentNames) {
51967
52417
  try {
51968
- const raw = readFile2(join75(agentsDir, name, "telegram", "access.json"));
52418
+ const raw = readFile2(join76(agentsDir, name, "telegram", "access.json"));
51969
52419
  const parsed = JSON.parse(raw);
51970
52420
  readAny = true;
51971
52421
  if (Array.isArray(parsed.allowFrom)) {
@@ -51998,13 +52448,13 @@ var init_doctor_approval_attribution = __esm(() => {
51998
52448
 
51999
52449
  // src/cli/doctor-audit-integrity.ts
52000
52450
  import { readFileSync as fsReadFileSync3 } from "node:fs";
52001
- import { homedir as homedir40 } from "node:os";
52002
- import { join as join76 } from "node:path";
52451
+ import { homedir as homedir41 } from "node:os";
52452
+ import { join as join77 } from "node:path";
52003
52453
  function rootWrittenLogs(home2) {
52004
52454
  return [
52005
52455
  {
52006
52456
  label: "vault-broker",
52007
- path: join76(home2, ".switchroom", "vault-audit.log"),
52457
+ path: join77(home2, ".switchroom", "vault-audit.log"),
52008
52458
  maxFiles: resolveRotationConfig({
52009
52459
  envBytesVar: "SWITCHROOM_VAULT_AUDIT_MAX_BYTES",
52010
52460
  envFilesVar: "SWITCHROOM_VAULT_AUDIT_MAX_FILES",
@@ -52014,7 +52464,7 @@ function rootWrittenLogs(home2) {
52014
52464
  },
52015
52465
  {
52016
52466
  label: "hostd",
52017
- path: join76(home2, ".switchroom", "host-control-audit.log"),
52467
+ path: join77(home2, ".switchroom", "host-control-audit.log"),
52018
52468
  maxFiles: resolveRotationConfig({
52019
52469
  envBytesVar: "SWITCHROOM_HOSTD_AUDIT_MAX_BYTES",
52020
52470
  envFilesVar: "SWITCHROOM_HOSTD_AUDIT_MAX_FILES",
@@ -52046,11 +52496,11 @@ function readRetainedHistory(read, path7, maxFiles) {
52046
52496
  return { text, sources: parts.map((p) => p.path), unreadable };
52047
52497
  }
52048
52498
  function runAuditIntegrityChecks(deps = {}) {
52049
- const home2 = deps.homeDir ?? homedir40();
52499
+ const home2 = deps.homeDir ?? homedir41();
52050
52500
  const read = deps.readFileSync ?? ((p) => fsReadFileSync3(p, "utf8"));
52051
52501
  const readFailOpen = deps.readFailOpenState ?? readFailOpenState;
52052
52502
  const results = [];
52053
- const vaultAuditLog = join76(home2, ".switchroom", "vault-audit.log");
52503
+ const vaultAuditLog = join77(home2, ".switchroom", "vault-audit.log");
52054
52504
  const failOpen = readFailOpen(failOpenStatePath(vaultAuditLog));
52055
52505
  if (failOpen.failOpenCount > 0) {
52056
52506
  const when = failOpen.lastFailureTs ? ` (last at ${failOpen.lastFailureTs})` : ``;
@@ -52133,14 +52583,14 @@ var init_doctor_audit_integrity = __esm(() => {
52133
52583
 
52134
52584
  // src/cli/doctor-agent-smoke.ts
52135
52585
  import { existsSync as existsSync74 } from "node:fs";
52136
- import { homedir as homedir41 } from "node:os";
52137
- import { join as join77 } from "node:path";
52586
+ import { homedir as homedir42 } from "node:os";
52587
+ import { join as join78 } from "node:path";
52138
52588
  import { randomUUID as randomUUID5 } from "node:crypto";
52139
52589
  async function runAgentSmokeChecks(config, deps = {}) {
52140
52590
  if (deps.fast)
52141
52591
  return [];
52142
- const home2 = deps.homeDir ?? homedir41();
52143
- const sock = deps.operatorSockPath ?? join77(home2, ".switchroom", "hostd", "operator", "sock");
52592
+ const home2 = deps.homeDir ?? homedir42();
52593
+ const sock = deps.operatorSockPath ?? join78(home2, ".switchroom", "hostd", "operator", "sock");
52144
52594
  if (!deps.hostdRequestImpl && !existsSync74(sock)) {
52145
52595
  return [
52146
52596
  {
@@ -52219,9 +52669,9 @@ var init_doctor_agent_smoke = __esm(() => {
52219
52669
 
52220
52670
  // src/cli/doctor-vault-broker-durability.ts
52221
52671
  import { execFileSync as execFileSync23 } from "node:child_process";
52222
- import { existsSync as existsSync75, statSync as statSync42, readFileSync as readFileSync69 } from "node:fs";
52223
- import { homedir as homedir42 } from "node:os";
52224
- import { join as join78 } from "node:path";
52672
+ import { existsSync as existsSync75, statSync as statSync42, readFileSync as readFileSync70 } from "node:fs";
52673
+ import { homedir as homedir43 } from "node:os";
52674
+ import { join as join79 } from "node:path";
52225
52675
  import { createRequire as createRequire2 } from "node:module";
52226
52676
  function probeBindMountInode(hostPath, brokerContainerPath, opts) {
52227
52677
  const statHost = opts?.statHost ?? defaultStatHost;
@@ -52365,13 +52815,13 @@ function defaultBrokerStatusProbe() {
52365
52815
  }
52366
52816
  }
52367
52817
  function runVaultBrokerDurabilityChecks(_config, opts) {
52368
- const home2 = homedir42();
52818
+ const home2 = homedir43();
52369
52819
  const probe2 = opts?.inodeProbe ?? probeBindMountInode;
52370
52820
  return [
52371
52821
  probeBrokerUnlocked(opts?.statusProbe),
52372
52822
  probeAutoUnlockBlob(home2),
52373
52823
  probeMachineIdMount(),
52374
- formatBindMountResult("vault-broker: vault.enc bind mount", join78(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join78(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
52824
+ formatBindMountResult("vault-broker: vault.enc bind mount", join79(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join79(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
52375
52825
  formatBindMountResult("vault-broker: vault-grants dir bind mount (#1737 / #3289)", getGrantsDbDir(home2), GRANTS_DB_CONTAINER_DIR, probe2(getGrantsDbDir(home2), GRANTS_DB_CONTAINER_DIR)),
52376
52826
  probeGrantsWalDivergence(opts?.walStatBroker),
52377
52827
  probeOrphanVaultTokens(_config, {
@@ -52380,14 +52830,14 @@ function runVaultBrokerDurabilityChecks(_config, opts) {
52380
52830
  readTokenId: opts?.readTokenId,
52381
52831
  tokenMtimeMs: opts?.tokenMtimeMs
52382
52832
  }),
52383
- formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join78(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join78(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
52833
+ formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join79(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join79(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
52384
52834
  probeKernelDbDurability(home2, {
52385
52835
  statBroker: opts?.kernelStatBroker
52386
52836
  })
52387
52837
  ];
52388
52838
  }
52389
52839
  function probeKernelDbDurability(home2, opts) {
52390
- const hostDir = join78(home2, ".switchroom", "approvals");
52840
+ const hostDir = join79(home2, ".switchroom", "approvals");
52391
52841
  const containerDir = "/state/approvals";
52392
52842
  const name = "approval-kernel: approvals bind mount (allow_always durability)";
52393
52843
  const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
@@ -52520,7 +52970,7 @@ function probeGrantsWalDivergence(statBroker = defaultWalStatBroker, opts) {
52520
52970
  }
52521
52971
  function probeOrphanVaultTokens(config, opts) {
52522
52972
  const name = "vault-broker: no orphan .vault-token grants (#1737 / #3289)";
52523
- const home2 = opts?.home ?? homedir42();
52973
+ const home2 = opts?.home ?? homedir43();
52524
52974
  const agents = Object.keys(config.agents ?? {});
52525
52975
  const readTokenId = opts?.readTokenId ?? defaultReadTokenId;
52526
52976
  const grantIdExists = opts?.grantIdExists ?? defaultGrantIdExists;
@@ -52585,15 +53035,15 @@ function probeOrphanVaultTokens(config, opts) {
52585
53035
  }
52586
53036
  function defaultTokenMtimeMs(agent, home2) {
52587
53037
  try {
52588
- return statSync42(join78(home2, ".switchroom", "agents", agent, ".vault-token")).mtimeMs;
53038
+ return statSync42(join79(home2, ".switchroom", "agents", agent, ".vault-token")).mtimeMs;
52589
53039
  } catch {
52590
53040
  return null;
52591
53041
  }
52592
53042
  }
52593
53043
  function defaultReadTokenId(agent, home2) {
52594
- const tokenPath = join78(home2, ".switchroom", "agents", agent, ".vault-token");
53044
+ const tokenPath = join79(home2, ".switchroom", "agents", agent, ".vault-token");
52595
53045
  try {
52596
- const raw = readFileSync69(tokenPath, "utf8").trim();
53046
+ const raw = readFileSync70(tokenPath, "utf8").trim();
52597
53047
  if (!raw)
52598
53048
  return null;
52599
53049
  const dot = raw.indexOf(".");
@@ -52624,7 +53074,7 @@ function defaultGrantIdExists(id) {
52624
53074
  }
52625
53075
  }
52626
53076
  function probeAutoUnlockBlob(home2) {
52627
- const blobPath = join78(home2, ".switchroom", "vault-auto-unlock");
53077
+ const blobPath = join79(home2, ".switchroom", "vault-auto-unlock");
52628
53078
  if (!existsSync75(blobPath)) {
52629
53079
  return {
52630
53080
  name: "vault-broker: auto-unlock blob",
@@ -52754,7 +53204,7 @@ var init_doctor_timezone = __esm(() => {
52754
53204
  });
52755
53205
 
52756
53206
  // src/cli/doctor-fix-session-model.ts
52757
- import { join as join79 } from "node:path";
53207
+ import { join as join80 } from "node:path";
52758
53208
  function describeReconcileChanges(changes) {
52759
53209
  if (changes.length === 0)
52760
53210
  return "Reconcile rewrote no other files.";
@@ -52767,7 +53217,7 @@ function fixSessionModelCarrierDrift(config, deps) {
52767
53217
  const agentsDir = resolveAgentsDir(config);
52768
53218
  const results = [];
52769
53219
  for (const name of Object.keys(config.agents ?? {})) {
52770
- const startShPath = join79(agentsDir, name, "start.sh");
53220
+ const startShPath = join80(agentsDir, name, "start.sh");
52771
53221
  const label = `${name}: start.sh /model session carrier (--fix)`;
52772
53222
  const pre = deps.check(name, startShPath);
52773
53223
  if (pre.status === "ok") {
@@ -52818,8 +53268,8 @@ var init_doctor_fix_session_model = __esm(() => {
52818
53268
 
52819
53269
  // src/cli/doctor-claude-cli.ts
52820
53270
  import { spawnSync as spawnSync13 } from "node:child_process";
52821
- import { existsSync as existsSync76, readFileSync as readFileSync70 } from "node:fs";
52822
- import { dirname as dirname30, join as join80 } from "node:path";
53271
+ import { existsSync as existsSync76, readFileSync as readFileSync71 } from "node:fs";
53272
+ import { dirname as dirname30, join as join81 } from "node:path";
52823
53273
  function parseClaudeCliVersion(raw) {
52824
53274
  const m = raw.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
52825
53275
  if (!m)
@@ -52842,7 +53292,7 @@ function claudeCliMeetsFloor(raw, floor) {
52842
53292
  function locateRepoFile(relative4) {
52843
53293
  let dir = import.meta.dirname;
52844
53294
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
52845
- const candidate = join80(dir, relative4);
53295
+ const candidate = join81(dir, relative4);
52846
53296
  if (existsSync76(candidate))
52847
53297
  return candidate;
52848
53298
  dir = dirname30(dir);
@@ -52850,12 +53300,12 @@ function locateRepoFile(relative4) {
52850
53300
  return null;
52851
53301
  }
52852
53302
  function readPinnedClaudeCliVersion(dockerfilePath) {
52853
- const path7 = dockerfilePath === undefined ? locateRepoFile(join80("docker", "Dockerfile.base")) : dockerfilePath;
53303
+ const path7 = dockerfilePath === undefined ? locateRepoFile(join81("docker", "Dockerfile.base")) : dockerfilePath;
52854
53304
  if (!path7)
52855
53305
  return null;
52856
53306
  let text;
52857
53307
  try {
52858
- text = readFileSync70(path7, "utf-8");
53308
+ text = readFileSync71(path7, "utf-8");
52859
53309
  } catch {
52860
53310
  return null;
52861
53311
  }
@@ -53013,34 +53463,34 @@ import {
53013
53463
  existsSync as existsSync77,
53014
53464
  lstatSync as lstatSync11,
53015
53465
  mkdirSync as mkdirSync42,
53016
- readFileSync as readFileSync71,
53466
+ readFileSync as readFileSync72,
53017
53467
  readdirSync as readdirSync26,
53018
53468
  statSync as statSync43
53019
53469
  } from "node:fs";
53020
- import { dirname as dirname31, join as join81, resolve as resolve44 } from "node:path";
53470
+ import { dirname as dirname31, join as join82, resolve as resolve44 } from "node:path";
53021
53471
  import { createPublicKey, createPrivateKey } from "node:crypto";
53022
53472
  function readLitellmConfigText() {
53023
53473
  const candidates = [
53024
53474
  process.env.LITELLM_CONFIG_PATH,
53025
53475
  "/data/coolify/services/litellm/litellm-config.yaml",
53026
- join81(process.cwd(), "docker", "litellm-proxy", "litellm-config.yaml")
53476
+ join82(process.cwd(), "docker", "litellm-proxy", "litellm-config.yaml")
53027
53477
  ].filter((p) => typeof p === "string" && p.length > 0);
53028
53478
  for (const p of candidates) {
53029
53479
  try {
53030
53480
  if (existsSync77(p))
53031
- return readFileSync71(p, "utf-8");
53481
+ return readFileSync72(p, "utf-8");
53032
53482
  } catch {}
53033
53483
  }
53034
53484
  return null;
53035
53485
  }
53036
53486
  function findInNvm(bin) {
53037
- const nvmRoot = join81(process.env.HOME ?? "", ".nvm", "versions", "node");
53487
+ const nvmRoot = join82(process.env.HOME ?? "", ".nvm", "versions", "node");
53038
53488
  if (!existsSync77(nvmRoot))
53039
53489
  return null;
53040
53490
  try {
53041
53491
  const versions = readdirSync26(nvmRoot).sort().reverse();
53042
53492
  for (const v of versions) {
53043
- const candidate = join81(nvmRoot, v, "bin", bin);
53493
+ const candidate = join82(nvmRoot, v, "bin", bin);
53044
53494
  try {
53045
53495
  const s = statSync43(candidate);
53046
53496
  if (s.isFile() || s.isSymbolicLink()) {
@@ -53067,7 +53517,7 @@ function whichOnPath(bin) {
53067
53517
  for (const dir of pathEnv.split(":")) {
53068
53518
  if (!dir)
53069
53519
  continue;
53070
- const candidate = join81(dir, bin);
53520
+ const candidate = join82(dir, bin);
53071
53521
  if (isX(candidate))
53072
53522
  return candidate;
53073
53523
  }
@@ -53233,7 +53683,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
53233
53683
  if (envBrowsersPath && envBrowsersPath.length > 0) {
53234
53684
  cacheLocations.push(envBrowsersPath);
53235
53685
  }
53236
- cacheLocations.push(join81(homeDir, ".cache", "ms-playwright"));
53686
+ cacheLocations.push(join82(homeDir, ".cache", "ms-playwright"));
53237
53687
  for (const cacheDir of cacheLocations) {
53238
53688
  if (!existsSync77(cacheDir))
53239
53689
  continue;
@@ -53241,10 +53691,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
53241
53691
  const entries = readdirSync26(cacheDir).filter((e) => e.startsWith("chromium"));
53242
53692
  for (const entry of entries) {
53243
53693
  const candidates2 = [
53244
- join81(cacheDir, entry, "chrome-linux64", "chrome"),
53245
- join81(cacheDir, entry, "chrome-linux", "chrome"),
53246
- join81(cacheDir, entry, "chrome-linux64", "headless_shell"),
53247
- join81(cacheDir, entry, "chrome-linux", "headless_shell")
53694
+ join82(cacheDir, entry, "chrome-linux64", "chrome"),
53695
+ join82(cacheDir, entry, "chrome-linux", "chrome"),
53696
+ join82(cacheDir, entry, "chrome-linux64", "headless_shell"),
53697
+ join82(cacheDir, entry, "chrome-linux", "headless_shell")
53248
53698
  ];
53249
53699
  for (const path7 of candidates2) {
53250
53700
  if (existsSync77(path7))
@@ -53376,7 +53826,7 @@ function checkDeployMounts(opts) {
53376
53826
  const home2 = opts?.home ?? process.env.HOME ?? "/root";
53377
53827
  const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
53378
53828
  const results = [];
53379
- const dockerComposePlugin = join81(home2, ".docker", "cli-plugins", "docker-compose");
53829
+ const dockerComposePlugin = join82(home2, ".docker", "cli-plugins", "docker-compose");
53380
53830
  const pluginKind = pathKind(dockerComposePlugin);
53381
53831
  if (pluginKind === "dir") {
53382
53832
  results.push({
@@ -53414,7 +53864,7 @@ function checkDeployMounts(opts) {
53414
53864
  function checkLegacyState() {
53415
53865
  const results = [];
53416
53866
  const h = process.env.HOME ?? "/root";
53417
- const clerkDir = join81(h, LEGACY_STATE_DIR);
53867
+ const clerkDir = join82(h, LEGACY_STATE_DIR);
53418
53868
  const clerkPresent = existsSync77(clerkDir);
53419
53869
  results.push({
53420
53870
  name: "legacy ~/.clerk state",
@@ -53424,7 +53874,7 @@ function checkLegacyState() {
53424
53874
  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."
53425
53875
  } : {}
53426
53876
  });
53427
- const legacySock = join81(h, ".switchroom", "vault-broker.sock");
53877
+ const legacySock = join82(h, ".switchroom", "vault-broker.sock");
53428
53878
  let sockStat = null;
53429
53879
  try {
53430
53880
  sockStat = lstatSync11(legacySock);
@@ -53914,6 +54364,7 @@ async function checkHindsight(config) {
53914
54364
  results.push(await checkHindsightHealthEndpoint(url));
53915
54365
  results.push(...await checkBankIngestHealth(config, url, { includeConsolidationBacklog: true }));
53916
54366
  results.push(...await checkBankObservationsMissions(config, url));
54367
+ results.push(await checkObservationScopeSaturation(config, url));
53917
54368
  const memoryAgentsDir = resolveAgentsDir(config);
53918
54369
  for (const agentName of Object.keys(config.agents)) {
53919
54370
  results.push(checkAgentRecallHealth(agentName, resolve44(memoryAgentsDir, agentName)));
@@ -54092,7 +54543,7 @@ function classifyReadError(err) {
54092
54543
  }
54093
54544
  function tryReadHostFile(path7) {
54094
54545
  try {
54095
- return { kind: "ok", content: readFileSync71(path7, "utf-8") };
54546
+ return { kind: "ok", content: readFileSync72(path7, "utf-8") };
54096
54547
  } catch (err) {
54097
54548
  const kind = classifyReadError(err);
54098
54549
  const error = err?.message ?? String(err);
@@ -54108,7 +54559,7 @@ function parseEnvFile(path7) {
54108
54559
  return {};
54109
54560
  let content;
54110
54561
  try {
54111
- content = readFileSync71(path7, "utf-8");
54562
+ content = readFileSync72(path7, "utf-8");
54112
54563
  } catch {
54113
54564
  return {};
54114
54565
  }
@@ -54163,7 +54614,7 @@ async function checkTelegram(config) {
54163
54614
  const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
54164
54615
  if (plugin !== "switchroom")
54165
54616
  continue;
54166
- const envPath = join81(agentsDir, name, "telegram", ".env");
54617
+ const envPath = join82(agentsDir, name, "telegram", ".env");
54167
54618
  const read = tryReadHostFile(envPath);
54168
54619
  if (read.kind === "eacces") {
54169
54620
  results.push({
@@ -54225,7 +54676,7 @@ function checkStartShStale(agentName, startShPath) {
54225
54676
  }
54226
54677
  let content;
54227
54678
  try {
54228
- content = readFileSync71(startShPath, "utf-8");
54679
+ content = readFileSync72(startShPath, "utf-8");
54229
54680
  } catch (err) {
54230
54681
  return {
54231
54682
  name: label,
@@ -54256,7 +54707,7 @@ function checkStartShSessionModelCarrier(agentName, startShPath) {
54256
54707
  }
54257
54708
  let content;
54258
54709
  try {
54259
- content = readFileSync71(startShPath, "utf-8");
54710
+ content = readFileSync72(startShPath, "utf-8");
54260
54711
  } catch (err) {
54261
54712
  return {
54262
54713
  name: label,
@@ -54293,7 +54744,7 @@ function checkStartShSessionModelCarrier(agentName, startShPath) {
54293
54744
  }
54294
54745
  function checkLeakedHomeSwitchroom(agentName, agentDir) {
54295
54746
  const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
54296
- const path7 = join81(agentDir, "home", ".switchroom");
54747
+ const path7 = join82(agentDir, "home", ".switchroom");
54297
54748
  let stats;
54298
54749
  try {
54299
54750
  stats = lstatSync11(path7);
@@ -54330,7 +54781,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
54330
54781
  }
54331
54782
  function checkRepoHygiene(repoRoot) {
54332
54783
  const results = [];
54333
- const exportDir = join81(repoRoot, "clerk-export");
54784
+ const exportDir = join82(repoRoot, "clerk-export");
54334
54785
  if (existsSync77(exportDir)) {
54335
54786
  results.push({
54336
54787
  name: "repo hygiene: clerk-export/ on disk (#1072)",
@@ -54339,7 +54790,7 @@ function checkRepoHygiene(repoRoot) {
54339
54790
  fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
54340
54791
  });
54341
54792
  }
54342
- const knownTarball = join81(repoRoot, "clerk-export-with-secrets.tar.gz");
54793
+ const knownTarball = join82(repoRoot, "clerk-export-with-secrets.tar.gz");
54343
54794
  if (existsSync77(knownTarball)) {
54344
54795
  results.push({
54345
54796
  name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
@@ -54357,7 +54808,7 @@ function checkRepoHygiene(repoRoot) {
54357
54808
  results.push({
54358
54809
  name: `repo hygiene: ${name} on disk (#1072)`,
54359
54810
  status: "warn",
54360
- detail: `${join81(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
54811
+ detail: `${join82(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
54361
54812
  fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
54362
54813
  });
54363
54814
  }
@@ -54380,12 +54831,12 @@ function checkRepoHygiene(repoRoot) {
54380
54831
  }
54381
54832
  function isSwitchroomCheckout(dir) {
54382
54833
  try {
54383
- if (!existsSync77(join81(dir, ".git")))
54834
+ if (!existsSync77(join82(dir, ".git")))
54384
54835
  return false;
54385
- const pkgPath = join81(dir, "package.json");
54836
+ const pkgPath = join82(dir, "package.json");
54386
54837
  if (!existsSync77(pkgPath))
54387
54838
  return false;
54388
- const pkg = JSON.parse(readFileSync71(pkgPath, "utf-8"));
54839
+ const pkg = JSON.parse(readFileSync72(pkgPath, "utf-8"));
54389
54840
  return pkg.name === "switchroom";
54390
54841
  } catch {
54391
54842
  return false;
@@ -54419,8 +54870,8 @@ function checkAgents(config, configPath) {
54419
54870
  fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
54420
54871
  });
54421
54872
  }
54422
- results.push(checkStartShStale(name, join81(agentDir, "start.sh")));
54423
- results.push(checkStartShSessionModelCarrier(name, join81(agentDir, "start.sh")));
54873
+ results.push(checkStartShStale(name, join82(agentDir, "start.sh")));
54874
+ results.push(checkStartShSessionModelCarrier(name, join82(agentDir, "start.sh")));
54424
54875
  results.push(checkLeakedHomeSwitchroom(name, agentDir));
54425
54876
  const status = statuses[name];
54426
54877
  const active = status?.active ?? "unknown";
@@ -54497,7 +54948,7 @@ function checkAgents(config, configPath) {
54497
54948
  }
54498
54949
  }
54499
54950
  if (agentConfig.channels?.telegram?.plugin === "switchroom") {
54500
- const mcpJsonPath = join81(agentDir, ".mcp.json");
54951
+ const mcpJsonPath = join82(agentDir, ".mcp.json");
54501
54952
  if (!existsSync77(mcpJsonPath)) {
54502
54953
  results.push({
54503
54954
  name: `${name}: .mcp.json`,
@@ -54507,7 +54958,7 @@ function checkAgents(config, configPath) {
54507
54958
  });
54508
54959
  } else {
54509
54960
  try {
54510
- const mcp = JSON.parse(readFileSync71(mcpJsonPath, "utf-8"));
54961
+ const mcp = JSON.parse(readFileSync72(mcpJsonPath, "utf-8"));
54511
54962
  const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
54512
54963
  const memoryEnabled = isHindsightEnabled(config);
54513
54964
  const hasHindsight = !!mcp.mcpServers?.hindsight;
@@ -54799,7 +55250,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
54799
55250
  };
54800
55251
  }
54801
55252
  const credDir = dirname31(envPath);
54802
- const authScript = join81(credDir, "claude-auth.py");
55253
+ const authScript = join82(credDir, "claude-auth.py");
54803
55254
  if (!existsSync77(authScript)) {
54804
55255
  return {
54805
55256
  name: "mff: auth flow",
@@ -55002,11 +55453,11 @@ function runDockerSection(config) {
55002
55453
  let composeYaml;
55003
55454
  let dockerfileAgent;
55004
55455
  try {
55005
- composeYaml = readFileSync71(composePath, "utf8");
55456
+ composeYaml = readFileSync72(composePath, "utf8");
55006
55457
  } catch {}
55007
55458
  const dockerfilePath = resolve44(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
55008
55459
  try {
55009
- dockerfileAgent = readFileSync71(dockerfilePath, "utf8");
55460
+ dockerfileAgent = readFileSync72(dockerfilePath, "utf8");
55010
55461
  } catch {}
55011
55462
  return runDockerChecks({
55012
55463
  config,
@@ -55152,6 +55603,10 @@ function registerDoctorCommand(program3) {
55152
55603
  }
55153
55604
  })
55154
55605
  },
55606
+ {
55607
+ title: "LiteLLM boot routing",
55608
+ results: runRoutingModeChecks(config)
55609
+ },
55155
55610
  { title: "Telegram", results: await checkTelegram(config) },
55156
55611
  {
55157
55612
  title: "Telegram flood pressure (429)",
@@ -55331,8 +55786,10 @@ var init_doctor = __esm(() => {
55331
55786
  init_doctor_memory();
55332
55787
  init_doctor_recall_health();
55333
55788
  init_doctor_hnsw_index();
55789
+ init_doctor_observation_scopes();
55334
55790
  init_doctor_hindsight_watch();
55335
55791
  init_key_allowlist_check();
55792
+ init_doctor_routing_mode();
55336
55793
  init_resolver();
55337
55794
  init_doctor_docker();
55338
55795
  init_doctor_auth_broker();
@@ -69302,7 +69759,7 @@ var init_server4 = __esm(() => {
69302
69759
  },
69303
69760
  {
69304
69761
  name: "rollout",
69305
- description: "SAFELY roll the fleet to a pinned SEMVER version, staggered and " + "canary-gated (#2487). Unlike update_apply (a blunt all-at-once " + "recreate), this restarts agents one at a time canary-first, asserts " + "each agent's in-container `switchroom --version` matches the target, " + "and STOPS on the first mismatch \u2014 so a bad build fails on the canary " + "before touching the rest of the fleet. The durable release.pin is " + "persisted only AFTER the canary confirms (a failed canary never " + "strands a bad pin). `pin` MUST be a tagged semver (vX.Y.Z) \u2014 sha " + "pins are rejected because the version assert needs a semver to " + "compare against. When the target pin is NEWER than hostd's own CLI " + "(every freshly tagged release), hostd SELF-BUMPS first (#2645): its " + "container restarts on the target image (~30-60s blip on this MCP " + "socket), then the roll resumes AUTOMATICALLY under the SAME " + "request_id \u2014 do NOT re-issue the rollout during the blip; poll " + "get_status (pass the returned request_id as its `request_id` " + "argument) once the socket is back. " + "The web + hindsight singletons are refreshed in-plan on this path " + "(only the hostd template regen + host operator CLI stay host-side; " + "the terminal warnings name exactly what is still on the prior " + "version and the commands to finish). By default downgrade pins " + "are rejected \u2014 pass `allow_downgrade: true` for the operator-approved " + "rollback path to a known-good earlier tag; all other safety rails " + "(canary order, version-assert, stop-on-mismatch) apply unchanged. " + "Admin-only at the wire layer AND deliberately NOT pre-approved \u2014 every " + "call surfaces a Telegram approval card for the operator to tap. " + "ALWAYS pass a one-line `reason` explaining why you're rolling the " + "fleet to this pin; it renders on the card's `why:` line so the " + "operator can decide in context. " + "Returns `started`; poll get_status with `request_id` for the " + "structured outcome (which agents rolled / where it stopped).",
69762
+ description: "SAFELY roll the fleet to a pinned SEMVER version, staggered and " + "canary-gated (#2487). Unlike update_apply (a blunt all-at-once " + "recreate), this restarts agents one at a time canary-first, asserts " + "each agent's in-container `switchroom --version` matches the target, " + "and STOPS on the first mismatch \u2014 so a bad build fails on the canary " + "before touching the rest of the fleet. The durable release.pin is " + "persisted only AFTER the canary confirms (a failed canary never " + "strands a bad pin). `pin` MUST be a tagged semver (vX.Y.Z) \u2014 sha " + "pins are rejected because the version assert needs a semver to " + "compare against. When the target pin is NEWER than hostd's own CLI " + "(every freshly tagged release), hostd SELF-BUMPS first (#2645): its " + "container restarts on the target image (~30-60s blip on this MCP " + "socket), then the roll resumes AUTOMATICALLY under the SAME " + "request_id \u2014 do NOT re-issue the rollout during the blip; poll " + "get_status (pass the returned request_id as its `request_id` " + "argument) once the socket is back. " + "The web + hindsight singletons are refreshed in-plan on this path " + "(only the hostd template regen + host operator CLI stay host-side; " + "the terminal warnings name exactly what is still on the prior " + "version and the commands to finish). The roll ENDS by re-reading " + "the same component inventory `update --check` uses and FAILS if " + "any in-scope component is still behind the target (#3928), so a " + "`completed` result means the host actually converged \u2014 never just " + "the agents. On that failure get_status returns " + '`failedStep: "verify-components"` plus `drifted[]`. By default downgrade pins ' + "are rejected \u2014 pass `allow_downgrade: true` for the operator-approved " + "rollback path to a known-good earlier tag; all other safety rails " + "(canary order, version-assert, stop-on-mismatch) apply unchanged. " + "Admin-only at the wire layer AND deliberately NOT pre-approved \u2014 every " + "call surfaces a Telegram approval card for the operator to tap. " + "ALWAYS pass a one-line `reason` explaining why you're rolling the " + "fleet to this pin; it renders on the card's `why:` line so the " + "operator can decide in context. " + "Returns `started`; poll get_status with `request_id` for the " + "structured outcome (which agents rolled / where it stopped).",
69306
69763
  inputSchema: {
69307
69764
  type: "object",
69308
69765
  required: ["pin"],
@@ -69362,7 +69819,7 @@ var init_server4 = __esm(() => {
69362
69819
  },
69363
69820
  {
69364
69821
  name: "get_status",
69365
- description: "Look up the status of a prior async fleet mutation. With " + "`request_id` (the id echoed by rollout / update_apply / " + "agent_restart etc.), asks the host-control daemon for that " + "specific request's live status \u2014 for an in-flight `rollout` " + "this is the ONLY way to see progress: the response payload " + "carries the current phase, n/m counters, rolled[] agents, " + "failedStep/failedAgent and pin, and it keeps working across a " + "hostd self-bump/restart via the durable audit-log fallback. " + "This is the tool the `rollout` description tells you to poll " + "with the returned request_id. Without `request_id`, falls back " + "to reading the most recent terminal `update_apply` audit row " + "(channel, pin, resolved_sha, install_context, result, " + "exit_code, stderr_tail) \u2014 use that no-arg form only to report " + "the last blunt update on demand; it CANNOT see rollouts or " + "in-flight work. Read-only; returns JSON.",
69822
+ description: "Look up the status of a prior async fleet mutation. With " + "`request_id` (the id echoed by rollout / update_apply / " + "agent_restart etc.), asks the host-control daemon for that " + "specific request's live status \u2014 for an in-flight `rollout` " + "this is the ONLY way to see progress: the response payload " + "carries the current phase, n/m counters, rolled[] agents, " + "failedStep/failedAgent and pin, and it keeps working across a " + "hostd self-bump/restart via the durable audit-log fallback. " + "When `failedStep` is `verify-components` the payload also " + "carries `drifted[]` \u2014 the components that are STILL behind the " + "target after the roll. Report those names verbatim: re-running " + "the roll will not fix them, the named component's own install " + "must be run. " + "This is the tool the `rollout` description tells you to poll " + "with the returned request_id. Without `request_id`, falls back " + "to reading the most recent terminal `update_apply` audit row " + "(channel, pin, resolved_sha, install_context, result, " + "exit_code, stderr_tail) \u2014 use that no-arg form only to report " + "the last blunt update on demand; it CANNOT see rollouts or " + "in-flight work. Read-only; returns JSON.",
69366
69823
  inputSchema: {
69367
69824
  type: "object",
69368
69825
  properties: {
@@ -69900,7 +70357,7 @@ var init_header_passthrough_guard = __esm(() => {
69900
70357
  });
69901
70358
 
69902
70359
  // src/fleet-health/litellm-config-sensor.ts
69903
- import { readFileSync as readFileSync96, existsSync as existsSync109 } from "node:fs";
70360
+ import { readFileSync as readFileSync97, existsSync as existsSync109 } from "node:fs";
69904
70361
  function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitellmConfigPath()) {
69905
70362
  if (explicit)
69906
70363
  return explicit;
@@ -69912,7 +70369,7 @@ function resolveLitellmConfigPath(explicit, discoverFn = () => discoverLiveLitel
69912
70369
  function scanLitellmConfig(opts = {}) {
69913
70370
  const path10 = resolveLitellmConfigPath(opts.path, opts.discoverFn);
69914
70371
  const exists = opts.existsFn ?? existsSync109;
69915
- const read = opts.readFn ?? ((p) => readFileSync96(p, "utf-8"));
70372
+ const read = opts.readFn ?? ((p) => readFileSync97(p, "utf-8"));
69916
70373
  const log = opts.log ?? (() => {});
69917
70374
  const nowIso = opts.nowIso ?? new Date().toISOString();
69918
70375
  if (path10 === null) {
@@ -69987,15 +70444,15 @@ __export(exports_scan, {
69987
70444
  ledgerPathForBase: () => ledgerPathForBase
69988
70445
  });
69989
70446
  import {
69990
- readFileSync as readFileSync97,
70447
+ readFileSync as readFileSync98,
69991
70448
  readdirSync as readdirSync42,
69992
70449
  existsSync as existsSync110,
69993
70450
  mkdirSync as mkdirSync63,
69994
70451
  writeFileSync as writeFileSync46
69995
70452
  } from "node:fs";
69996
70453
  import { resolve as resolve61, dirname as dirname42 } from "node:path";
69997
- import { homedir as homedir60 } from "node:os";
69998
- function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir60()) {
70454
+ import { homedir as homedir61 } from "node:os";
70455
+ function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir61()) {
69999
70456
  return resolve61(home2, ".switchroom");
70000
70457
  }
70001
70458
  function listAgents(base) {
@@ -70030,7 +70487,7 @@ function runScan(opts = {}) {
70030
70487
  let sawArtifact = false;
70031
70488
  try {
70032
70489
  if (existsSync110(turnsPath)) {
70033
- turnsText = readFileSync97(turnsPath, "utf-8");
70490
+ turnsText = readFileSync98(turnsPath, "utf-8");
70034
70491
  sawArtifact = true;
70035
70492
  }
70036
70493
  } catch (e) {
@@ -70038,7 +70495,7 @@ function runScan(opts = {}) {
70038
70495
  }
70039
70496
  try {
70040
70497
  if (existsSync110(gwPath)) {
70041
- gwText = readFileSync97(gwPath, "utf-8");
70498
+ gwText = readFileSync98(gwPath, "utf-8");
70042
70499
  sawArtifact = true;
70043
70500
  }
70044
70501
  } catch (e) {
@@ -70082,7 +70539,7 @@ function readLedgerIfPresent(base) {
70082
70539
  try {
70083
70540
  if (!existsSync110(path10))
70084
70541
  return null;
70085
- return JSON.parse(readFileSync97(path10, "utf-8"));
70542
+ return JSON.parse(readFileSync98(path10, "utf-8"));
70086
70543
  } catch {
70087
70544
  return null;
70088
70545
  }
@@ -70114,7 +70571,7 @@ init_esm();
70114
70571
  init_source();
70115
70572
  import { join as join24, resolve as resolve23 } from "node:path";
70116
70573
  import { rmSync as rmSync10, existsSync as existsSync33, readFileSync as readFileSync26, statSync as statSync18 } from "node:fs";
70117
- import { homedir as homedir9 } from "node:os";
70574
+ import { homedir as homedir10 } from "node:os";
70118
70575
 
70119
70576
  // src/agents/scheduler-state.ts
70120
70577
  import { closeSync, openSync, readSync, statSync } from "node:fs";
@@ -70459,6 +70916,7 @@ import { join as join19 } from "node:path";
70459
70916
  import { execFileSync as execFileSync11 } from "node:child_process";
70460
70917
 
70461
70918
  // src/agents/handoff-summarizer.ts
70919
+ init_atomic();
70462
70920
  init_observation_scopes();
70463
70921
  import { readFileSync as readFileSync19, writeFileSync as writeFileSync9, renameSync as renameSync7, mkdirSync as mkdirSync16, existsSync as existsSync23, statSync as statSync11, readdirSync as readdirSync10 } from "node:fs";
70464
70922
  import { join as join18 } from "node:path";
@@ -70603,8 +71061,13 @@ function writeSidecarsAtomic(agentDir, briefing, topic) {
70603
71061
  const topicTmp = topicPath + ".tmp";
70604
71062
  writeFileSync9(handoffTmp, briefing, "utf-8");
70605
71063
  writeFileSync9(topicTmp, topic, "utf-8");
71064
+ fsyncPathSync(handoffTmp);
71065
+ fsyncPathSync(topicTmp);
70606
71066
  renameSync7(handoffTmp, handoffPath);
70607
71067
  renameSync7(topicTmp, topicPath);
71068
+ try {
71069
+ fsyncPathSync(agentDir);
71070
+ } catch {}
70608
71071
  }
70609
71072
  async function buildHandoff(opts) {
70610
71073
  const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
@@ -72840,7 +73303,7 @@ function registerAgentCommand(program3) {
72840
73303
  }
72841
73304
  return;
72842
73305
  }
72843
- const logsDir = join24(homedir9(), ".switchroom", "logs");
73306
+ const logsDir = join24(homedir10(), ".switchroom", "logs");
72844
73307
  const schedulerStates = Object.fromEntries(agentNames.map((n) => [n, getSchedulerState(n, logsDir)]));
72845
73308
  if (opts.json) {
72846
73309
  const data = agentNames.map((name) => {
@@ -73909,7 +74372,7 @@ init_source();
73909
74372
  init_lifecycle();
73910
74373
  import { execFile } from "node:child_process";
73911
74374
  import { promisify } from "node:util";
73912
- import { homedir as homedir11 } from "node:os";
74375
+ import { homedir as homedir12 } from "node:os";
73913
74376
  import { join as join26 } from "node:path";
73914
74377
  init_helpers();
73915
74378
  init_broker_call();
@@ -73982,7 +74445,7 @@ function registerStatusCommand(program3) {
73982
74445
  const config = getConfig(program3);
73983
74446
  const agentNames = Object.keys(config.agents ?? {}).sort();
73984
74447
  const statuses = getAllAgentStatuses(config);
73985
- const logsDir = join26(homedir11(), ".switchroom", "logs");
74448
+ const logsDir = join26(homedir12(), ".switchroom", "logs");
73986
74449
  const schedulerStates = Object.fromEntries(agentNames.map((n) => [n, getSchedulerState(n, logsDir)]));
73987
74450
  const accountsP = fetchAccounts();
73988
74451
  const mcpEnabled = opts.mcp !== false;
@@ -76423,12 +76886,12 @@ function loadCredentialsFromGlobalAccount(label) {
76423
76886
  };
76424
76887
  }
76425
76888
  async function loadCredentialsViaClaude(label) {
76426
- const [{ runViaClaude: runViaClaude2 }, { homedir: homedir14 }, { default: readline }] = await Promise.all([
76889
+ const [{ runViaClaude: runViaClaude2 }, { homedir: homedir15 }, { default: readline }] = await Promise.all([
76427
76890
  Promise.resolve().then(() => (init_via_claude(), exports_via_claude)),
76428
76891
  import("node:os"),
76429
76892
  import("node:readline")
76430
76893
  ]);
76431
- const configDir = join30(homedir14(), ".switchroom", "accounts", label);
76894
+ const configDir = join30(homedir15(), ".switchroom", "accounts", label);
76432
76895
  const result = await runViaClaude2({
76433
76896
  configDir,
76434
76897
  promptForCode: async (_url) => {
@@ -76695,7 +77158,7 @@ init_hindsight2();
76695
77158
  import { readFileSync as readFileSync32, writeFileSync as writeFileSync13, copyFileSync as copyFileSync6, existsSync as existsSync38, readdirSync as readdirSync17, statSync as statSync23 } from "node:fs";
76696
77159
  import { execFileSync as execFileSync17 } from "node:child_process";
76697
77160
  import { join as join31 } from "node:path";
76698
- import { homedir as homedir14 } from "node:os";
77161
+ import { homedir as homedir15 } from "node:os";
76699
77162
  function getVaultPath2(configPath) {
76700
77163
  try {
76701
77164
  const config = loadConfig(configPath);
@@ -76710,7 +77173,7 @@ function maskToken(s) {
76710
77173
  return "***";
76711
77174
  }
76712
77175
  function findClaudeTranscripts() {
76713
- const root = join31(homedir14(), ".claude", "projects");
77176
+ const root = join31(homedir15(), ".claude", "projects");
76714
77177
  if (!existsSync38(root))
76715
77178
  return [];
76716
77179
  const out = [];
@@ -76742,7 +77205,7 @@ function findClaudeTranscripts() {
76742
77205
  function findTelegramHistoryDb() {
76743
77206
  const candidates = [
76744
77207
  process.env.TELEGRAM_STATE_DIR ? join31(process.env.TELEGRAM_STATE_DIR, "history.db") : null,
76745
- join31(homedir14(), ".claude", "channels", "telegram", "history.db")
77208
+ join31(homedir15(), ".claude", "channels", "telegram", "history.db")
76746
77209
  ].filter(Boolean);
76747
77210
  for (const c of candidates) {
76748
77211
  if (existsSync38(c))
@@ -81361,7 +81824,7 @@ init_auto_unlock();
81361
81824
  var import_yaml11 = __toESM(require_dist(), 1);
81362
81825
  import { spawnSync as spawnSync4 } from "node:child_process";
81363
81826
  import { readFileSync as readFileSync39, statSync as statSync28 } from "node:fs";
81364
- import { homedir as homedir18 } from "node:os";
81827
+ import { homedir as homedir19 } from "node:os";
81365
81828
  import { join as join40 } from "node:path";
81366
81829
 
81367
81830
  class EncryptFailedError extends Error {
@@ -81400,7 +81863,7 @@ function setVaultBrokerAutoUnlock(configPath, value) {
81400
81863
  } catch {}
81401
81864
  writeConfigFileSync(configPath, doc.toString(), mode);
81402
81865
  }
81403
- var DEFAULT_COMPOSE_FILE = join40(homedir18(), ".switchroom", "compose", "docker-compose.yml");
81866
+ var DEFAULT_COMPOSE_FILE = join40(homedir19(), ".switchroom", "compose", "docker-compose.yml");
81404
81867
  async function applyAutoUnlock(opts = {}) {
81405
81868
  const log = opts.log ?? ((s) => console.log(s));
81406
81869
  const err = opts.err ?? ((s) => console.error(s));
@@ -82121,7 +82584,7 @@ init_loader();
82121
82584
  init_loader();
82122
82585
  init_client();
82123
82586
  import { join as join41 } from "node:path";
82124
- import { homedir as homedir19 } from "node:os";
82587
+ import { homedir as homedir20 } from "node:os";
82125
82588
  function parseDuration(raw) {
82126
82589
  const lower = raw.toLowerCase().trim();
82127
82590
  if (lower === "0" || lower === "never" || lower === "none")
@@ -82213,7 +82676,7 @@ function registerVaultGrantCommands(vault, program3) {
82213
82676
  console.error(source_default.red(`Failed to mint grant: ${result.msg}`));
82214
82677
  process.exit(1);
82215
82678
  }
82216
- const tokenPath = join41(homedir19(), ".switchroom", "agents", agent, ".vault-token");
82679
+ const tokenPath = join41(homedir20(), ".switchroom", "agents", agent, ".vault-token");
82217
82680
  console.log(source_default.green(`\u2713 Grant minted`));
82218
82681
  if (process.stdout.isTTY) {
82219
82682
  console.log(source_default.bold("Token: ") + result.token);
@@ -82287,7 +82750,7 @@ import {
82287
82750
  unlinkSync as unlinkSync12,
82288
82751
  writeSync as writeSync7
82289
82752
  } from "node:fs";
82290
- import { homedir as homedir20 } from "node:os";
82753
+ import { homedir as homedir21 } from "node:os";
82291
82754
  import { join as join42, resolve as resolvePath2 } from "node:path";
82292
82755
  var LATEST_SYMLINK = "vault.enc.latest.bak";
82293
82756
  var MANIFEST_FILE = "manifest.jsonl";
@@ -82486,7 +82949,7 @@ function backupVault(opts) {
82486
82949
  };
82487
82950
  }
82488
82951
  function defaultHome() {
82489
- return process.env.HOME ?? homedir20();
82952
+ return process.env.HOME ?? homedir21();
82490
82953
  }
82491
82954
 
82492
82955
  // src/cli/vault-backup.ts
@@ -84957,7 +85420,7 @@ Demoting memory ${source_default.cyan(memoryId)}`));
84957
85420
  // src/cli/web.ts
84958
85421
  init_source();
84959
85422
  import { join as join56 } from "node:path";
84960
- import { homedir as homedir29 } from "node:os";
85423
+ import { homedir as homedir30 } from "node:os";
84961
85424
 
84962
85425
  // src/web/server.ts
84963
85426
  init_merge();
@@ -84974,7 +85437,7 @@ import {
84974
85437
  constants as fsConstants3
84975
85438
  } from "node:fs";
84976
85439
  import { resolve as resolve34, extname, join as join55, relative as relative3, dirname as dirname18 } from "node:path";
84977
- import { homedir as homedir28 } from "node:os";
85440
+ import { homedir as homedir29 } from "node:os";
84978
85441
  import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
84979
85442
 
84980
85443
  // src/web/api.ts
@@ -85053,8 +85516,8 @@ init_hindsight();
85053
85516
  // src/web/blocked-approvals-read.ts
85054
85517
  import { readFileSync as readFileSync47, readdirSync as readdirSync21 } from "node:fs";
85055
85518
  import { resolve as resolve30, join as join46 } from "node:path";
85056
- import { homedir as homedir21 } from "node:os";
85057
- function blockedApprovalsDir(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir21()) {
85519
+ import { homedir as homedir22 } from "node:os";
85520
+ function blockedApprovalsDir(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir22()) {
85058
85521
  return resolve30(home2, ".switchroom", "blocked-approvals");
85059
85522
  }
85060
85523
  var FALLBACK_RECORD_NAME = "blocked-approval.json";
@@ -85256,7 +85719,7 @@ init_client();
85256
85719
  init_posthog();
85257
85720
  init_loader();
85258
85721
  init_merge();
85259
- import { homedir as homedir24 } from "node:os";
85722
+ import { homedir as homedir25 } from "node:os";
85260
85723
 
85261
85724
  // src/web/config-edit-plan.ts
85262
85725
  init_schema();
@@ -85351,7 +85814,7 @@ function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "
85351
85814
  // src/web/hostd-config-propose.ts
85352
85815
  init_client5();
85353
85816
  import { existsSync as existsSync53 } from "node:fs";
85354
- import { homedir as homedir23 } from "node:os";
85817
+ import { homedir as homedir24 } from "node:os";
85355
85818
  import { join as join49 } from "node:path";
85356
85819
  var PROPOSE_TIMEOUT_MS = 61 * 60 * 1000;
85357
85820
  function resolveHostdOperatorSocket(env2 = process.env) {
@@ -85360,7 +85823,7 @@ function resolveHostdOperatorSocket(env2 = process.env) {
85360
85823
  return override;
85361
85824
  const candidates = [
85362
85825
  "/host-home/.switchroom/hostd/operator/sock",
85363
- join49(homedir23(), ".switchroom", "hostd", "operator", "sock")
85826
+ join49(homedir24(), ".switchroom", "hostd", "operator", "sock")
85364
85827
  ];
85365
85828
  for (const c of candidates) {
85366
85829
  if (existsSync53(c))
@@ -85628,7 +86091,7 @@ var PHASE4_MIGRATIONS = [
85628
86091
  ];
85629
86092
  function applySchema(db) {
85630
86093
  db.exec("PRAGMA journal_mode = WAL");
85631
- db.exec("PRAGMA synchronous = NORMAL");
86094
+ db.exec("PRAGMA synchronous = FULL");
85632
86095
  db.exec("PRAGMA busy_timeout = 5000");
85633
86096
  db.exec(SCHEMA_SQL);
85634
86097
  for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS, ...PHASE4_MIGRATIONS]) {
@@ -86613,10 +87076,10 @@ async function handleGetApprovals() {
86613
87076
  const sorted = [...decisions].sort((a, b) => b.granted_at - a.granted_at);
86614
87077
  return { reachable: true, decisions: sorted };
86615
87078
  }
86616
- function brokerOperatorSocketPath(home2 = process.env.HOME || homedir24()) {
87079
+ function brokerOperatorSocketPath(home2 = process.env.HOME || homedir25()) {
86617
87080
  return join51(home2, ".switchroom", "broker-operator", "sock");
86618
87081
  }
86619
- function resolveBrokerOperatorSocket(home2 = process.env.HOME || homedir24()) {
87082
+ function resolveBrokerOperatorSocket(home2 = process.env.HOME || homedir25()) {
86620
87083
  const p = brokerOperatorSocketPath(home2);
86621
87084
  return existsSync54(p) ? p : null;
86622
87085
  }
@@ -87011,7 +87474,7 @@ init_fleet_health_read();
87011
87474
  // src/web/webhook-handler.ts
87012
87475
  import { appendFileSync as appendFileSync4, existsSync as existsSync56, mkdirSync as mkdirSync31, readFileSync as readFileSync53, writeFileSync as writeFileSync20 } from "fs";
87013
87476
  import { join as join53 } from "path";
87014
- import { homedir as homedir27 } from "os";
87477
+ import { homedir as homedir28 } from "os";
87015
87478
 
87016
87479
  // src/web/webhook-verify.ts
87017
87480
  import { createHmac as createHmac2, timingSafeEqual } from "crypto";
@@ -87221,11 +87684,11 @@ function forwardToGateway(socketPath, req, opts = {}) {
87221
87684
  // src/web/webhook-edge.ts
87222
87685
  import { existsSync as existsSync55, readFileSync as readFileSync52 } from "fs";
87223
87686
  import { join as join52 } from "path";
87224
- import { homedir as homedir26 } from "os";
87687
+ import { homedir as homedir27 } from "os";
87225
87688
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
87226
87689
  var EDGE_HEADER = "x-switchroom-edge";
87227
87690
  function edgeSecretPath() {
87228
- return join52(homedir26(), ".switchroom", "webhook-edge-secret");
87691
+ return join52(homedir27(), ".switchroom", "webhook-edge-secret");
87229
87692
  }
87230
87693
  function loadEdgeSecret(path6) {
87231
87694
  const p = path6 ?? edgeSecretPath();
@@ -87385,7 +87848,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
87385
87848
  async function handleWebhookIngest(args, deps = {}) {
87386
87849
  const log = deps.log ?? ((s) => process.stderr.write(s));
87387
87850
  const now = (deps.now ?? Date.now)();
87388
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join53(homedir27(), ".switchroom", "agents", a));
87851
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join53(homedir28(), ".switchroom", "agents", a));
87389
87852
  const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
87390
87853
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
87391
87854
  if (!args.agentExists) {
@@ -88450,7 +88913,7 @@ function resolveWebToken() {
88450
88913
  const fromEnv = process.env.SWITCHROOM_WEB_TOKEN;
88451
88914
  if (fromEnv && fromEnv.length > 0)
88452
88915
  return fromEnv;
88453
- const home2 = process.env.HOME ?? homedir28();
88916
+ const home2 = process.env.HOME ?? homedir29();
88454
88917
  const tokenPath = join55(home2, ".switchroom", "web-token");
88455
88918
  if (existsSync58(tokenPath)) {
88456
88919
  const existing = readFileSync55(tokenPath, "utf8").trim();
@@ -88543,7 +89006,7 @@ function checkWsAuth(req, token, server) {
88543
89006
  return presented !== null && constantTimeEqual(presented, token);
88544
89007
  }
88545
89008
  function loadWebhookSecrets() {
88546
- const path6 = join55(homedir28(), ".switchroom", "webhook-secrets.json");
89009
+ const path6 = join55(homedir29(), ".switchroom", "webhook-secrets.json");
88547
89010
  if (!existsSync58(path6))
88548
89011
  return {};
88549
89012
  try {
@@ -89251,7 +89714,7 @@ async function handleWebStartupFailure(err, configPath, stateFile) {
89251
89714
  throw err;
89252
89715
  }
89253
89716
  function webCrashStatePath() {
89254
- return join56(homedir29(), ".switchroom", "web", ".crashloop-state.json");
89717
+ return join56(homedir30(), ".switchroom", "web", ".crashloop-state.json");
89255
89718
  }
89256
89719
  function registerWebCommand(program3) {
89257
89720
  program3.command("web").description("Start the web dashboard for monitoring agents").option("-p, --port <port>", "Port to listen on", "8080").option("-b, --bind <host>", "Host/IP to bind to (default: 127.0.0.1, localhost-only)", "127.0.0.1").action(withConfigError(async (opts) => {
@@ -90803,10 +91266,10 @@ init_source();
90803
91266
  init_loader();
90804
91267
  init_lifecycle();
90805
91268
  init_compose_env();
90806
- import { existsSync as existsSync80, mkdirSync as mkdirSync45, readFileSync as readFileSync73, realpathSync as realpathSync6, statSync as statSync45, chownSync as chownSync8 } from "node:fs";
91269
+ import { existsSync as existsSync80, mkdirSync as mkdirSync45, readFileSync as readFileSync74, realpathSync as realpathSync6, statSync as statSync45, chownSync as chownSync8 } from "node:fs";
90807
91270
  import { spawnSync as spawnSync16 } from "node:child_process";
90808
- import { join as join83, dirname as dirname33, resolve as resolve45 } from "node:path";
90809
- import { homedir as homedir43 } from "node:os";
91271
+ import { join as join84, dirname as dirname33, resolve as resolve45 } from "node:path";
91272
+ import { homedir as homedir44 } from "node:os";
90810
91273
 
90811
91274
  // src/cli/release-yaml.ts
90812
91275
  var import_yaml19 = __toESM(require_dist(), 1);
@@ -90938,13 +91401,13 @@ import {
90938
91401
  cpSync as cpSync2,
90939
91402
  existsSync as existsSync78,
90940
91403
  mkdirSync as mkdirSync43,
90941
- readFileSync as readFileSync72,
91404
+ readFileSync as readFileSync73,
90942
91405
  readdirSync as readdirSync27,
90943
91406
  renameSync as renameSync18,
90944
91407
  rmSync as rmSync13,
90945
91408
  writeFileSync as writeFileSync30
90946
91409
  } from "node:fs";
90947
- import { join as join82 } from "node:path";
91410
+ import { join as join83 } from "node:path";
90948
91411
  var BUNDLED_SKILL_MANIFEST_NAME = ".switchroom-manifest.json";
90949
91412
  function listSkillDirs(dir) {
90950
91413
  if (!existsSync78(dir))
@@ -90952,11 +91415,11 @@ function listSkillDirs(dir) {
90952
91415
  return readdirSync27(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
90953
91416
  }
90954
91417
  function readBundledSkillManifest(poolDir) {
90955
- const path7 = join82(poolDir, BUNDLED_SKILL_MANIFEST_NAME);
91418
+ const path7 = join83(poolDir, BUNDLED_SKILL_MANIFEST_NAME);
90956
91419
  if (!existsSync78(path7))
90957
91420
  return { firstRun: true };
90958
91421
  try {
90959
- const parsed = JSON.parse(readFileSync72(path7, "utf8"));
91422
+ const parsed = JSON.parse(readFileSync73(path7, "utf8"));
90960
91423
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.skills) || !parsed.skills.every((s) => typeof s === "string")) {
90961
91424
  return { corrupt: true };
90962
91425
  }
@@ -90967,7 +91430,7 @@ function readBundledSkillManifest(poolDir) {
90967
91430
  }
90968
91431
  }
90969
91432
  function stageAndSwap(srcSkill, destSkill, poolDir, name) {
90970
- const staging = join82(poolDir, `.tmp-${name}-${process.pid}-${Date.now()}`);
91433
+ const staging = join83(poolDir, `.tmp-${name}-${process.pid}-${Date.now()}`);
90971
91434
  try {
90972
91435
  rmSync13(staging, { recursive: true, force: true });
90973
91436
  cpSync2(srcSkill, staging, { recursive: true, dereference: false });
@@ -90996,11 +91459,11 @@ function syncBundledSkills(opts) {
90996
91459
  const shipped = listSkillDirs(source).sort();
90997
91460
  const shippedSet = new Set(shipped);
90998
91461
  for (const name of shipped) {
90999
- const destSkill = join82(dest, name);
91462
+ const destSkill = join83(dest, name);
91000
91463
  const existed = existsSync78(destSkill);
91001
91464
  let transferred = false;
91002
91465
  if (existed && !priorSkills.has(name) && !result.firstRun) {
91003
- const backup = join82(dest, `${name}.operator-backup-${process.pid}-${Date.now()}`);
91466
+ const backup = join83(dest, `${name}.operator-backup-${process.pid}-${Date.now()}`);
91004
91467
  try {
91005
91468
  renameSync18(destSkill, backup);
91006
91469
  transferred = true;
@@ -91014,7 +91477,7 @@ function syncBundledSkills(opts) {
91014
91477
  continue;
91015
91478
  }
91016
91479
  }
91017
- stageAndSwap(join82(source, name), destSkill, dest, name);
91480
+ stageAndSwap(join83(source, name), destSkill, dest, name);
91018
91481
  if (!transferred && (priorSkills.has(name) || existed))
91019
91482
  result.updated.push(name);
91020
91483
  else
@@ -91024,7 +91487,7 @@ function syncBundledSkills(opts) {
91024
91487
  for (const name of priorSkills) {
91025
91488
  if (shippedSet.has(name))
91026
91489
  continue;
91027
- const target = join82(dest, name);
91490
+ const target = join83(dest, name);
91028
91491
  if (existsSync78(target)) {
91029
91492
  rmSync13(target, { recursive: true, force: true });
91030
91493
  result.removed.push(name);
@@ -91043,8 +91506,8 @@ function syncBundledSkills(opts) {
91043
91506
  skills: shipped,
91044
91507
  updatedAt: new Date().toISOString()
91045
91508
  };
91046
- const manifestPath = join82(dest, BUNDLED_SKILL_MANIFEST_NAME);
91047
- const manifestTmp = join82(dest, `${BUNDLED_SKILL_MANIFEST_NAME}.tmp-${process.pid}-${Date.now()}`);
91509
+ const manifestPath = join83(dest, BUNDLED_SKILL_MANIFEST_NAME);
91510
+ const manifestTmp = join83(dest, `${BUNDLED_SKILL_MANIFEST_NAME}.tmp-${process.pid}-${Date.now()}`);
91048
91511
  writeFileSync30(manifestTmp, JSON.stringify(manifest, null, 2) + `
91049
91512
  `, "utf8");
91050
91513
  renameSync18(manifestTmp, manifestPath);
@@ -91336,7 +91799,7 @@ init_component_versions();
91336
91799
  function defaultPersistPin(configPath) {
91337
91800
  return (pin) => {
91338
91801
  const path7 = configPath ?? findConfigFile();
91339
- const before = readFileSync73(path7, "utf8");
91802
+ const before = readFileSync74(path7, "utf8");
91340
91803
  const after = setReleasePinInConfig(before, pin);
91341
91804
  if (after === before)
91342
91805
  return;
@@ -91350,13 +91813,13 @@ function defaultPersistPin(configPath) {
91350
91813
  } catch {}
91351
91814
  };
91352
91815
  }
91353
- var DEFAULT_COMPOSE_PATH2 = join83(homedir43(), ".switchroom", "compose", "docker-compose.yml");
91816
+ var DEFAULT_COMPOSE_PATH2 = join84(homedir44(), ".switchroom", "compose", "docker-compose.yml");
91354
91817
  function runningFromSwitchroomCheckout(scriptPath) {
91355
91818
  let dir = dirname33(scriptPath);
91356
91819
  for (let i = 0;i < 12; i++) {
91357
- if (existsSync80(join83(dir, ".git"))) {
91820
+ if (existsSync80(join84(dir, ".git"))) {
91358
91821
  try {
91359
- const pkg = JSON.parse(readFileSync73(join83(dir, "package.json"), "utf-8"));
91822
+ const pkg = JSON.parse(readFileSync74(join84(dir, "package.json"), "utf-8"));
91360
91823
  if (pkg.name === "switchroom")
91361
91824
  return true;
91362
91825
  } catch {}
@@ -91622,7 +92085,7 @@ function planUpdate(opts) {
91622
92085
  return;
91623
92086
  }
91624
92087
  const source = resolve45(import.meta.dirname, "../../skills");
91625
- const dest = join83(homedir43(), ".switchroom", "skills", "_bundled");
92088
+ const dest = join84(homedir44(), ".switchroom", "skills", "_bundled");
91626
92089
  if (!existsSync80(source)) {
91627
92090
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
91628
92091
  `);
@@ -91654,11 +92117,11 @@ function planUpdate(opts) {
91654
92117
  run: () => {
91655
92118
  if (opts.syncBundledSkillsFn)
91656
92119
  return;
91657
- const dest = join83(homedir43(), ".switchroom", "skills", "_bundled");
92120
+ const dest = join84(homedir44(), ".switchroom", "skills", "_bundled");
91658
92121
  if (!existsSync80(dest)) {
91659
92122
  return;
91660
92123
  }
91661
- const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync80(join83(dest, key)));
92124
+ const missing = getBuiltinDefaultSkillEntries().map((e) => e.key).filter((key) => !existsSync80(join84(dest, key)));
91662
92125
  if (missing.length > 0) {
91663
92126
  throw new Error(`verify-bundled-skills: builtin default skill(s) missing from the pool after sync: ` + `${missing.join(", ")}. These ship in the CLI package and must exist in ${dest}. ` + `This is a broken sync or a packaging regression \u2014 the pool is not converged.`);
91664
92127
  }
@@ -91693,7 +92156,7 @@ function planUpdate(opts) {
91693
92156
  description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
91694
92157
  run: () => {
91695
92158
  try {
91696
- const composeText = readFileSync73(composePath, "utf8");
92159
+ const composeText = readFileSync74(composePath, "utf8");
91697
92160
  const pf = validateBindSources(composeText);
91698
92161
  if (!pf.ok)
91699
92162
  throw new Error(formatPreflightError(pf));
@@ -91772,10 +92235,10 @@ function defaultStatusProbe(composePath) {
91772
92235
  } catch {}
91773
92236
  let dir = dirname33(scriptPath);
91774
92237
  for (let i = 0;i < 8; i++) {
91775
- const pkgPath = join83(dir, "package.json");
92238
+ const pkgPath = join84(dir, "package.json");
91776
92239
  if (existsSync80(pkgPath)) {
91777
92240
  try {
91778
- const pkg = JSON.parse(readFileSync73(pkgPath, "utf-8"));
92241
+ const pkg = JSON.parse(readFileSync74(pkgPath, "utf-8"));
91779
92242
  if (typeof pkg.version === "string")
91780
92243
  cliVersion = pkg.version;
91781
92244
  } catch (err) {
@@ -92044,22 +92507,22 @@ function registerUpdateCommand(program3) {
92044
92507
  // src/cli/rollout.ts
92045
92508
  init_helpers();
92046
92509
  import { spawnSync as spawnSync18 } from "node:child_process";
92047
- import { readFileSync as readFileSync75, chownSync as chownSync9, statSync as statSync47 } from "node:fs";
92048
- import { homedir as homedir45 } from "node:os";
92510
+ import { readFileSync as readFileSync76, chownSync as chownSync9, statSync as statSync47 } from "node:fs";
92511
+ import { homedir as homedir46 } from "node:os";
92049
92512
 
92050
92513
  // src/cli/rollout-pin-journal.ts
92051
92514
  import {
92052
92515
  existsSync as existsSync81,
92053
- readFileSync as readFileSync74,
92516
+ readFileSync as readFileSync75,
92054
92517
  writeFileSync as writeFileSync31,
92055
92518
  renameSync as renameSync20,
92056
92519
  unlinkSync as unlinkSync15,
92057
92520
  statSync as statSync46,
92058
92521
  mkdirSync as mkdirSync46
92059
92522
  } from "node:fs";
92060
- import { homedir as homedir44 } from "node:os";
92523
+ import { homedir as homedir45 } from "node:os";
92061
92524
  import { createHash as createHash17 } from "node:crypto";
92062
- import { join as join84, basename as basename9, resolve as resolve46, dirname as dirname34 } from "node:path";
92525
+ import { join as join85, basename as basename9, resolve as resolve46, dirname as dirname34 } from "node:path";
92063
92526
  init_flock();
92064
92527
  var PIN_JOURNAL_MAX_AGE_MS = 15 * 60 * 1000;
92065
92528
  var STATE_DIR_NAME = ".switchroom";
@@ -92072,12 +92535,12 @@ function pinJournalDir(configPath) {
92072
92535
  if (basename9(dir) === STATE_DIR_NAME)
92073
92536
  return dir;
92074
92537
  }
92075
- return join84(homedir44(), STATE_DIR_NAME);
92538
+ return join85(homedir45(), STATE_DIR_NAME);
92076
92539
  }
92077
92540
  function pinJournalPath(configPath) {
92078
92541
  const abs = resolve46(configPath);
92079
92542
  const key = createHash17("sha256").update(abs).digest("hex").slice(0, 12);
92080
- return join84(pinJournalDir(abs), `.rollout-pin-journal.${basename9(abs)}.${key}.json`);
92543
+ return join85(pinJournalDir(abs), `.rollout-pin-journal.${basename9(abs)}.${key}.json`);
92081
92544
  }
92082
92545
  function isPidAlive(pid) {
92083
92546
  if (!Number.isInteger(pid) || pid <= 0)
@@ -92111,7 +92574,7 @@ function readPinJournal(configPath, warn = (m) => process.stderr.write(m)) {
92111
92574
  const p = pinJournalPath(configPath);
92112
92575
  let raw;
92113
92576
  try {
92114
- raw = readFileSync74(p, "utf8");
92577
+ raw = readFileSync75(p, "utf8");
92115
92578
  } catch (e) {
92116
92579
  if (existsSync81(p)) {
92117
92580
  warn(`\u26a0\ufe0f rollout pin journal: ${p} exists but could not be read ` + `(${e.message}). A provisional \`release.pin\` may be ` + `uncommitted \u2014 verify it host-side before the next reconcile.
@@ -92164,7 +92627,7 @@ function beginPinPersist(configPath, pin, opts = {}) {
92164
92627
  warn(`\u26a0\ufe0f rollout pin journal: overwriting an ABANDONED journal at ${p} ` + `(pid ${existing.pid} recorded ${existing.at}, provisional pin ` + `${existing.pin}). Its roll never committed or reverted; \`release.pin\` ` + `in ${configPath} may still name that unproven build \u2014 verify it.
92165
92628
  `);
92166
92629
  }
92167
- const priorPin = getReleasePinFromConfig(readFileSync74(configPath, "utf8"));
92630
+ const priorPin = getReleasePinFromConfig(readFileSync75(configPath, "utf8"));
92168
92631
  const journal = {
92169
92632
  v: 1,
92170
92633
  configPath,
@@ -92207,7 +92670,7 @@ function rollbackPinPersist(configPath, opts = {}) {
92207
92670
  }
92208
92671
  }
92209
92672
  try {
92210
- const current = readFileSync74(configPath, "utf8");
92673
+ const current = readFileSync75(configPath, "utf8");
92211
92674
  const next = journal.priorPin ? setReleasePinInConfig(current, journal.priorPin) : deleteReleasePinInConfig(current);
92212
92675
  let mode = 384;
92213
92676
  try {
@@ -92306,6 +92769,8 @@ function checkDowngrade(opts) {
92306
92769
 
92307
92770
  // src/cli/rollout.ts
92308
92771
  init_agent_owned_tree();
92772
+ init_component_versions();
92773
+ var VERIFY_COMPONENTS_STEP = "verify-components";
92309
92774
  function normalizeVersion(v) {
92310
92775
  return v.trim().replace(/^v/, "");
92311
92776
  }
@@ -92339,6 +92804,7 @@ function planRollout(agents, opts = {}) {
92339
92804
  }
92340
92805
  steps.push({ kind: "refresh-hindsight" });
92341
92806
  steps.push({ kind: "sweep" });
92807
+ steps.push({ kind: "verify-components" });
92342
92808
  return steps;
92343
92809
  }
92344
92810
  if (opts.pinToPersist)
@@ -92353,6 +92819,7 @@ function planRollout(agents, opts = {}) {
92353
92819
  steps.push({ kind: "refresh-hindsight" });
92354
92820
  }
92355
92821
  steps.push({ kind: "sweep" });
92822
+ steps.push({ kind: "verify-components" });
92356
92823
  return steps;
92357
92824
  }
92358
92825
  function formatRolloutPlan(steps, target) {
@@ -92382,6 +92849,9 @@ function formatRolloutPlan(steps, target) {
92382
92849
  case "sweep":
92383
92850
  lines.push(` ${n}. sweep \u2014 print per-agent version table`);
92384
92851
  break;
92852
+ case "verify-components":
92853
+ lines.push(` ${n}. verify-components \u2014 assert EVERY in-scope component is on ${target} ` + `(same inventory \`switchroom update --check\` uses); non-zero exit if any is behind`);
92854
+ break;
92385
92855
  }
92386
92856
  }
92387
92857
  lines.push("");
@@ -92429,9 +92899,17 @@ function encodeRolloutResultLine(result) {
92429
92899
  ...result.got !== undefined ? { got: result.got } : {},
92430
92900
  ...result.timedOut ? { timedOut: true } : {},
92431
92901
  ...result.pinReverted ? { pinReverted: true } : {},
92902
+ ...result.drifted && result.drifted.length > 0 ? { drifted: result.drifted } : {},
92432
92903
  warnings: result.warnings
92433
92904
  });
92434
92905
  }
92906
+ function evaluateRolloutDrift(components, target, steps, hostdContext) {
92907
+ const report = detectComponentDrift(components, `v${normalizeVersion(target)}`);
92908
+ const owners = gatedOwners(steps.map((s) => s.kind), hostdContext);
92909
+ const rolledAgents = new Set(steps.flatMap((s) => s.kind === "restart-agent" ? [s.agent] : []));
92910
+ const { gated, exempt } = partitionDrift(report.behind, { owners, rolledAgents });
92911
+ return { gated, exempt, target: report.target };
92912
+ }
92435
92913
  function executeRollout(steps, target, deps, execOpts = {}) {
92436
92914
  const targetNorm = normalizeVersion(target);
92437
92915
  const rolled = [];
@@ -92440,6 +92918,7 @@ function executeRollout(steps, target, deps, execOpts = {}) {
92440
92918
  const totalAgents = steps.filter((s) => s.kind === "restart-agent").length;
92441
92919
  let agentIndex = 0;
92442
92920
  let pinPersisted = false;
92921
+ let driftVerdict = null;
92443
92922
  const fail4 = (partial) => {
92444
92923
  let pinReverted = false;
92445
92924
  if (pinPersisted && deps.revertPin) {
@@ -92580,6 +93059,25 @@ function executeRollout(steps, target, deps, execOpts = {}) {
92580
93059
  }
92581
93060
  break;
92582
93061
  }
93062
+ case "verify-components": {
93063
+ deps.log(`ROLL_STEP verify-components \u2014 asserting every in-scope component is on ${target}`);
93064
+ if (!deps.collectComponents) {
93065
+ warnings.push(`verify-components ran with NO component collector wired, so this ` + `roll did not prove the host converged. Confirm with ` + `\`switchroom update --check\` \u2014 anything still BEHIND ${target} ` + `was NOT caught here.`);
93066
+ break;
93067
+ }
93068
+ let inventory;
93069
+ try {
93070
+ inventory = deps.collectComponents();
93071
+ } catch (e) {
93072
+ warnings.push(`verify-components could not read the component inventory ` + `(${e.message}), so this roll did not prove the host ` + `converged. Confirm with \`switchroom update --check\`.`);
93073
+ break;
93074
+ }
93075
+ driftVerdict = evaluateRolloutDrift(inventory, target, steps, execOpts.hostdContext === true);
93076
+ for (const c of driftVerdict.exempt) {
93077
+ warnings.push(`${c.name} is on ${c.version ?? "an unreadable version"}, behind ` + `${target}, but was OUTSIDE this roll's scope (no step in this ` + `plan owns it). Finish it: ` + `${remediationFor(classifyComponent(c), `v${normalizeVersion(target)}`)}`);
93078
+ }
93079
+ break;
93080
+ }
92583
93081
  }
92584
93082
  }
92585
93083
  } catch (e) {
@@ -92599,10 +93097,25 @@ function executeRollout(steps, target, deps, execOpts = {}) {
92599
93097
  warnings.push(`roll succeeded but committing the durable release.pin threw: ` + `${e.message}. The pin IS written; a stale rollout ` + `journal may cause a later boot to revert it \u2014 verify host-side.`);
92600
93098
  }
92601
93099
  }
93100
+ if (driftVerdict && driftVerdict.gated.length > 0) {
93101
+ const normalizedTarget = `v${normalizeVersion(target)}`;
93102
+ for (const c of driftVerdict.gated) {
93103
+ deps.log(` \u2717 ${c.name} \u2192 ${c.version ?? "<unreadable>"} (expected ${target}) \u2014 STILL BEHIND`);
93104
+ warnings.push(`${c.name} is STILL on ${c.version ?? "an unreadable version"} after ` + `the roll to ${target} \u2014 it was in this roll's scope and did not ` + `converge. Finish it: ` + `${remediationFor(classifyComponent(c), normalizedTarget)}`);
93105
+ }
93106
+ deps.log(` \u2717 verify-components FAILED \u2014 ${driftVerdict.gated.length} component(s) ` + `still behind ${target}: ${driftVerdict.gated.map((c) => c.name).join(", ")}`);
93107
+ return {
93108
+ ok: false,
93109
+ rolled,
93110
+ warnings,
93111
+ failedStep: VERIFY_COMPONENTS_STEP,
93112
+ drifted: driftVerdict.gated.map((c) => c.name)
93113
+ };
93114
+ }
92602
93115
  return { ok: true, rolled, warnings };
92603
93116
  }
92604
93117
  function resolveRollbackTarget(auditLogPath) {
92605
- const logPath = auditLogPath ?? defaultAuditLogPath2(homedir45());
93118
+ const logPath = auditLogPath ?? defaultAuditLogPath2(homedir46());
92606
93119
  let raw;
92607
93120
  try {
92608
93121
  raw = readAuditRaw(logPath, {
@@ -92714,8 +93227,14 @@ function createRolloutDeps(params) {
92714
93227
  const r = dockerRun(args);
92715
93228
  return r.ok ? r.stdout : null;
92716
93229
  }),
93230
+ collectComponents: () => collectComponents(SWITCHROOM_VERSION, (cmd, args) => {
93231
+ if (cmd !== "docker")
93232
+ return { status: 1, stdout: "" };
93233
+ const r = dockerRun(args);
93234
+ return { status: r.ok ? 0 : 1, stdout: r.stdout };
93235
+ }),
92717
93236
  persistPin: (pin) => {
92718
- const before = readFileSync75(configPath, "utf8");
93237
+ const before = readFileSync76(configPath, "utf8");
92719
93238
  const after = setReleasePinInConfig(before, pin);
92720
93239
  if (after === before)
92721
93240
  return false;
@@ -92852,6 +93371,15 @@ function registerRolloutCommand(program3) {
92852
93371
  process.stdout.write(encodeRolloutResultLine(result) + `
92853
93372
  `);
92854
93373
  }
93374
+ if (!result.ok && result.failedStep === VERIFY_COMPONENTS_STEP) {
93375
+ process.stderr.write(`
93376
+ \u2717 Rollout INCOMPLETE \u2014 ${(result.drifted ?? []).length} component(s) ` + `still behind ${target}: ${(result.drifted ?? []).join(", ") || "unknown"}.
93377
+ ` + ` ${result.rolled.length} agent(s) DID reach ${target} and the pin is ` + `committed \u2014 re-running the agent roll will not fix this.
93378
+ ` + ` Run the per-component command named in the warning(s) above, then ` + `\`switchroom update --check\` to confirm the host is converged.
93379
+ `);
93380
+ process.exitCode = 1;
93381
+ return;
93382
+ }
92855
93383
  if (!result.ok) {
92856
93384
  process.stderr.write(`
92857
93385
  \u2717 Rollout STOPPED at ${result.failedStep}` + (result.failedAgent ? ` (${result.failedAgent} \u2192 ${result.got ?? "unreachable"})` : "") + `.
@@ -92894,8 +93422,8 @@ init_helpers();
92894
93422
  init_lifecycle();
92895
93423
  init_resolve_version();
92896
93424
  import { execSync as execSync3 } from "node:child_process";
92897
- import { existsSync as existsSync82, readFileSync as readFileSync76 } from "node:fs";
92898
- import { dirname as dirname35, join as join85 } from "node:path";
93425
+ import { existsSync as existsSync82, readFileSync as readFileSync77 } from "node:fs";
93426
+ import { dirname as dirname35, join as join86 } from "node:path";
92899
93427
  function getClaudeCodeVersion() {
92900
93428
  try {
92901
93429
  const out = execSync3("claude --version 2>/dev/null", {
@@ -92945,11 +93473,11 @@ function formatUptime3(timestamp) {
92945
93473
  function locateSwitchroomInstallDir() {
92946
93474
  let dir = import.meta.dirname;
92947
93475
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
92948
- const pkgPath = join85(dir, "package.json");
93476
+ const pkgPath = join86(dir, "package.json");
92949
93477
  if (existsSync82(pkgPath)) {
92950
93478
  try {
92951
- const pkg = JSON.parse(readFileSync76(pkgPath, "utf-8"));
92952
- if (pkg.name === "switchroom" && existsSync82(join85(dir, ".git"))) {
93479
+ const pkg = JSON.parse(readFileSync77(pkgPath, "utf-8"));
93480
+ if (pkg.name === "switchroom" && existsSync82(join86(dir, ".git"))) {
92953
93481
  return dir;
92954
93482
  }
92955
93483
  } catch {}
@@ -93133,12 +93661,12 @@ import {
93133
93661
  statSync as statSync48,
93134
93662
  unlinkSync as unlinkSync16
93135
93663
  } from "node:fs";
93136
- import { join as join86 } from "node:path";
93664
+ import { join as join87 } from "node:path";
93137
93665
  var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
93138
93666
  var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
93139
93667
  var MIN_KEEP = 2;
93140
93668
  function collectSessionJsonl(claudeConfigDir) {
93141
- const projects = join86(claudeConfigDir, "projects");
93669
+ const projects = join87(claudeConfigDir, "projects");
93142
93670
  if (!existsSync83(projects))
93143
93671
  return [];
93144
93672
  const found = [];
@@ -93150,7 +93678,7 @@ function collectSessionJsonl(claudeConfigDir) {
93150
93678
  return;
93151
93679
  }
93152
93680
  for (const name of entries) {
93153
- const full = join86(dir, name);
93681
+ const full = join87(dir, name);
93154
93682
  let st;
93155
93683
  try {
93156
93684
  st = statSync48(full);
@@ -93279,14 +93807,14 @@ import {
93279
93807
  mkdirSync as mkdirSync47,
93280
93808
  openSync as openSync16,
93281
93809
  readdirSync as readdirSync29,
93282
- readFileSync as readFileSync77,
93810
+ readFileSync as readFileSync78,
93283
93811
  renameSync as renameSync21,
93284
93812
  statSync as statSync49,
93285
93813
  unlinkSync as unlinkSync17,
93286
93814
  writeFileSync as writeFileSync32,
93287
93815
  writeSync as writeSync9
93288
93816
  } from "node:fs";
93289
- import { join as join87 } from "node:path";
93817
+ import { join as join88 } from "node:path";
93290
93818
  import { randomBytes as randomBytes13 } from "node:crypto";
93291
93819
  import { execSync as execSync4 } from "node:child_process";
93292
93820
 
@@ -93717,12 +94245,12 @@ function redactedMarker(ruleId) {
93717
94245
  var ISSUES_FILE = "issues.jsonl";
93718
94246
  var ISSUES_LOCK = "issues.lock";
93719
94247
  function readAll(stateDir) {
93720
- const path7 = join87(stateDir, ISSUES_FILE);
94248
+ const path7 = join88(stateDir, ISSUES_FILE);
93721
94249
  if (!existsSync84(path7))
93722
94250
  return [];
93723
94251
  let raw;
93724
94252
  try {
93725
- raw = readFileSync77(path7, "utf-8");
94253
+ raw = readFileSync78(path7, "utf-8");
93726
94254
  } catch {
93727
94255
  return [];
93728
94256
  }
@@ -93795,7 +94323,7 @@ function record(stateDir, input, nowFn = Date.now) {
93795
94323
  });
93796
94324
  }
93797
94325
  function resolve49(stateDir, fingerprint, nowFn = Date.now) {
93798
- if (!existsSync84(join87(stateDir, ISSUES_FILE)))
94326
+ if (!existsSync84(join88(stateDir, ISSUES_FILE)))
93799
94327
  return 0;
93800
94328
  return withLock(stateDir, () => {
93801
94329
  const all = readAll(stateDir);
@@ -93813,7 +94341,7 @@ function resolve49(stateDir, fingerprint, nowFn = Date.now) {
93813
94341
  });
93814
94342
  }
93815
94343
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
93816
- if (!existsSync84(join87(stateDir, ISSUES_FILE)))
94344
+ if (!existsSync84(join88(stateDir, ISSUES_FILE)))
93817
94345
  return 0;
93818
94346
  return withLock(stateDir, () => {
93819
94347
  const all = readAll(stateDir);
@@ -93831,7 +94359,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
93831
94359
  });
93832
94360
  }
93833
94361
  function prune(stateDir, opts = {}) {
93834
- if (!existsSync84(join87(stateDir, ISSUES_FILE)))
94362
+ if (!existsSync84(join88(stateDir, ISSUES_FILE)))
93835
94363
  return 0;
93836
94364
  return withLock(stateDir, () => {
93837
94365
  const all = readAll(stateDir);
@@ -93864,7 +94392,7 @@ function ensureDir(stateDir) {
93864
94392
  mkdirSync47(stateDir, { recursive: true });
93865
94393
  }
93866
94394
  function writeAll(stateDir, events) {
93867
- const path7 = join87(stateDir, ISSUES_FILE);
94395
+ const path7 = join88(stateDir, ISSUES_FILE);
93868
94396
  sweepOrphanTmpFiles(stateDir);
93869
94397
  const tmp = `${path7}.tmp-${process.pid}-${randomBytes13(4).toString("hex")}`;
93870
94398
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -93886,7 +94414,7 @@ function sweepOrphanTmpFiles(stateDir) {
93886
94414
  for (const entry of entries) {
93887
94415
  if (!entry.startsWith(TMP_PREFIX))
93888
94416
  continue;
93889
- const tmpPath = join87(stateDir, entry);
94417
+ const tmpPath = join88(stateDir, entry);
93890
94418
  try {
93891
94419
  const stat = statSync49(tmpPath);
93892
94420
  if (stat.mtimeMs < cutoff) {
@@ -93898,7 +94426,7 @@ function sweepOrphanTmpFiles(stateDir) {
93898
94426
  var LOCK_RETRY_MS = 25;
93899
94427
  var LOCK_TIMEOUT_MS = 1e4;
93900
94428
  function withLock(stateDir, fn) {
93901
- const lockPath = join87(stateDir, ISSUES_LOCK);
94429
+ const lockPath = join88(stateDir, ISSUES_LOCK);
93902
94430
  const startedAt = Date.now();
93903
94431
  let fd = null;
93904
94432
  while (fd === null) {
@@ -93933,7 +94461,7 @@ function withLock(stateDir, fn) {
93933
94461
  function tryStealStaleLock(lockPath) {
93934
94462
  let pidStr;
93935
94463
  try {
93936
- pidStr = readFileSync77(lockPath, "utf-8").trim();
94464
+ pidStr = readFileSync78(lockPath, "utf-8").trim();
93937
94465
  } catch {
93938
94466
  return true;
93939
94467
  }
@@ -94182,20 +94710,20 @@ function relTime(deltaMs) {
94182
94710
  // src/cli/deps.ts
94183
94711
  init_source();
94184
94712
  import { existsSync as existsSync87 } from "node:fs";
94185
- import { homedir as homedir48 } from "node:os";
94186
- import { join as join90, resolve as resolve50 } from "node:path";
94713
+ import { homedir as homedir49 } from "node:os";
94714
+ import { join as join91, resolve as resolve50 } from "node:path";
94187
94715
 
94188
94716
  // src/deps/python.ts
94189
94717
  import { createHash as createHash18 } from "node:crypto";
94190
94718
  import {
94191
94719
  existsSync as existsSync85,
94192
94720
  mkdirSync as mkdirSync48,
94193
- readFileSync as readFileSync78,
94721
+ readFileSync as readFileSync79,
94194
94722
  rmSync as rmSync15,
94195
94723
  writeFileSync as writeFileSync33
94196
94724
  } from "node:fs";
94197
- import { dirname as dirname36, join as join88 } from "node:path";
94198
- import { homedir as homedir46 } from "node:os";
94725
+ import { dirname as dirname36, join as join89 } from "node:path";
94726
+ import { homedir as homedir47 } from "node:os";
94199
94727
  import { execFileSync as execFileSync24 } from "node:child_process";
94200
94728
 
94201
94729
  class PythonEnvError extends Error {
@@ -94207,10 +94735,10 @@ class PythonEnvError extends Error {
94207
94735
  }
94208
94736
  }
94209
94737
  function defaultPythonCacheRoot() {
94210
- return join88(homedir46(), ".switchroom", "deps", "python");
94738
+ return join89(homedir47(), ".switchroom", "deps", "python");
94211
94739
  }
94212
94740
  function hashFile(path7) {
94213
- return createHash18("sha256").update(readFileSync78(path7)).digest("hex");
94741
+ return createHash18("sha256").update(readFileSync79(path7)).digest("hex");
94214
94742
  }
94215
94743
  function ensurePythonEnv(opts) {
94216
94744
  const { skillName, requirementsPath, force = false } = opts;
@@ -94219,14 +94747,14 @@ function ensurePythonEnv(opts) {
94219
94747
  if (!existsSync85(requirementsPath)) {
94220
94748
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
94221
94749
  }
94222
- const venvDir = join88(cacheRoot, skillName);
94223
- const stampPath = join88(venvDir, ".requirements.sha256");
94224
- const binDir = join88(venvDir, "bin");
94225
- const pythonBin = join88(binDir, "python");
94226
- const pipBin = join88(binDir, "pip");
94750
+ const venvDir = join89(cacheRoot, skillName);
94751
+ const stampPath = join89(venvDir, ".requirements.sha256");
94752
+ const binDir = join89(venvDir, "bin");
94753
+ const pythonBin = join89(binDir, "python");
94754
+ const pipBin = join89(binDir, "pip");
94227
94755
  const targetHash = hashFile(requirementsPath);
94228
94756
  if (!force && existsSync85(stampPath) && existsSync85(pythonBin)) {
94229
- const existingHash = readFileSync78(stampPath, "utf8").trim();
94757
+ const existingHash = readFileSync79(stampPath, "utf8").trim();
94230
94758
  if (existingHash === targetHash) {
94231
94759
  return {
94232
94760
  skillName,
@@ -94278,12 +94806,12 @@ import {
94278
94806
  copyFileSync as copyFileSync12,
94279
94807
  existsSync as existsSync86,
94280
94808
  mkdirSync as mkdirSync49,
94281
- readFileSync as readFileSync79,
94809
+ readFileSync as readFileSync80,
94282
94810
  rmSync as rmSync16,
94283
94811
  writeFileSync as writeFileSync34
94284
94812
  } from "node:fs";
94285
- import { dirname as dirname37, join as join89 } from "node:path";
94286
- import { homedir as homedir47 } from "node:os";
94813
+ import { dirname as dirname37, join as join90 } from "node:path";
94814
+ import { homedir as homedir48 } from "node:os";
94287
94815
  import { execFileSync as execFileSync25 } from "node:child_process";
94288
94816
 
94289
94817
  class NodeEnvError extends Error {
@@ -94306,23 +94834,23 @@ var LOCKFILES_FOR = {
94306
94834
  npm: ["package-lock.json"]
94307
94835
  };
94308
94836
  function defaultNodeCacheRoot() {
94309
- return join89(homedir47(), ".switchroom", "deps", "node");
94837
+ return join90(homedir48(), ".switchroom", "deps", "node");
94310
94838
  }
94311
94839
  function hashDepInputs(packageJsonPath) {
94312
94840
  const sourceDir = dirname37(packageJsonPath);
94313
94841
  const hasher = createHash19("sha256");
94314
94842
  hasher.update(`package.json
94315
94843
  `);
94316
- hasher.update(readFileSync79(packageJsonPath));
94844
+ hasher.update(readFileSync80(packageJsonPath));
94317
94845
  for (const lockName of ALL_LOCKFILES) {
94318
- const lockPath = join89(sourceDir, lockName);
94846
+ const lockPath = join90(sourceDir, lockName);
94319
94847
  if (existsSync86(lockPath)) {
94320
94848
  hasher.update(`
94321
94849
  `);
94322
94850
  hasher.update(lockName);
94323
94851
  hasher.update(`
94324
94852
  `);
94325
- hasher.update(readFileSync79(lockPath));
94853
+ hasher.update(readFileSync80(lockPath));
94326
94854
  }
94327
94855
  }
94328
94856
  return hasher.digest("hex");
@@ -94335,13 +94863,13 @@ function ensureNodeEnv(opts) {
94335
94863
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
94336
94864
  }
94337
94865
  const sourceDir = dirname37(packageJsonPath);
94338
- const envDir = join89(cacheRoot, skillName);
94339
- const stampPath = join89(envDir, ".package.sha256");
94340
- const nodeModulesDir = join89(envDir, "node_modules");
94341
- const binDir = join89(nodeModulesDir, ".bin");
94866
+ const envDir = join90(cacheRoot, skillName);
94867
+ const stampPath = join90(envDir, ".package.sha256");
94868
+ const nodeModulesDir = join90(envDir, "node_modules");
94869
+ const binDir = join90(nodeModulesDir, ".bin");
94342
94870
  const targetHash = hashDepInputs(packageJsonPath);
94343
94871
  if (!force && existsSync86(stampPath) && existsSync86(nodeModulesDir)) {
94344
- const existingHash = readFileSync79(stampPath, "utf8").trim();
94872
+ const existingHash = readFileSync80(stampPath, "utf8").trim();
94345
94873
  if (existingHash === targetHash) {
94346
94874
  return {
94347
94875
  skillName,
@@ -94356,12 +94884,12 @@ function ensureNodeEnv(opts) {
94356
94884
  rmSync16(envDir, { recursive: true, force: true });
94357
94885
  }
94358
94886
  mkdirSync49(envDir, { recursive: true });
94359
- copyFileSync12(packageJsonPath, join89(envDir, "package.json"));
94887
+ copyFileSync12(packageJsonPath, join90(envDir, "package.json"));
94360
94888
  let copiedLockfile = false;
94361
94889
  for (const lockName of LOCKFILES_FOR[installer]) {
94362
- const lockPath = join89(sourceDir, lockName);
94890
+ const lockPath = join90(sourceDir, lockName);
94363
94891
  if (existsSync86(lockPath)) {
94364
- copyFileSync12(lockPath, join89(envDir, lockName));
94892
+ copyFileSync12(lockPath, join90(envDir, lockName));
94365
94893
  copiedLockfile = true;
94366
94894
  }
94367
94895
  }
@@ -94390,7 +94918,7 @@ function ensureNodeEnv(opts) {
94390
94918
 
94391
94919
  // src/cli/deps.ts
94392
94920
  function builtinSkillsRoot() {
94393
- return resolve50(homedir48(), ".switchroom/skills/_bundled");
94921
+ return resolve50(homedir49(), ".switchroom/skills/_bundled");
94394
94922
  }
94395
94923
  function registerDepsCommand(program3) {
94396
94924
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
@@ -94400,13 +94928,13 @@ function registerDepsCommand(program3) {
94400
94928
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
94401
94929
  process.exit(1);
94402
94930
  }
94403
- const skillDir = join90(skillsRoot, skill);
94931
+ const skillDir = join91(skillsRoot, skill);
94404
94932
  if (!existsSync87(skillDir)) {
94405
94933
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
94406
94934
  process.exit(1);
94407
94935
  }
94408
- const requirementsPath = join90(skillDir, "requirements.txt");
94409
- const packageJsonPath = join90(skillDir, "package.json");
94936
+ const requirementsPath = join91(skillDir, "requirements.txt");
94937
+ const packageJsonPath = join91(skillDir, "package.json");
94410
94938
  const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync87(requirementsPath));
94411
94939
  const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync87(packageJsonPath));
94412
94940
  let did = 0;
@@ -95360,8 +95888,8 @@ function safeParseInt(value, fallback) {
95360
95888
  init_helpers();
95361
95889
  init_loader();
95362
95890
  init_merge();
95363
- import { copyFileSync as copyFileSync13, existsSync as existsSync89, readFileSync as readFileSync80, writeFileSync as writeFileSync35 } from "node:fs";
95364
- import { join as join91, resolve as resolve52 } from "node:path";
95891
+ import { copyFileSync as copyFileSync13, existsSync as existsSync89, readFileSync as readFileSync81, writeFileSync as writeFileSync35 } from "node:fs";
95892
+ import { join as join92, resolve as resolve52 } from "node:path";
95365
95893
  init_scaffold();
95366
95894
  init_profiles();
95367
95895
  init_schema();
@@ -95387,7 +95915,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
95387
95915
  profileName,
95388
95916
  profilePath,
95389
95917
  workspaceDir,
95390
- soulPath: join91(workspaceDir, "SOUL.md"),
95918
+ soulPath: join92(workspaceDir, "SOUL.md"),
95391
95919
  soul: merged.soul
95392
95920
  };
95393
95921
  }
@@ -95408,7 +95936,7 @@ function registerSoulCommand(program3) {
95408
95936
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
95409
95937
  process.exit(1);
95410
95938
  }
95411
- process.stdout.write(readFileSync80(t.soulPath, "utf-8"));
95939
+ process.stdout.write(readFileSync81(t.soulPath, "utf-8"));
95412
95940
  }));
95413
95941
  cmd.command("reset <agent>").description("Re-seed SOUL.md from the agent's current profile " + "(backs the existing file up to SOUL.md.bak first)").option("-y, --yes", "Skip the confirmation prompt").action(withConfigError(async (agentName, opts) => {
95414
95942
  const t = resolveSoulTargetOrExit(program3, agentName);
@@ -95453,8 +95981,8 @@ function registerSoulCommand(program3) {
95453
95981
  // src/cli/debug.ts
95454
95982
  init_helpers();
95455
95983
  init_loader();
95456
- import { existsSync as existsSync90, readFileSync as readFileSync81, readdirSync as readdirSync30, statSync as statSync50 } from "node:fs";
95457
- import { resolve as resolve53, join as join92 } from "node:path";
95984
+ import { existsSync as existsSync90, readFileSync as readFileSync82, readdirSync as readdirSync30, statSync as statSync50 } from "node:fs";
95985
+ import { resolve as resolve53, join as join93 } from "node:path";
95458
95986
  import { createHash as createHash20 } from "node:crypto";
95459
95987
  init_merge();
95460
95988
  init_hindsight2();
@@ -95465,11 +95993,11 @@ function estimateTokens(bytes) {
95465
95993
  return Math.round(bytes / 3.7);
95466
95994
  }
95467
95995
  function readMcpServerNames(agentDir) {
95468
- const mcpPath = join92(agentDir, ".mcp.json");
95996
+ const mcpPath = join93(agentDir, ".mcp.json");
95469
95997
  if (!existsSync90(mcpPath))
95470
95998
  return [];
95471
95999
  try {
95472
- const parsed = JSON.parse(readFileSync81(mcpPath, "utf-8"));
96000
+ const parsed = JSON.parse(readFileSync82(mcpPath, "utf-8"));
95473
96001
  return Object.keys(parsed.mcpServers ?? {});
95474
96002
  } catch {
95475
96003
  return null;
@@ -95479,7 +96007,7 @@ function sha256(content) {
95479
96007
  return createHash20("sha256").update(content).digest("hex").slice(0, 16);
95480
96008
  }
95481
96009
  function findLatestTranscriptJsonl(claudeConfigDir) {
95482
- const projectsDir = join92(claudeConfigDir, "projects");
96010
+ const projectsDir = join93(claudeConfigDir, "projects");
95483
96011
  if (!existsSync90(projectsDir))
95484
96012
  return;
95485
96013
  try {
@@ -95488,8 +96016,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
95488
96016
  for (const entry of entries) {
95489
96017
  if (!entry.isDirectory())
95490
96018
  continue;
95491
- const projectPath = join92(projectsDir, entry.name);
95492
- const transcriptPath = join92(projectPath, "transcript.jsonl");
96019
+ const projectPath = join93(projectsDir, entry.name);
96020
+ const transcriptPath = join93(projectPath, "transcript.jsonl");
95493
96021
  if (!existsSync90(transcriptPath))
95494
96022
  continue;
95495
96023
  const stat3 = statSync50(transcriptPath);
@@ -95504,7 +96032,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
95504
96032
  }
95505
96033
  function extractLatestUserMessage(transcriptPath) {
95506
96034
  try {
95507
- const content = readFileSync81(transcriptPath, "utf-8");
96035
+ const content = readFileSync82(transcriptPath, "utf-8");
95508
96036
  const lines = content.trim().split(`
95509
96037
  `).filter(Boolean);
95510
96038
  for (let i = lines.length - 1;i >= 0; i--) {
@@ -95558,11 +96086,11 @@ function registerDebugCommand(program3) {
95558
96086
  process.exit(1);
95559
96087
  }
95560
96088
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
95561
- const claudeConfigDir = join92(agentDir, ".claude");
95562
- const claudeMdPath = join92(agentDir, "CLAUDE.md");
95563
- const soulMdPath = join92(agentDir, "SOUL.md");
95564
- const workspaceSoulMdPath = join92(workspaceDir, "SOUL.md");
95565
- const handoffPath = join92(agentDir, ".handoff.md");
96089
+ const claudeConfigDir = join93(agentDir, ".claude");
96090
+ const claudeMdPath = join93(agentDir, "CLAUDE.md");
96091
+ const soulMdPath = join93(agentDir, "SOUL.md");
96092
+ const workspaceSoulMdPath = join93(workspaceDir, "SOUL.md");
96093
+ const handoffPath = join93(agentDir, ".handoff.md");
95566
96094
  const lastN = parseInt(opts.last, 10);
95567
96095
  if (isNaN(lastN) || lastN < 1) {
95568
96096
  console.error("--last must be a positive integer");
@@ -95608,7 +96136,7 @@ function registerDebugCommand(program3) {
95608
96136
  }
95609
96137
  console.log(`=== Append System Prompt (per-session) ===
95610
96138
  `);
95611
- const handoffContent = existsSync90(handoffPath) ? readFileSync81(handoffPath, "utf-8") : "";
96139
+ const handoffContent = existsSync90(handoffPath) ? readFileSync82(handoffPath, "utf-8") : "";
95612
96140
  if (handoffContent.trim().length > 0) {
95613
96141
  console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
95614
96142
  console.log(handoffContent);
@@ -95619,7 +96147,7 @@ function registerDebugCommand(program3) {
95619
96147
  }
95620
96148
  console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
95621
96149
  `);
95622
- const claudeMdContent = existsSync90(claudeMdPath) ? readFileSync81(claudeMdPath, "utf-8") : "";
96150
+ const claudeMdContent = existsSync90(claudeMdPath) ? readFileSync82(claudeMdPath, "utf-8") : "";
95623
96151
  if (claudeMdContent.trim().length > 0) {
95624
96152
  console.log(`(${formatBytes(claudeMdContent.length)})`);
95625
96153
  console.log(claudeMdContent);
@@ -95630,7 +96158,7 @@ function registerDebugCommand(program3) {
95630
96158
  }
95631
96159
  console.log(`=== Persona (SOUL.md) ===
95632
96160
  `);
95633
- const soulMdContent = existsSync90(soulMdPath) ? readFileSync81(soulMdPath, "utf-8") : existsSync90(workspaceSoulMdPath) ? readFileSync81(workspaceSoulMdPath, "utf-8") : "";
96161
+ const soulMdContent = existsSync90(soulMdPath) ? readFileSync82(soulMdPath, "utf-8") : existsSync90(workspaceSoulMdPath) ? readFileSync82(workspaceSoulMdPath, "utf-8") : "";
95634
96162
  if (soulMdContent.trim().length > 0) {
95635
96163
  console.log(`(${formatBytes(soulMdContent.length)})`);
95636
96164
  console.log(soulMdContent);
@@ -95691,11 +96219,11 @@ function registerDebugCommand(program3) {
95691
96219
  const soulMdBytes = soulMdContent.length;
95692
96220
  const perTurnBytes = dynamicResult.concatenated.length;
95693
96221
  const userBytes = userMessage?.text.length ?? 0;
95694
- const fleetDir = join92(agentsDir, "..", "fleet");
95695
- const fleetInvPath = join92(fleetDir, "switchroom-invariants.md");
95696
- const fleetClaudePath = join92(fleetDir, "CLAUDE.md");
95697
- const fleetInvBytes = existsSync90(fleetInvPath) ? readFileSync81(fleetInvPath, "utf-8").length : 0;
95698
- const fleetClaudeBytes = existsSync90(fleetClaudePath) ? readFileSync81(fleetClaudePath, "utf-8").length : 0;
96222
+ const fleetDir = join93(agentsDir, "..", "fleet");
96223
+ const fleetInvPath = join93(fleetDir, "switchroom-invariants.md");
96224
+ const fleetClaudePath = join93(fleetDir, "CLAUDE.md");
96225
+ const fleetInvBytes = existsSync90(fleetInvPath) ? readFileSync82(fleetInvPath, "utf-8").length : 0;
96226
+ const fleetClaudeBytes = existsSync90(fleetClaudePath) ? readFileSync82(fleetClaudePath, "utf-8").length : 0;
95699
96227
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
95700
96228
  const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
95701
96229
  console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
@@ -95729,27 +96257,27 @@ init_source();
95729
96257
  // src/worktree/claim.ts
95730
96258
  import { execFileSync as execFileSync26 } from "node:child_process";
95731
96259
  import { closeSync as closeSync17, mkdirSync as mkdirSync51, openSync as openSync17, existsSync as existsSync92, unlinkSync as unlinkSync19 } from "node:fs";
95732
- import { join as join94, resolve as resolve55 } from "node:path";
95733
- import { homedir as homedir50 } from "node:os";
96260
+ import { join as join95, resolve as resolve55 } from "node:path";
96261
+ import { homedir as homedir51 } from "node:os";
95734
96262
  import { randomBytes as randomBytes14 } from "node:crypto";
95735
96263
 
95736
96264
  // src/worktree/registry.ts
95737
96265
  import {
95738
96266
  mkdirSync as mkdirSync50,
95739
96267
  writeFileSync as writeFileSync36,
95740
- readFileSync as readFileSync82,
96268
+ readFileSync as readFileSync83,
95741
96269
  readdirSync as readdirSync31,
95742
96270
  unlinkSync as unlinkSync18,
95743
96271
  existsSync as existsSync91,
95744
96272
  renameSync as renameSync22
95745
96273
  } from "node:fs";
95746
- import { join as join93, resolve as resolve54 } from "node:path";
95747
- import { homedir as homedir49 } from "node:os";
96274
+ import { join as join94, resolve as resolve54 } from "node:path";
96275
+ import { homedir as homedir50 } from "node:os";
95748
96276
  function registryDir() {
95749
- return resolve54(process.env.SWITCHROOM_WORKTREE_DIR ?? join93(homedir49(), ".switchroom", "worktrees"));
96277
+ return resolve54(process.env.SWITCHROOM_WORKTREE_DIR ?? join94(homedir50(), ".switchroom", "worktrees"));
95750
96278
  }
95751
96279
  function recordPath(id) {
95752
- return join93(registryDir(), `${id}.json`);
96280
+ return join94(registryDir(), `${id}.json`);
95753
96281
  }
95754
96282
  function ensureDir2() {
95755
96283
  mkdirSync50(registryDir(), { recursive: true });
@@ -95765,7 +96293,7 @@ function writeRecord(record2) {
95765
96293
  function readRecord(id) {
95766
96294
  const path10 = recordPath(id);
95767
96295
  try {
95768
- const raw = readFileSync82(path10, "utf8");
96296
+ const raw = readFileSync83(path10, "utf8");
95769
96297
  return JSON.parse(raw);
95770
96298
  } catch {
95771
96299
  return null;
@@ -95800,7 +96328,7 @@ function acquireRepoLock(repoPath) {
95800
96328
  const lockDir = registryDir();
95801
96329
  mkdirSync51(lockDir, { recursive: true });
95802
96330
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
95803
- const lockPath = join94(lockDir, `.lock-${lockName}`);
96331
+ const lockPath = join95(lockDir, `.lock-${lockName}`);
95804
96332
  const deadline = Date.now() + 5000;
95805
96333
  let fd = null;
95806
96334
  while (fd === null) {
@@ -95827,7 +96355,7 @@ function acquireRepoLock(repoPath) {
95827
96355
  }
95828
96356
  var DEFAULT_CONCURRENCY = 5;
95829
96357
  function worktreesBaseDir() {
95830
- return resolve55(process.env.SWITCHROOM_WORKTREE_BASE ?? join94(homedir50(), ".switchroom", "worktree-checkouts"));
96358
+ return resolve55(process.env.SWITCHROOM_WORKTREE_BASE ?? join95(homedir51(), ".switchroom", "worktree-checkouts"));
95831
96359
  }
95832
96360
  function shortId() {
95833
96361
  return randomBytes14(4).toString("hex");
@@ -95849,7 +96377,7 @@ function resolveRepoPath(repo, codeRepos) {
95849
96377
  }
95850
96378
  function expandHome(p) {
95851
96379
  if (p.startsWith("~/"))
95852
- return join94(homedir50(), p.slice(2));
96380
+ return join95(homedir51(), p.slice(2));
95853
96381
  return p;
95854
96382
  }
95855
96383
  async function claimWorktree(input, codeRepos) {
@@ -95877,7 +96405,7 @@ async function claimWorktree(input, codeRepos) {
95877
96405
  branch = `task/${taskSuffix}-${id}`;
95878
96406
  const baseDir = worktreesBaseDir();
95879
96407
  mkdirSync51(baseDir, { recursive: true });
95880
- worktreePath = join94(baseDir, `${id}-${taskSuffix}`);
96408
+ worktreePath = join95(baseDir, `${id}-${taskSuffix}`);
95881
96409
  const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
95882
96410
  const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
95883
96411
  const now = new Date().toISOString();
@@ -96100,15 +96628,15 @@ function runReaper(nowMs, deps = {}) {
96100
96628
  import { execFileSync as execFileSync29 } from "node:child_process";
96101
96629
  import {
96102
96630
  existsSync as existsSync95,
96103
- readFileSync as readFileSync83,
96631
+ readFileSync as readFileSync84,
96104
96632
  readdirSync as readdirSync32,
96105
96633
  statSync as statSync51,
96106
96634
  renameSync as renameSync23,
96107
96635
  mkdirSync as mkdirSync52,
96108
96636
  rmSync as rmSync17
96109
96637
  } from "node:fs";
96110
- import { homedir as homedir51 } from "node:os";
96111
- import { join as join95, resolve as resolve56 } from "node:path";
96638
+ import { homedir as homedir52 } from "node:os";
96639
+ import { join as join96, resolve as resolve56 } from "node:path";
96112
96640
  function parseGitdirPointer(dotGitFileContents) {
96113
96641
  const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
96114
96642
  return m ? m[1] : null;
@@ -96223,17 +96751,17 @@ function defaultPrSignal(repo, branch, exec) {
96223
96751
  }
96224
96752
  }
96225
96753
  function trashRoot() {
96226
- return resolve56(process.env.SWITCHROOM_WORKTREE_TRASH ?? join95(homedir51(), ".switchroom", "worktree-gc-trash"));
96754
+ return resolve56(process.env.SWITCHROOM_WORKTREE_TRASH ?? join96(homedir52(), ".switchroom", "worktree-gc-trash"));
96227
96755
  }
96228
96756
  function planGc(roots, deps = {}) {
96229
96757
  const exists = deps.existsSync ?? existsSync95;
96230
96758
  const readDir = deps.readDir ?? ((p) => readdirSync32(p));
96231
- const readFile4 = deps.readFile ?? ((p) => readFileSync83(p, "utf8"));
96759
+ const readFile4 = deps.readFile ?? ((p) => readFileSync84(p, "utf8"));
96232
96760
  const stat3 = deps.stat ?? ((p) => statSync51(p));
96233
96761
  const exec = deps.exec ?? defaultExec3;
96234
96762
  const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
96235
96763
  const stamp = deps.dateStamp ?? "undated";
96236
- const trash = join95(trashRoot(), stamp);
96764
+ const trash = join96(trashRoot(), stamp);
96237
96765
  let claimed;
96238
96766
  try {
96239
96767
  claimed = new Set(listRecords().map((r) => resolve56(r.path)));
@@ -96269,10 +96797,10 @@ function planGc(roots, deps = {}) {
96269
96797
  continue;
96270
96798
  }
96271
96799
  for (const name of entries) {
96272
- const dir = join95(root, name);
96800
+ const dir = join96(root, name);
96273
96801
  if (isEphemeralPath(dir))
96274
96802
  continue;
96275
- const dotGit = join95(dir, ".git");
96803
+ const dotGit = join96(dir, ".git");
96276
96804
  if (!exists(dotGit))
96277
96805
  continue;
96278
96806
  let st;
@@ -96301,7 +96829,7 @@ function planGc(roots, deps = {}) {
96301
96829
  ownerRepos.add(repoRoot);
96302
96830
  if (exists(ptr))
96303
96831
  continue;
96304
- orphans.push({ dir, owner: repoRoot, dest: join95(trash, name) });
96832
+ orphans.push({ dir, owner: repoRoot, dest: join96(trash, name) });
96305
96833
  }
96306
96834
  }
96307
96835
  const registered = [];
@@ -96412,7 +96940,7 @@ function listTrashEntries(nowMs, deps = {}) {
96412
96940
  return [];
96413
96941
  const out = [];
96414
96942
  for (const stamp of readDir(root)) {
96415
- const stampDir = join95(root, stamp);
96943
+ const stampDir = join96(root, stamp);
96416
96944
  let names;
96417
96945
  try {
96418
96946
  names = readDir(stampDir);
@@ -96420,7 +96948,7 @@ function listTrashEntries(nowMs, deps = {}) {
96420
96948
  continue;
96421
96949
  }
96422
96950
  for (const name of names) {
96423
- const p = join95(stampDir, name);
96951
+ const p = join96(stampDir, name);
96424
96952
  let mtimeMs = nowMs;
96425
96953
  try {
96426
96954
  mtimeMs = statSync51(p).mtimeMs;
@@ -96444,7 +96972,7 @@ function purgeTrash(paths) {
96444
96972
  return { deleted, errors: errors2 };
96445
96973
  }
96446
96974
  function defaultRoots() {
96447
- return [join95(homedir51(), "code")];
96975
+ return [join96(homedir52(), "code")];
96448
96976
  }
96449
96977
 
96450
96978
  // src/cli/worktree.ts
@@ -96672,7 +97200,7 @@ import {
96672
97200
  rmSync as rmSync18,
96673
97201
  writeFileSync as writeFileSync37
96674
97202
  } from "node:fs";
96675
- import { join as join96 } from "node:path";
97203
+ import { join as join97 } from "node:path";
96676
97204
  function encodeCredentialsFilename(email) {
96677
97205
  const SAFE = new Set([
96678
97206
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -96862,16 +97390,16 @@ function resolveCredentialsDir(env2) {
96862
97390
  if (explicit && explicit.length > 0)
96863
97391
  return explicit;
96864
97392
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
96865
- return join96(stateBase, "google-workspace-mcp", "credentials");
97393
+ return join97(stateBase, "google-workspace-mcp", "credentials");
96866
97394
  }
96867
97395
  function writeSeedFile(dir, email, seed) {
96868
97396
  mkdirSync53(dir, { recursive: true, mode: 448 });
96869
97397
  chmodSync16(dir, 448);
96870
97398
  for (const name of readdirSync33(dir)) {
96871
- rmSync18(join96(dir, name), { force: true, recursive: true });
97399
+ rmSync18(join97(dir, name), { force: true, recursive: true });
96872
97400
  }
96873
97401
  const filename = encodeCredentialsFilename(email);
96874
- const filePath = join96(dir, filename);
97402
+ const filePath = join97(dir, filename);
96875
97403
  writeFileSync37(filePath, JSON.stringify(seed), { mode: 384 });
96876
97404
  chmodSync16(filePath, 384);
96877
97405
  return filePath;
@@ -97031,7 +97559,7 @@ function registerDriveMcpLauncherCommand(program3) {
97031
97559
  init_scaffold_integration();
97032
97560
  import { spawn as spawn6 } from "node:child_process";
97033
97561
  import { writeFileSync as writeFileSync38, mkdirSync as mkdirSync54 } from "node:fs";
97034
- import { dirname as dirname38, join as join97 } from "node:path";
97562
+ import { dirname as dirname38, join as join98 } from "node:path";
97035
97563
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
97036
97564
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
97037
97565
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
@@ -97076,7 +97604,7 @@ function heartbeatPath(agentName, account) {
97076
97604
  const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
97077
97605
  if (override) {
97078
97606
  const base = slug ? `m365-launcher-${agentName}-${slug}` : `m365-launcher-${agentName}`;
97079
- return join97(override, `${base}.heartbeat.json`);
97607
+ return join98(override, `${base}.heartbeat.json`);
97080
97608
  }
97081
97609
  return slug ? `/state/agent/m365-launcher-${slug}.heartbeat.json` : "/state/agent/m365-launcher.heartbeat.json";
97082
97610
  }
@@ -97432,9 +97960,9 @@ function registerNotionMcpLauncherCommand(program3) {
97432
97960
 
97433
97961
  // src/cli/hindsight-mcp-shim.ts
97434
97962
  init_hindsight();
97435
- import { mkdirSync as mkdirSync56, readFileSync as readFileSync84, renameSync as renameSync24, writeFileSync as writeFileSync40 } from "node:fs";
97963
+ import { mkdirSync as mkdirSync56, readFileSync as readFileSync85, renameSync as renameSync24, writeFileSync as writeFileSync40 } from "node:fs";
97436
97964
  import { tmpdir as tmpdir5 } from "node:os";
97437
- import { join as join98 } from "node:path";
97965
+ import { join as join99 } from "node:path";
97438
97966
  import { createInterface as createInterface6 } from "node:readline";
97439
97967
  var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
97440
97968
  "2025-06-18",
@@ -97660,12 +98188,12 @@ class HindsightShim {
97660
98188
  `));
97661
98189
  }
97662
98190
  get cachePath() {
97663
- return join98(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
98191
+ return join99(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
97664
98192
  }
97665
98193
  writeCache(result) {
97666
98194
  try {
97667
98195
  mkdirSync56(this.opts.cacheDir, { recursive: true });
97668
- const tmp = join98(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
98196
+ const tmp = join99(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
97669
98197
  writeFileSync40(tmp, JSON.stringify(result, null, 2) + `
97670
98198
  `);
97671
98199
  renameSync24(tmp, this.cachePath);
@@ -97675,7 +98203,7 @@ class HindsightShim {
97675
98203
  }
97676
98204
  readCache() {
97677
98205
  try {
97678
- const parsed = JSON.parse(readFileSync84(this.cachePath, "utf-8"));
98206
+ const parsed = JSON.parse(readFileSync85(this.cachePath, "utf-8"));
97679
98207
  if (Array.isArray(parsed.tools))
97680
98208
  return parsed;
97681
98209
  return null;
@@ -97838,7 +98366,7 @@ function resolveShimOptionsFromEnv(env2) {
97838
98366
  return {
97839
98367
  url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
97840
98368
  bankId: env2.HINDSIGHT_BANK_ID || "",
97841
- cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join98(home2, ".hindsight-shim")
98369
+ cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join99(home2, ".hindsight-shim")
97842
98370
  };
97843
98371
  }
97844
98372
  function registerHindsightMcpShimCommand(program3) {
@@ -97851,7 +98379,7 @@ function registerHindsightMcpShimCommand(program3) {
97851
98379
 
97852
98380
  // src/cli/deliver-file.ts
97853
98381
  init_client2();
97854
- import { readFileSync as readFileSync85, statSync as statSync52 } from "node:fs";
98382
+ import { readFileSync as readFileSync86, statSync as statSync52 } from "node:fs";
97855
98383
  import { basename as basename13 } from "node:path";
97856
98384
 
97857
98385
  // src/delivery/onedrive.ts
@@ -98191,7 +98719,7 @@ async function defaultResolveProvider() {
98191
98719
  async function runDeliverFile(localPath, deps = {}) {
98192
98720
  const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
98193
98721
  const sizeOf = deps.fileSize ?? ((p) => statSync52(p).size);
98194
- const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync85(p)));
98722
+ const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync86(p)));
98195
98723
  const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
98196
98724
  let size;
98197
98725
  try {
@@ -98512,9 +99040,9 @@ function runRedactStdin() {
98512
99040
  }
98513
99041
 
98514
99042
  // src/cli/status-ask.ts
98515
- import { readFileSync as readFileSync86, existsSync as existsSync97, readdirSync as readdirSync34 } from "node:fs";
98516
- import { join as join99 } from "node:path";
98517
- import { homedir as homedir52 } from "node:os";
99043
+ import { readFileSync as readFileSync87, existsSync as existsSync97, readdirSync as readdirSync34 } from "node:fs";
99044
+ import { join as join100 } from "node:path";
99045
+ import { homedir as homedir53 } from "node:os";
98518
99046
 
98519
99047
  // src/status-ask/report.ts
98520
99048
  function parseJsonl(content) {
@@ -98788,7 +99316,7 @@ function runReport(opts) {
98788
99316
  for (const src of sources) {
98789
99317
  let content;
98790
99318
  try {
98791
- content = readFileSync86(src.path, "utf-8");
99319
+ content = readFileSync87(src.path, "utf-8");
98792
99320
  } catch (err) {
98793
99321
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
98794
99322
  `);
@@ -98849,7 +99377,7 @@ function resolveSources(explicitPath) {
98849
99377
  const config = loadConfig();
98850
99378
  agentsDir = resolveAgentsDir(config);
98851
99379
  } catch {
98852
- agentsDir = join99(homedir52(), ".switchroom", "agents");
99380
+ agentsDir = join100(homedir53(), ".switchroom", "agents");
98853
99381
  }
98854
99382
  if (!existsSync97(agentsDir))
98855
99383
  return [];
@@ -98861,7 +99389,7 @@ function resolveSources(explicitPath) {
98861
99389
  return [];
98862
99390
  }
98863
99391
  for (const name of entries) {
98864
- const path10 = join99(agentsDir, name, "runtime-metrics.jsonl");
99392
+ const path10 = join100(agentsDir, name, "runtime-metrics.jsonl");
98865
99393
  if (existsSync97(path10)) {
98866
99394
  sources.push({ path: path10, agent: name });
98867
99395
  }
@@ -98897,27 +99425,27 @@ import {
98897
99425
  mkdirSync as mkdirSync57,
98898
99426
  openSync as openSync18,
98899
99427
  readdirSync as readdirSync35,
98900
- readFileSync as readFileSync87,
99428
+ readFileSync as readFileSync88,
98901
99429
  renameSync as renameSync25,
98902
99430
  statSync as statSync53,
98903
99431
  unlinkSync as unlinkSync20,
98904
99432
  writeSync as writeSync10
98905
99433
  } from "node:fs";
98906
- import { join as join100, resolve as resolve57 } from "node:path";
99434
+ import { join as join101, resolve as resolve57 } from "node:path";
98907
99435
  var STAGING_SUBDIR = ".staging";
98908
99436
  function overlayPathsFor(agent, opts = {}) {
98909
99437
  const base = opts.root ? resolve57(opts.root, agent) : resolve57(resolveDualPath(`~/.switchroom/agents/${agent}`));
98910
- const scheduleDir = join100(base, "schedule.d");
98911
- const scheduleStagingDir = join100(scheduleDir, STAGING_SUBDIR);
98912
- const skillsDir = join100(base, "skills.d");
98913
- const skillsStagingDir = join100(skillsDir, STAGING_SUBDIR);
99438
+ const scheduleDir = join101(base, "schedule.d");
99439
+ const scheduleStagingDir = join101(scheduleDir, STAGING_SUBDIR);
99440
+ const skillsDir = join101(base, "skills.d");
99441
+ const skillsStagingDir = join101(skillsDir, STAGING_SUBDIR);
98914
99442
  return {
98915
99443
  agentRoot: base,
98916
99444
  scheduleDir,
98917
99445
  scheduleStagingDir,
98918
99446
  skillsDir,
98919
99447
  skillsStagingDir,
98920
- lockPath: join100(base, ".lock"),
99448
+ lockPath: join101(base, ".lock"),
98921
99449
  stagingDir: scheduleStagingDir
98922
99450
  };
98923
99451
  }
@@ -98971,8 +99499,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
98971
99499
  const paths = overlayPathsFor(agent, opts);
98972
99500
  return withAgentLock(paths, () => {
98973
99501
  ensureDirs(paths);
98974
- const stagingPath = join100(paths.scheduleStagingDir, `${slug}.yaml`);
98975
- const finalPath = join100(paths.scheduleDir, `${slug}.yaml`);
99502
+ const stagingPath = join101(paths.scheduleStagingDir, `${slug}.yaml`);
99503
+ const finalPath = join101(paths.scheduleDir, `${slug}.yaml`);
98976
99504
  const fd = openSync18(stagingPath, "w", 384);
98977
99505
  try {
98978
99506
  writeSync10(fd, yamlText);
@@ -98988,8 +99516,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
98988
99516
  const paths = overlayPathsFor(agent, opts);
98989
99517
  return withAgentLock(paths, () => {
98990
99518
  ensureSkillsDirs(paths);
98991
- const stagingPath = join100(paths.skillsStagingDir, `${slug}.yaml`);
98992
- const finalPath = join100(paths.skillsDir, `${slug}.yaml`);
99519
+ const stagingPath = join101(paths.skillsStagingDir, `${slug}.yaml`);
99520
+ const finalPath = join101(paths.skillsDir, `${slug}.yaml`);
98993
99521
  const fd = openSync18(stagingPath, "w", 384);
98994
99522
  try {
98995
99523
  writeSync10(fd, yamlText);
@@ -99004,7 +99532,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
99004
99532
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
99005
99533
  const paths = overlayPathsFor(agent, opts);
99006
99534
  return withAgentLock(paths, () => {
99007
- const finalPath = join100(paths.skillsDir, `${slug}.yaml`);
99535
+ const finalPath = join101(paths.skillsDir, `${slug}.yaml`);
99008
99536
  if (!existsSync98(finalPath))
99009
99537
  return false;
99010
99538
  unlinkSync20(finalPath);
@@ -99019,9 +99547,9 @@ function listSkillsOverlayEntries(agent, opts = {}) {
99019
99547
  for (const name of readdirSync35(paths.skillsDir)) {
99020
99548
  if (!/\.ya?ml$/i.test(name))
99021
99549
  continue;
99022
- const full = join100(paths.skillsDir, name);
99550
+ const full = join101(paths.skillsDir, name);
99023
99551
  try {
99024
- const raw = readFileSync87(full, "utf-8");
99552
+ const raw = readFileSync88(full, "utf-8");
99025
99553
  const slug = name.replace(/\.ya?ml$/i, "");
99026
99554
  out.push({ slug, path: full, raw });
99027
99555
  } catch {}
@@ -99031,7 +99559,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
99031
99559
  function deleteOverlayEntry(agent, slug, opts = {}) {
99032
99560
  const paths = overlayPathsFor(agent, opts);
99033
99561
  return withAgentLock(paths, () => {
99034
- const finalPath = join100(paths.scheduleDir, `${slug}.yaml`);
99562
+ const finalPath = join101(paths.scheduleDir, `${slug}.yaml`);
99035
99563
  if (!existsSync98(finalPath))
99036
99564
  return false;
99037
99565
  unlinkSync20(finalPath);
@@ -99046,9 +99574,9 @@ function listOverlayEntries(agent, opts = {}) {
99046
99574
  for (const name of readdirSync35(paths.scheduleDir)) {
99047
99575
  if (!/\.ya?ml$/i.test(name))
99048
99576
  continue;
99049
- const full = join100(paths.scheduleDir, name);
99577
+ const full = join101(paths.scheduleDir, name);
99050
99578
  try {
99051
- const raw = readFileSync87(full, "utf-8");
99579
+ const raw = readFileSync88(full, "utf-8");
99052
99580
  const slug = name.replace(/\.ya?ml$/i, "");
99053
99581
  out.push({ slug, path: full, raw });
99054
99582
  } catch {}
@@ -99273,18 +99801,18 @@ import {
99273
99801
  mkdirSync as mkdirSync58,
99274
99802
  openSync as openSync19,
99275
99803
  readdirSync as readdirSync36,
99276
- readFileSync as readFileSync88,
99804
+ readFileSync as readFileSync89,
99277
99805
  renameSync as renameSync26,
99278
99806
  unlinkSync as unlinkSync21,
99279
99807
  writeFileSync as writeFileSync41,
99280
99808
  writeSync as writeSync11
99281
99809
  } from "node:fs";
99282
- import { join as join101 } from "node:path";
99810
+ import { join as join102 } from "node:path";
99283
99811
  import { randomBytes as randomBytes15 } from "node:crypto";
99284
99812
  var STAGE_ID_PREFIX = "cap_";
99285
99813
  function pendingDir(agent, opts = {}) {
99286
99814
  const paths = overlayPathsFor(agent, opts);
99287
- return join101(paths.scheduleDir, ".pending");
99815
+ return join102(paths.scheduleDir, ".pending");
99288
99816
  }
99289
99817
  function ensurePendingDir(agent, opts = {}) {
99290
99818
  const dir = pendingDir(agent, opts);
@@ -99297,8 +99825,8 @@ function newStageId() {
99297
99825
  function stagePendingScheduleEntry(opts) {
99298
99826
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
99299
99827
  const stageId = opts.stageId ?? newStageId();
99300
- const yamlPath = join101(dir, `${stageId}.yaml`);
99301
- const metaPath = join101(dir, `${stageId}.meta.json`);
99828
+ const yamlPath = join102(dir, `${stageId}.yaml`);
99829
+ const metaPath = join102(dir, `${stageId}.meta.json`);
99302
99830
  const meta = {
99303
99831
  v: 1,
99304
99832
  stage_id: stageId,
@@ -99332,12 +99860,12 @@ function listPendingScheduleEntries(agent, opts = {}) {
99332
99860
  if (!name.endsWith(".meta.json"))
99333
99861
  continue;
99334
99862
  const stageId = name.slice(0, -".meta.json".length);
99335
- const metaPath = join101(dir, name);
99336
- const yamlPath = join101(dir, `${stageId}.yaml`);
99863
+ const metaPath = join102(dir, name);
99864
+ const yamlPath = join102(dir, `${stageId}.yaml`);
99337
99865
  if (!existsSync99(yamlPath))
99338
99866
  continue;
99339
99867
  try {
99340
- const meta = JSON.parse(readFileSync88(metaPath, "utf-8"));
99868
+ const meta = JSON.parse(readFileSync89(metaPath, "utf-8"));
99341
99869
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
99342
99870
  continue;
99343
99871
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -99352,7 +99880,7 @@ function commitPendingScheduleEntry(opts) {
99352
99880
  return { committed: false, reason: "not_found" };
99353
99881
  const slug = match.meta.entry.name ?? match.stageId;
99354
99882
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
99355
- const finalPath = join101(paths.scheduleDir, `${slug}.yaml`);
99883
+ const finalPath = join102(paths.scheduleDir, `${slug}.yaml`);
99356
99884
  if (existsSync99(finalPath)) {
99357
99885
  return { committed: false, reason: "slug_collision" };
99358
99886
  }
@@ -99375,7 +99903,7 @@ function denyPendingScheduleEntry(opts) {
99375
99903
  }
99376
99904
 
99377
99905
  // src/cli/agent-config-write.ts
99378
- import { existsSync as existsSync100, readFileSync as readFileSync89 } from "node:fs";
99906
+ import { existsSync as existsSync100, readFileSync as readFileSync90 } from "node:fs";
99379
99907
  import { execFileSync as execFileSync30 } from "node:child_process";
99380
99908
 
99381
99909
  // src/scheduler/schedule-report.ts
@@ -99766,7 +100294,7 @@ function scheduleRemove(opts) {
99766
100294
  let priorContent = null;
99767
100295
  try {
99768
100296
  if (existsSync100(match.path))
99769
- priorContent = readFileSync89(match.path, "utf-8");
100297
+ priorContent = readFileSync90(match.path, "utf-8");
99770
100298
  } catch {}
99771
100299
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
99772
100300
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -99969,7 +100497,7 @@ function registerAgentConfigWriteCommands(program3) {
99969
100497
  }
99970
100498
  let blob;
99971
100499
  if (opts.jsonl) {
99972
- blob = existsSync100(opts.jsonl) ? readFileSync89(opts.jsonl, "utf-8") : "";
100500
+ blob = existsSync100(opts.jsonl) ? readFileSync90(opts.jsonl, "utf-8") : "";
99973
100501
  } else {
99974
100502
  try {
99975
100503
  blob = execFileSync30("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
@@ -100005,7 +100533,7 @@ import { existsSync as existsSync101 } from "node:fs";
100005
100533
  init_reconcile_default_skills();
100006
100534
  init_agent_config();
100007
100535
  var import_yaml23 = __toESM(require_dist(), 1);
100008
- import { join as join102 } from "node:path";
100536
+ import { join as join103 } from "node:path";
100009
100537
  var MAX_SKILLS_PER_AGENT = 20;
100010
100538
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
100011
100539
  function exitCodeFor2(code) {
@@ -100080,7 +100608,7 @@ function skillInstall(opts) {
100080
100608
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
100081
100609
  }
100082
100610
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
100083
- const skillPath = join102(poolDir, skillName);
100611
+ const skillPath = join103(poolDir, skillName);
100084
100612
  if (!existsSync101(skillPath)) {
100085
100613
  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.`);
100086
100614
  }
@@ -100250,7 +100778,7 @@ import {
100250
100778
  mkdirSync as mkdirSync59,
100251
100779
  mkdtempSync as mkdtempSync5,
100252
100780
  openSync as openSync20,
100253
- readFileSync as readFileSync90,
100781
+ readFileSync as readFileSync91,
100254
100782
  readdirSync as readdirSync37,
100255
100783
  realpathSync as realpathSync7,
100256
100784
  renameSync as renameSync27,
@@ -100258,8 +100786,8 @@ import {
100258
100786
  statSync as statSync54,
100259
100787
  writeFileSync as writeFileSync42
100260
100788
  } from "node:fs";
100261
- import { tmpdir as tmpdir6, homedir as homedir53 } from "node:os";
100262
- import { dirname as dirname40, join as join103, relative as relative4, resolve as resolve58 } from "node:path";
100789
+ import { tmpdir as tmpdir6, homedir as homedir54 } from "node:os";
100790
+ import { dirname as dirname40, join as join104, relative as relative4, resolve as resolve58 } from "node:path";
100263
100791
  import { spawnSync as spawnSync20 } from "node:child_process";
100264
100792
 
100265
100793
  // src/cli/skill-common.ts
@@ -100493,10 +101021,10 @@ function scanForClaudeP2(content) {
100493
101021
  function resolveSkillsPoolDir2(override) {
100494
101022
  const raw = override ?? "~/.switchroom/skills";
100495
101023
  if (raw.startsWith("~/")) {
100496
- return join103(homedir53(), raw.slice(2));
101024
+ return join104(homedir54(), raw.slice(2));
100497
101025
  }
100498
101026
  if (raw === "~")
100499
- return homedir53();
101027
+ return homedir54();
100500
101028
  return resolve58(raw);
100501
101029
  }
100502
101030
  function readStdinSync() {
@@ -100532,7 +101060,7 @@ function loadFromDir(dir) {
100532
101060
  const walk2 = (sub) => {
100533
101061
  const entries = readdirSync37(sub, { withFileTypes: true });
100534
101062
  for (const ent of entries) {
100535
- const full = join103(sub, ent.name);
101063
+ const full = join104(sub, ent.name);
100536
101064
  const rel = relative4(abs, full);
100537
101065
  if (ent.isSymbolicLink()) {
100538
101066
  fail4(`refusing to read symlink inside --from dir: ${rel}`);
@@ -100542,7 +101070,7 @@ function loadFromDir(dir) {
100542
101070
  continue;
100543
101071
  }
100544
101072
  if (ent.isFile()) {
100545
- const buf = readFileSync90(full);
101073
+ const buf = readFileSync91(full);
100546
101074
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
100547
101075
  }
100548
101076
  }
@@ -100567,7 +101095,7 @@ function loadFromTarball(tarPath) {
100567
101095
  fail4(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
100568
101096
  }
100569
101097
  }
100570
- const staging = mkdtempSync5(join103(tmpdir6(), "skill-apply-extract-"));
101098
+ const staging = mkdtempSync5(join104(tmpdir6(), "skill-apply-extract-"));
100571
101099
  try {
100572
101100
  const flags = isGz ? ["-xzf"] : ["-xf"];
100573
101101
  const r = spawnSync20("tar", [
@@ -100591,7 +101119,7 @@ function loadFromTarball(tarPath) {
100591
101119
  }
100592
101120
  }
100593
101121
  function loadSingleFile(filePath) {
100594
- const content = readFileSync90(filePath, "utf-8");
101122
+ const content = readFileSync91(filePath, "utf-8");
100595
101123
  return { "SKILL.md": content };
100596
101124
  }
100597
101125
  function loadFromStdin() {
@@ -100653,8 +101181,8 @@ function validatePayload(name, files) {
100653
101181
  errors2.push(`${path10} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
100654
101182
  }
100655
101183
  } else if (PY_SCRIPT_RE2.test(path10)) {
100656
- const tmp = mkdtempSync5(join103(tmpdir6(), "skill-apply-py-"));
100657
- const tmpPy = join103(tmp, "check.py");
101184
+ const tmp = mkdtempSync5(join104(tmpdir6(), "skill-apply-py-"));
101185
+ const tmpPy = join104(tmp, "check.py");
100658
101186
  try {
100659
101187
  writeFileSync42(tmpPy, content);
100660
101188
  const r = spawnSync20("python3", ["-m", "py_compile", tmpPy], {
@@ -100677,12 +101205,12 @@ function diffSummary(currentDir, files) {
100677
101205
  if (existsSync102(currentDir)) {
100678
101206
  const walk2 = (sub) => {
100679
101207
  for (const ent of readdirSync37(sub, { withFileTypes: true })) {
100680
- const full = join103(sub, ent.name);
101208
+ const full = join104(sub, ent.name);
100681
101209
  const rel = relative4(currentDir, full);
100682
101210
  if (ent.isDirectory()) {
100683
101211
  walk2(full);
100684
101212
  } else if (ent.isFile()) {
100685
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync90(full, "utf-8");
101213
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync91(full, "utf-8");
100686
101214
  }
100687
101215
  }
100688
101216
  };
@@ -100713,7 +101241,7 @@ function writePayload(poolDir, name, files) {
100713
101241
  if (!existsSync102(poolDir)) {
100714
101242
  mkdirSync59(poolDir, { recursive: true, mode: 493 });
100715
101243
  }
100716
- const target = join103(poolDir, name);
101244
+ const target = join104(poolDir, name);
100717
101245
  let targetIsSymlink = false;
100718
101246
  try {
100719
101247
  const st = lstatSync12(target);
@@ -100724,11 +101252,11 @@ function writePayload(poolDir, name, files) {
100724
101252
  if (targetIsSymlink) {
100725
101253
  fail4(`refusing to overwrite symlink at ${target}; investigate manually`);
100726
101254
  }
100727
- const staging = mkdtempSync5(join103(poolDir, `.skill-apply-stage-${name}-`));
101255
+ const staging = mkdtempSync5(join104(poolDir, `.skill-apply-stage-${name}-`));
100728
101256
  let oldRename = null;
100729
101257
  try {
100730
101258
  for (const [path10, content] of Object.entries(files)) {
100731
- const full = join103(staging, path10);
101259
+ const full = join104(staging, path10);
100732
101260
  mkdirSync59(dirname40(full), { recursive: true, mode: 493 });
100733
101261
  const fd = openSync20(full, "wx");
100734
101262
  try {
@@ -100806,7 +101334,7 @@ function registerSkillCommand(program3) {
100806
101334
  }
100807
101335
  const config = loadConfig();
100808
101336
  const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
100809
- const currentDir = join103(poolDir, name);
101337
+ const currentDir = join104(poolDir, name);
100810
101338
  console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
100811
101339
  console.log(source_default.bold("Diff vs current pool content:"));
100812
101340
  console.log(diffSummary(currentDir, files));
@@ -100843,7 +101371,7 @@ import {
100843
101371
  mkdirSync as mkdirSync60,
100844
101372
  mkdtempSync as mkdtempSync6,
100845
101373
  openSync as openSync21,
100846
- readFileSync as readFileSync91,
101374
+ readFileSync as readFileSync92,
100847
101375
  readdirSync as readdirSync38,
100848
101376
  renameSync as renameSync28,
100849
101377
  rmSync as rmSync20,
@@ -100851,8 +101379,8 @@ import {
100851
101379
  utimesSync,
100852
101380
  writeFileSync as writeFileSync43
100853
101381
  } from "node:fs";
100854
- import { dirname as dirname41, join as join104, relative as relative5, resolve as resolve59 } from "node:path";
100855
- import { homedir as homedir54, tmpdir as tmpdir7 } from "node:os";
101382
+ import { dirname as dirname41, join as join105, relative as relative5, resolve as resolve59 } from "node:path";
101383
+ import { homedir as homedir55, tmpdir as tmpdir7 } from "node:os";
100856
101384
  import { spawnSync as spawnSync21 } from "node:child_process";
100857
101385
  init_helpers();
100858
101386
  init_agent_config();
@@ -100863,10 +101391,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
100863
101391
  var PERSONAL_SKILLS_SUBPATH = "personal-skills";
100864
101392
  function resolveConfigSkillsDir(agent) {
100865
101393
  const override = process.env.SWITCHROOM_CONFIG_DIR;
100866
- const candidate = override ? resolve59(override) : join104(homedir54(), ".switchroom-config");
101394
+ const candidate = override ? resolve59(override) : join105(homedir55(), ".switchroom-config");
100867
101395
  if (!existsSync103(candidate))
100868
101396
  return null;
100869
- return join104(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
101397
+ return join105(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
100870
101398
  }
100871
101399
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
100872
101400
  function sweepMirrorPriors(configSkillsRoot) {
@@ -100884,7 +101412,7 @@ function sweepMirrorPriors(configSkillsRoot) {
100884
101412
  if (now - ts < MIRROR_PRIOR_TTL_MS)
100885
101413
  continue;
100886
101414
  try {
100887
- rmSync20(join104(configSkillsRoot, ent), { recursive: true, force: true });
101415
+ rmSync20(join105(configSkillsRoot, ent), { recursive: true, force: true });
100888
101416
  } catch {}
100889
101417
  }
100890
101418
  } catch {}
@@ -100893,7 +101421,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
100893
101421
  const configSkillsRoot = resolveConfigSkillsDir(agent);
100894
101422
  if (!configSkillsRoot)
100895
101423
  return;
100896
- const dest = join104(configSkillsRoot, name);
101424
+ const dest = join105(configSkillsRoot, name);
100897
101425
  try {
100898
101426
  if (liveSkillDir !== null) {
100899
101427
  try {
@@ -100908,31 +101436,31 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
100908
101436
  if (liveSkillDir === null) {
100909
101437
  sweepMirrorPriors(configSkillsRoot);
100910
101438
  if (existsSync103(dest)) {
100911
- const trash = join104(configSkillsRoot, `.${name}-trash-${Date.now()}`);
101439
+ const trash = join105(configSkillsRoot, `.${name}-trash-${Date.now()}`);
100912
101440
  renameSync28(dest, trash);
100913
101441
  }
100914
101442
  return;
100915
101443
  }
100916
101444
  mkdirSync60(configSkillsRoot, { recursive: true, mode: 493 });
100917
101445
  sweepMirrorPriors(configSkillsRoot);
100918
- const staging = mkdtempSync6(join104(configSkillsRoot, `.${name}-staging-`));
101446
+ const staging = mkdtempSync6(join105(configSkillsRoot, `.${name}-staging-`));
100919
101447
  const walk2 = (src, dst) => {
100920
101448
  mkdirSync60(dst, { recursive: true, mode: 493 });
100921
101449
  for (const ent of readdirSync38(src, { withFileTypes: true })) {
100922
- const s = join104(src, ent.name);
100923
- const d = join104(dst, ent.name);
101450
+ const s = join105(src, ent.name);
101451
+ const d = join105(dst, ent.name);
100924
101452
  if (ent.isSymbolicLink())
100925
101453
  continue;
100926
101454
  if (ent.isDirectory())
100927
101455
  walk2(s, d);
100928
101456
  else if (ent.isFile()) {
100929
- writeFileSync43(d, readFileSync91(s));
101457
+ writeFileSync43(d, readFileSync92(s));
100930
101458
  }
100931
101459
  }
100932
101460
  };
100933
101461
  walk2(liveSkillDir, staging);
100934
101462
  if (existsSync103(dest)) {
100935
- const prior = join104(configSkillsRoot, `.${name}-prior-${Date.now()}`);
101463
+ const prior = join105(configSkillsRoot, `.${name}-prior-${Date.now()}`);
100936
101464
  renameSync28(dest, prior);
100937
101465
  }
100938
101466
  renameSync28(staging, dest);
@@ -100962,16 +101490,16 @@ function resolveAgent(opts) {
100962
101490
  function resolveAgentsRoot(opts) {
100963
101491
  if (opts.root)
100964
101492
  return resolve59(opts.root);
100965
- return join104(homedir54(), ".switchroom", "agents");
101493
+ return join105(homedir55(), ".switchroom", "agents");
100966
101494
  }
100967
101495
  function personalSkillDir(agentsRoot, agent, name) {
100968
- return join104(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
101496
+ return join105(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
100969
101497
  }
100970
101498
  function trashDir(agentsRoot, agent) {
100971
- return join104(agentsRoot, agent, ".claude", TRASH_DIRNAME);
101499
+ return join105(agentsRoot, agent, ".claude", TRASH_DIRNAME);
100972
101500
  }
100973
101501
  function countPersonalSkills(agentsRoot, agent) {
100974
- const skillsDir = join104(agentsRoot, agent, ".claude", "skills");
101502
+ const skillsDir = join105(agentsRoot, agent, ".claude", "skills");
100975
101503
  if (!existsSync103(skillsDir))
100976
101504
  return 0;
100977
101505
  let n = 0;
@@ -101009,7 +101537,7 @@ function loadFromDir2(dir) {
101009
101537
  const files = {};
101010
101538
  const walk2 = (sub) => {
101011
101539
  for (const ent of readdirSync38(sub, { withFileTypes: true })) {
101012
- const full = join104(sub, ent.name);
101540
+ const full = join105(sub, ent.name);
101013
101541
  if (ent.isSymbolicLink()) {
101014
101542
  fail5(`refusing to read symlink in --from dir: ${relative5(abs, full)}`);
101015
101543
  }
@@ -101019,7 +101547,7 @@ function loadFromDir2(dir) {
101019
101547
  }
101020
101548
  if (ent.isFile()) {
101021
101549
  const rel = relative5(abs, full).replace(/\\/g, "/");
101022
- files[rel] = readFileSync91(full, "utf-8");
101550
+ files[rel] = readFileSync92(full, "utf-8");
101023
101551
  }
101024
101552
  }
101025
101553
  };
@@ -101062,8 +101590,8 @@ function behavioralValidate(files) {
101062
101590
  errors2.push(`${path10} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
101063
101591
  }
101064
101592
  } else if (PY_SCRIPT_RE.test(path10)) {
101065
- const tmp = mkdtempSync6(join104(tmpdir7(), "skill-personal-py-"));
101066
- const tmpPy = join104(tmp, "check.py");
101593
+ const tmp = mkdtempSync6(join105(tmpdir7(), "skill-personal-py-"));
101594
+ const tmpPy = join105(tmp, "check.py");
101067
101595
  try {
101068
101596
  writeFileSync43(tmpPy, content);
101069
101597
  const r = spawnSync21("python3", ["-m", "py_compile", tmpPy], {
@@ -101087,7 +101615,7 @@ function sweepTrash(agentsRoot, agent) {
101087
101615
  for (const ent of readdirSync38(trash, { withFileTypes: true })) {
101088
101616
  if (!ent.isDirectory())
101089
101617
  continue;
101090
- const entPath = join104(trash, ent.name);
101618
+ const entPath = join105(trash, ent.name);
101091
101619
  try {
101092
101620
  const st = statSync55(entPath);
101093
101621
  if (now - st.mtimeMs > TRASH_TTL_MS) {
@@ -101108,11 +101636,11 @@ function writePersonalSkill(targetDir, files) {
101108
101636
  fail5(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
101109
101637
  }
101110
101638
  mkdirSync60(dirname41(targetDir), { recursive: true, mode: 493 });
101111
- const staging = mkdtempSync6(join104(dirname41(targetDir), `.skill-personal-stage-`));
101639
+ const staging = mkdtempSync6(join105(dirname41(targetDir), `.skill-personal-stage-`));
101112
101640
  let oldRename = null;
101113
101641
  try {
101114
101642
  for (const [path10, content] of Object.entries(files)) {
101115
- const full = join104(staging, path10);
101643
+ const full = join105(staging, path10);
101116
101644
  mkdirSync60(dirname41(full), { recursive: true, mode: 493 });
101117
101645
  const fd = openSync21(full, "wx");
101118
101646
  try {
@@ -101219,7 +101747,7 @@ function loadFiles(opts) {
101219
101747
  return loadFromDir2(p);
101220
101748
  }
101221
101749
  if (p.endsWith(".md")) {
101222
- return { "SKILL.md": readFileSync91(p, "utf-8") };
101750
+ return { "SKILL.md": readFileSync92(p, "utf-8") };
101223
101751
  }
101224
101752
  fail5(`--from must be a directory or a .md file. Got: ${opts.from}`);
101225
101753
  }
@@ -101259,10 +101787,10 @@ function editPersonalAction(name, opts) {
101259
101787
  }
101260
101788
  var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
101261
101789
  function defaultSharedRoot() {
101262
- return join104(homedir54(), ".switchroom", "skills");
101790
+ return join105(homedir55(), ".switchroom", "skills");
101263
101791
  }
101264
101792
  function defaultBundledRoot() {
101265
- return join104(homedir54(), ".switchroom", "skills", "_bundled");
101793
+ return join105(homedir55(), ".switchroom", "skills", "_bundled");
101266
101794
  }
101267
101795
  function resolveCloneSource(source, opts) {
101268
101796
  const m = CLONE_SOURCE_RE.exec(source);
@@ -101272,7 +101800,7 @@ function resolveCloneSource(source, opts) {
101272
101800
  const tier = m[1];
101273
101801
  const slug = m[2];
101274
101802
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
101275
- const dir = join104(root, slug);
101803
+ const dir = join105(root, slug);
101276
101804
  if (!existsSync103(dir)) {
101277
101805
  fail5(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
101278
101806
  }
@@ -101288,7 +101816,7 @@ function readSourceFiles(dir) {
101288
101816
  const skipped = [];
101289
101817
  const walk2 = (sub) => {
101290
101818
  for (const ent of readdirSync38(sub, { withFileTypes: true })) {
101291
- const full = join104(sub, ent.name);
101819
+ const full = join105(sub, ent.name);
101292
101820
  if (ent.isSymbolicLink()) {
101293
101821
  continue;
101294
101822
  }
@@ -101308,7 +101836,7 @@ function readSourceFiles(dir) {
101308
101836
  fail5(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
101309
101837
  }
101310
101838
  } catch {}
101311
- files[rel] = readFileSync91(full, "utf-8");
101839
+ files[rel] = readFileSync92(full, "utf-8");
101312
101840
  }
101313
101841
  }
101314
101842
  };
@@ -101399,7 +101927,7 @@ function removePersonalAction(name, opts) {
101399
101927
  const trashRoot2 = trashDir(agentsRoot, agent);
101400
101928
  mkdirSync60(trashRoot2, { recursive: true, mode: 493 });
101401
101929
  const ts = Date.now();
101402
- const trashTarget = join104(trashRoot2, `${name}-${ts}`);
101930
+ const trashTarget = join105(trashRoot2, `${name}-${ts}`);
101403
101931
  renameSync28(target, trashTarget);
101404
101932
  const now = new Date(ts);
101405
101933
  utimesSync(trashTarget, now, now);
@@ -101418,7 +101946,7 @@ function listPersonalAction(opts) {
101418
101946
  const agent = resolveAgent(opts);
101419
101947
  const agentsRoot = resolveAgentsRoot(opts);
101420
101948
  sweepTrash(agentsRoot, agent);
101421
- const skillsDir = join104(agentsRoot, agent, ".claude", "skills");
101949
+ const skillsDir = join105(agentsRoot, agent, ".claude", "skills");
101422
101950
  const personal = [];
101423
101951
  if (existsSync103(skillsDir)) {
101424
101952
  for (const ent of readdirSync38(skillsDir, { withFileTypes: true })) {
@@ -101427,7 +101955,7 @@ function listPersonalAction(opts) {
101427
101955
  if (!ent.name.startsWith(PERSONAL_PREFIX))
101428
101956
  continue;
101429
101957
  const skillName = ent.name.slice(PERSONAL_PREFIX.length);
101430
- const skillPath = join104(skillsDir, ent.name);
101958
+ const skillPath = join105(skillsDir, ent.name);
101431
101959
  let fileCount = 0;
101432
101960
  let totalBytes = 0;
101433
101961
  const walk2 = (sub) => {
@@ -101435,10 +101963,10 @@ function listPersonalAction(opts) {
101435
101963
  if (e.isFile()) {
101436
101964
  fileCount += 1;
101437
101965
  try {
101438
- totalBytes += statSync55(join104(sub, e.name)).size;
101966
+ totalBytes += statSync55(join105(sub, e.name)).size;
101439
101967
  } catch {}
101440
101968
  } else if (e.isDirectory()) {
101441
- walk2(join104(sub, e.name));
101969
+ walk2(join105(sub, e.name));
101442
101970
  }
101443
101971
  }
101444
101972
  };
@@ -101476,12 +102004,12 @@ function registerSkillPersonalCommands(program3) {
101476
102004
 
101477
102005
  // src/cli/self-improve-propose-skill.ts
101478
102006
  import { createConnection as createConnection4 } from "node:net";
101479
- import { homedir as homedir55 } from "node:os";
101480
- import { join as join105 } from "node:path";
101481
- import { readFileSync as readFileSync92 } from "node:fs";
102007
+ import { homedir as homedir56 } from "node:os";
102008
+ import { join as join106 } from "node:path";
102009
+ import { readFileSync as readFileSync93 } from "node:fs";
101482
102010
  var IPC_CONNECT_TIMEOUT_MS = 5000;
101483
102011
  function gatewaySocketPath() {
101484
- return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join105(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join105(homedir55(), ".claude", "channels", "telegram", "gateway.sock"));
102012
+ return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join106(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join106(homedir56(), ".claude", "channels", "telegram", "gateway.sock"));
101485
102013
  }
101486
102014
  function fail6(msg, code = 1) {
101487
102015
  console.error(msg);
@@ -101516,7 +102044,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
101516
102044
  fail6("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
101517
102045
  let draft;
101518
102046
  try {
101519
- draft = JSON.parse(readFileSync92(opts.draft, "utf-8"));
102047
+ draft = JSON.parse(readFileSync93(opts.draft, "utf-8"));
101520
102048
  } catch (e) {
101521
102049
  fail6(`failed to read/parse --draft: ${e.message}`);
101522
102050
  }
@@ -101553,28 +102081,28 @@ function registerSelfImproveProposeSkillCommand(program3) {
101553
102081
  init_esm();
101554
102082
  init_helpers();
101555
102083
  var import_yaml25 = __toESM(require_dist(), 1);
101556
- import { existsSync as existsSync104, readdirSync as readdirSync39, readFileSync as readFileSync93, statSync as statSync56 } from "node:fs";
101557
- import { homedir as homedir56 } from "node:os";
101558
- import { join as join106, resolve as resolve60 } from "node:path";
102084
+ import { existsSync as existsSync104, readdirSync as readdirSync39, readFileSync as readFileSync94, statSync as statSync56 } from "node:fs";
102085
+ import { homedir as homedir57 } from "node:os";
102086
+ import { join as join107, resolve as resolve60 } from "node:path";
101559
102087
  var PERSONAL_PREFIX2 = "personal-";
101560
102088
  var BUNDLED_SUBDIR = "_bundled";
101561
102089
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
101562
102090
  function defaultAgentsRoot() {
101563
- return resolve60(homedir56(), ".switchroom/agents");
102091
+ return resolve60(homedir57(), ".switchroom/agents");
101564
102092
  }
101565
102093
  function defaultSharedRoot2() {
101566
- return resolve60(homedir56(), ".switchroom/skills");
102094
+ return resolve60(homedir57(), ".switchroom/skills");
101567
102095
  }
101568
102096
  function defaultBundledRoot2() {
101569
- return resolve60(homedir56(), ".switchroom/skills/_bundled");
102097
+ return resolve60(homedir57(), ".switchroom/skills/_bundled");
101570
102098
  }
101571
102099
  function readSkillFrontmatter(skillDir) {
101572
- const mdPath = join106(skillDir, "SKILL.md");
102100
+ const mdPath = join107(skillDir, "SKILL.md");
101573
102101
  if (!existsSync104(mdPath))
101574
102102
  return null;
101575
102103
  let content;
101576
102104
  try {
101577
- content = readFileSync93(mdPath, "utf-8");
102105
+ content = readFileSync94(mdPath, "utf-8");
101578
102106
  } catch {
101579
102107
  return null;
101580
102108
  }
@@ -101602,7 +102130,7 @@ function readSkillFrontmatter(skillDir) {
101602
102130
  return { fm: parsed };
101603
102131
  }
101604
102132
  function statSkillMd(skillDir) {
101605
- const mdPath = join106(skillDir, "SKILL.md");
102133
+ const mdPath = join107(skillDir, "SKILL.md");
101606
102134
  try {
101607
102135
  const st = statSync56(mdPath);
101608
102136
  return { size: st.size, mtime: st.mtime.toISOString() };
@@ -101613,7 +102141,7 @@ function statSkillMd(skillDir) {
101613
102141
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
101614
102142
  if (!AGENT_NAME_RE3.test(agent))
101615
102143
  return [];
101616
- const skillsDir = join106(agentsRoot, agent, ".claude/skills");
102144
+ const skillsDir = join107(agentsRoot, agent, ".claude/skills");
101617
102145
  if (!existsSync104(skillsDir))
101618
102146
  return [];
101619
102147
  const out = [];
@@ -101626,7 +102154,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
101626
102154
  for (const ent of entries) {
101627
102155
  if (!ent.startsWith(PERSONAL_PREFIX2))
101628
102156
  continue;
101629
- const dirPath = join106(skillsDir, ent);
102157
+ const dirPath = join107(skillsDir, ent);
101630
102158
  try {
101631
102159
  if (!statSync56(dirPath).isDirectory())
101632
102160
  continue;
@@ -101666,7 +102194,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
101666
102194
  continue;
101667
102195
  if (ent.startsWith("."))
101668
102196
  continue;
101669
- const dirPath = join106(sharedRoot, ent);
102197
+ const dirPath = join107(sharedRoot, ent);
101670
102198
  try {
101671
102199
  if (!statSync56(dirPath).isDirectory())
101672
102200
  continue;
@@ -101702,7 +102230,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
101702
102230
  for (const ent of entries) {
101703
102231
  if (ent.startsWith("."))
101704
102232
  continue;
101705
- const dirPath = join106(bundledRoot, ent);
102233
+ const dirPath = join107(bundledRoot, ent);
101706
102234
  try {
101707
102235
  if (!statSync56(dirPath).isDirectory())
101708
102236
  continue;
@@ -101857,8 +102385,8 @@ import {
101857
102385
  realpathSync as realpathSync8,
101858
102386
  copyFileSync as copyFileSync14
101859
102387
  } from "node:fs";
101860
- import { homedir as homedir57 } from "node:os";
101861
- import { join as join107 } from "node:path";
102388
+ import { homedir as homedir58 } from "node:os";
102389
+ import { join as join108 } from "node:path";
101862
102390
  import { spawnSync as spawnSync24 } from "node:child_process";
101863
102391
 
101864
102392
  // src/cli/singleton-stale-cleanup.ts
@@ -102196,7 +102724,7 @@ networks:
102196
102724
  # operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
102197
102725
  `;
102198
102726
  }
102199
- function resolveHostdHostHome(env2 = process.env, home2 = homedir57()) {
102727
+ function resolveHostdHostHome(env2 = process.env, home2 = homedir58()) {
102200
102728
  const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
102201
102729
  const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
102202
102730
  if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
@@ -102207,7 +102735,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir57()) {
102207
102735
  return resolved;
102208
102736
  }
102209
102737
  function resolveHostdSkillsTarget(hostHome) {
102210
- const skillsPath = join107(hostHome, ".switchroom", "skills");
102738
+ const skillsPath = join108(hostHome, ".switchroom", "skills");
102211
102739
  let st;
102212
102740
  try {
102213
102741
  st = lstatSync14(skillsPath);
@@ -102231,10 +102759,10 @@ function resolveHostdSkillsTarget(hostHome) {
102231
102759
  return target;
102232
102760
  }
102233
102761
  function hostdDir() {
102234
- return join107(homedir57(), ".switchroom", "hostd");
102762
+ return join108(homedir58(), ".switchroom", "hostd");
102235
102763
  }
102236
102764
  function hostdComposePath() {
102237
- return join107(hostdDir(), "docker-compose.yml");
102765
+ return join108(hostdDir(), "docker-compose.yml");
102238
102766
  }
102239
102767
  function backupExistingCompose() {
102240
102768
  const p = hostdComposePath();
@@ -102363,7 +102891,7 @@ function doStatus() {
102363
102891
  for (const name of readdirSync40(dir)) {
102364
102892
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
102365
102893
  continue;
102366
- const sockPath = join107(dir, name, "sock");
102894
+ const sockPath = join108(dir, name, "sock");
102367
102895
  if (existsSync106(sockPath)) {
102368
102896
  const st = statSync57(sockPath);
102369
102897
  if ((st.mode & 61440) === 49152) {
@@ -102455,22 +102983,25 @@ The log is created when hostd handles its first privileged-verb request.`));
102455
102983
  init_source();
102456
102984
  init_helpers();
102457
102985
  init_operator_uid();
102986
+ init_compose();
102458
102987
  import { chownSync as chownSync10, existsSync as existsSync107, mkdirSync as mkdirSync62, writeFileSync as writeFileSync45, copyFileSync as copyFileSync15 } from "node:fs";
102459
- import { homedir as homedir58 } from "node:os";
102460
- import { join as join108 } from "node:path";
102988
+ import { homedir as homedir59 } from "node:os";
102989
+ import { join as join109 } from "node:path";
102461
102990
  import { spawnSync as spawnSync25 } from "node:child_process";
102462
102991
  function resolveWebImageTag(explicitTag, release) {
102463
102992
  if (explicitTag)
102464
102993
  return explicitTag;
102465
102994
  return resolveImageTag(resolveRelease({ root: release }));
102466
102995
  }
102467
- function resolveWebHostHome(env2 = process.env, home2 = homedir58()) {
102996
+ function resolveWebHostHome(env2 = process.env, home2 = homedir59()) {
102468
102997
  const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
102469
102998
  const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
102470
- if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
102471
- throw new Error(`switchroom webd install: refusing to generate \u2014 the host home resolved to ` + `"${resolved}", the in-container mount point of the operator home (never a ` + `valid host bind source). Emitting it would make Docker create empty ` + `/host-home dirs on the host and break every webhook forward + secret read.
102999
+ try {
103000
+ assertPlausibleHostHome(resolved);
103001
+ } catch {
103002
+ throw new Error(`switchroom webd install: refusing to generate \u2014 the host home resolved to ` + `"${resolved}", an in-container path (never a valid host bind source). ` + `Emitting it would make Docker create empty dirs on the host and break ` + `every webhook forward + secret read.
102472
103003
 
102473
- ` + `Recovery: run \`switchroom webd install\` from the HOST shell, or set ` + `SWITCHROOM_HOST_HOME to the real host home first.`);
103004
+ ` + `Recovery: set SWITCHROOM_HOST_HOME to the real host home, or drive the ` + `install from hostd / the host shell (both set it).`);
102474
103005
  }
102475
103006
  return resolved;
102476
103007
  }
@@ -102557,10 +103088,10 @@ services:
102557
103088
  `;
102558
103089
  }
102559
103090
  function webdDir() {
102560
- return join108(homedir58(), ".switchroom", "web");
103091
+ return join109(homedir59(), ".switchroom", "web");
102561
103092
  }
102562
103093
  function webdComposePath() {
102563
- return join108(webdDir(), "docker-compose.yml");
103094
+ return join109(webdDir(), "docker-compose.yml");
102564
103095
  }
102565
103096
  function backupExistingCompose2() {
102566
103097
  const p = webdComposePath();
@@ -102582,9 +103113,15 @@ function runDocker2(args) {
102582
103113
  async function doInstall2(opts, program3) {
102583
103114
  const operatorUid = resolveOperatorUid();
102584
103115
  if (operatorUid === undefined) {
102585
- console.error(source_default.red(`Could not resolve the operator uid (no SUDO_UID and getuid() is 0 or unavailable).
103116
+ console.error(source_default.red(`Could not resolve the operator uid.
102586
103117
  ` + `The web container must run as the operator uid so its webhook forwards pass
102587
- ` + "each agent gateway's peercred ACL. Run `switchroom webd install` as the\n" + "operator user (not root), or under `sudo` from the operator's shell."));
103118
+ ` + `each agent gateway's peercred ACL; running as root would be silently 503'd.
103119
+ ` + `
103120
+ ` + `Tried, in order: SUDO_UID, this process's own uid (non-root only),
103121
+ ` + `SWITCHROOM_HOSTD_OPERATOR_UID, and the owner of ~/.switchroom.
103122
+ ` + `
103123
+ ` + "Recovery (no shell required): re-run `switchroom hostd install` so the\n" + `hostd container carries SWITCHROOM_HOSTD_OPERATOR_UID, or make
103124
+ ` + "~/.switchroom owned by the operator account (`chown -R <operator>:<operator>\n" + "~/.switchroom`) so it can be derived. If neither is possible, set\n" + "SWITCHROOM_HOSTD_OPERATOR_UID to the operator's numeric uid."));
102588
103125
  process.exit(1);
102589
103126
  }
102590
103127
  const dir = webdDir();
@@ -102719,10 +103256,10 @@ function registerWebdCommand(program3) {
102719
103256
 
102720
103257
  // src/cli/host-repair.ts
102721
103258
  init_source();
102722
- import { homedir as homedir59 } from "node:os";
102723
- import { join as join109 } from "node:path";
103259
+ import { homedir as homedir60 } from "node:os";
103260
+ import { join as join110 } from "node:path";
102724
103261
  var ARTIFACT_ALLOWLIST = {
102725
- dockerComposePluginDir: (home2) => join109(home2, ".docker", "cli-plugins", "docker-compose"),
103262
+ dockerComposePluginDir: (home2) => join110(home2, ".docker", "cli-plugins", "docker-compose"),
102726
103263
  stateSentinel: "/state"
102727
103264
  };
102728
103265
  function isStateBogusAutoDir(probe2) {
@@ -102771,7 +103308,7 @@ function isStateBogusAutoDir(probe2) {
102771
103308
  return true;
102772
103309
  }
102773
103310
  function planMountRepairs(probe2, opts = {}) {
102774
- const home2 = opts.hostHome ?? homedir59();
103311
+ const home2 = opts.hostHome ?? homedir60();
102775
103312
  const items = [];
102776
103313
  const dockerComposePath = ARTIFACT_ALLOWLIST.dockerComposePluginDir(home2);
102777
103314
  const dockerComposeSt = probe2.lstat(dockerComposePath);
@@ -103177,8 +103714,8 @@ function printDeepDiveBrief(targets) {
103177
103714
  // src/cli/hindsight-watch.ts
103178
103715
  init_source();
103179
103716
  import { existsSync as existsSync112, readdirSync as readdirSync45 } from "node:fs";
103180
- import { homedir as homedir62 } from "node:os";
103181
- import { join as join111, resolve as resolve63 } from "node:path";
103717
+ import { homedir as homedir63 } from "node:os";
103718
+ import { join as join112, resolve as resolve63 } from "node:path";
103182
103719
 
103183
103720
  // src/host-control/config-degraded.ts
103184
103721
  import { connect as connect3 } from "node:net";
@@ -103247,8 +103784,8 @@ init_install_cron();
103247
103784
 
103248
103785
  // src/hindsight-watch/probe.ts
103249
103786
  import { spawnSync as spawnSync27 } from "node:child_process";
103250
- import { readFileSync as readFileSync98, readdirSync as readdirSync44, statSync as statSync59 } from "node:fs";
103251
- import { homedir as homedir61 } from "node:os";
103787
+ import { readFileSync as readFileSync99, readdirSync as readdirSync44, statSync as statSync59 } from "node:fs";
103788
+ import { homedir as homedir62 } from "node:os";
103252
103789
  import { resolve as resolve62 } from "node:path";
103253
103790
 
103254
103791
  // src/hindsight-watch/metrics.ts
@@ -103376,12 +103913,12 @@ function readLlmSignals(series) {
103376
103913
 
103377
103914
  // src/hindsight-watch/recall-log.ts
103378
103915
  import { closeSync as closeSync22, existsSync as existsSync111, openSync as openSync22, readdirSync as readdirSync43, readSync as readSync6, statSync as statSync58 } from "node:fs";
103379
- import { join as join110 } from "node:path";
103916
+ import { join as join111 } from "node:path";
103380
103917
  var RECALL_WINDOW_ROWS = 200;
103381
103918
  var RECALL_MAX_TAIL_BYTES = 1024 * 1024;
103382
103919
  var RECALL_MAX_ROW_AGE_MS = 24 * 60 * 60 * 1000;
103383
103920
  function recallLogPath2(agentDir) {
103384
- return join110(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
103921
+ return join111(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
103385
103922
  }
103386
103923
  function readRecallLogTail2(path10, windowRows = RECALL_WINDOW_ROWS) {
103387
103924
  if (!existsSync111(path10))
@@ -103529,7 +104066,7 @@ function probeRecallLogs(agentsDir, now, windowRows = RECALL_WINDOW_ROWS, maxAge
103529
104066
  const all = [];
103530
104067
  let agents = 0;
103531
104068
  for (const name of names) {
103532
- const rows = withinWindow(readRecallLogTail2(recallLogPath2(join110(agentsDir, name)), windowRows), now, maxAgeMs);
104069
+ const rows = withinWindow(readRecallLogTail2(recallLogPath2(join111(agentsDir, name)), windowRows), now, maxAgeMs);
103533
104070
  if (rows.length > 0)
103534
104071
  agents++;
103535
104072
  all.push(...rows);
@@ -103624,7 +104161,7 @@ function readDropCount(hindsightDir) {
103624
104161
  try {
103625
104162
  if (statSync59(path10).size > DROPS_LEDGER_MAX_BYTES)
103626
104163
  return 0;
103627
- parsed = JSON.parse(readFileSync98(path10, "utf8"));
104164
+ parsed = JSON.parse(readFileSync99(path10, "utf8"));
103628
104165
  } catch {
103629
104166
  return 0;
103630
104167
  }
@@ -103634,7 +104171,7 @@ function readDropCount(hindsightDir) {
103634
104171
  return Number.isFinite(n) && n > 0 ? n : 0;
103635
104172
  }
103636
104173
  function resolveAgentsDir2(explicit) {
103637
- return explicit ?? resolve62(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir61(), ".switchroom", "agents");
104174
+ return explicit ?? resolve62(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir62(), ".switchroom", "agents");
103638
104175
  }
103639
104176
  function probeSpool(agentsDir = resolveAgentsDir2()) {
103640
104177
  let names;
@@ -104324,7 +104861,7 @@ function registerHindsightWatchCommand(program3) {
104324
104861
  process.exitCode = runInstallCron(opts.cronUser);
104325
104862
  return;
104326
104863
  }
104327
- const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join111(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir62(), ".switchroom", "agents");
104864
+ const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join112(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir63(), ".switchroom", "agents");
104328
104865
  const statePath = opts.state ?? defaultStatePath2();
104329
104866
  const log = (m) => {
104330
104867
  process.stderr.write(`${m}
@@ -104433,11 +104970,11 @@ async function notifyOperator(agentsDir, text, log) {
104433
104970
  // src/cli/openrouter-watch.ts
104434
104971
  init_source();
104435
104972
  import { existsSync as existsSync114, readdirSync as readdirSync46 } from "node:fs";
104436
- import { homedir as homedir64 } from "node:os";
104437
- import { join as join112, resolve as resolve65 } from "node:path";
104973
+ import { homedir as homedir65 } from "node:os";
104974
+ import { join as join113, resolve as resolve65 } from "node:path";
104438
104975
 
104439
104976
  // src/openrouter/install-cron.ts
104440
- import { existsSync as existsSync113, mkdirSync as mkdirSync64, readFileSync as readFileSync99, renameSync as renameSync29, writeFileSync as writeFileSync47 } from "node:fs";
104977
+ import { existsSync as existsSync113, mkdirSync as mkdirSync64, readFileSync as readFileSync100, renameSync as renameSync29, writeFileSync as writeFileSync47 } from "node:fs";
104441
104978
  import { dirname as dirname43 } from "node:path";
104442
104979
  var CRON_PATH2 = "/etc/cron.d/openrouter-watch";
104443
104980
  var CRON_SCHEDULE2 = "17 * * * *";
@@ -104457,7 +104994,7 @@ function installCron2(opts) {
104457
104994
  const content = renderCron2(opts);
104458
104995
  if (existsSync113(path10)) {
104459
104996
  try {
104460
- if (readFileSync99(path10, "utf8") === content) {
104997
+ if (readFileSync100(path10, "utf8") === content) {
104461
104998
  return { status: "unchanged", path: path10, content };
104462
104999
  }
104463
105000
  } catch {}
@@ -104469,16 +105006,16 @@ function installCron2(opts) {
104469
105006
  return { status: "installed", path: path10, content };
104470
105007
  }
104471
105008
  // src/openrouter/state.ts
104472
- import { mkdirSync as mkdirSync65, readFileSync as readFileSync100, renameSync as renameSync30, writeFileSync as writeFileSync48 } from "node:fs";
104473
- import { homedir as homedir63 } from "node:os";
105009
+ import { mkdirSync as mkdirSync65, readFileSync as readFileSync101, renameSync as renameSync30, writeFileSync as writeFileSync48 } from "node:fs";
105010
+ import { homedir as homedir64 } from "node:os";
104474
105011
  import { dirname as dirname44, resolve as resolve64 } from "node:path";
104475
- function defaultStatePath3(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir63()) {
105012
+ function defaultStatePath3(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir64()) {
104476
105013
  return resolve64(home2, ".switchroom", "openrouter-watch", "state.json");
104477
105014
  }
104478
105015
  function loadState2(path10) {
104479
105016
  let parsed;
104480
105017
  try {
104481
- parsed = JSON.parse(readFileSync100(path10, "utf8"));
105018
+ parsed = JSON.parse(readFileSync101(path10, "utf8"));
104482
105019
  } catch {
104483
105020
  return {};
104484
105021
  }
@@ -104589,7 +105126,7 @@ function registerOpenRouterWatchCommand(program3) {
104589
105126
  process.exitCode = runInstallCron2(opts.cronUser);
104590
105127
  return;
104591
105128
  }
104592
- const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join112(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir64(), ".switchroom", "agents");
105129
+ const agentsDir = opts.agentsDir ?? process.env.SWITCHROOM_AGENTS_DIR ?? join113(process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir65(), ".switchroom", "agents");
104593
105130
  const statePath = opts.state ?? defaultStatePath3();
104594
105131
  const log = (m) => {
104595
105132
  process.stderr.write(`${m}