switchroom 0.18.6 → 0.18.8

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 (116) hide show
  1. package/dist/agent-scheduler/index.js +1 -0
  2. package/dist/auth-broker/index.js +1 -0
  3. package/dist/cli/autoaccept-poll.js +140 -33
  4. package/dist/cli/notion-write-pretool.mjs +1 -0
  5. package/dist/cli/switchroom.js +1172 -812
  6. package/dist/host-control/main.js +2 -1
  7. package/dist/vault/approvals/kernel-server.js +1 -0
  8. package/dist/vault/broker/server.js +1 -0
  9. package/package.json +3 -3
  10. package/profiles/_base/cron-session.sh.hbs +55 -16
  11. package/profiles/_base/start.sh.hbs +146 -50
  12. package/profiles/default/CLAUDE.md.hbs +1 -1
  13. package/skills/switchroom-runtime/SKILL.md +2 -0
  14. package/telegram-plugin/dist/bridge/bridge.js +22 -0
  15. package/telegram-plugin/dist/gateway/gateway.js +2965 -862
  16. package/telegram-plugin/dist/server.js +24 -0
  17. package/telegram-plugin/flood-circuit-breaker.ts +123 -0
  18. package/telegram-plugin/gateway/activity-card-store.ts +63 -18
  19. package/telegram-plugin/gateway/always-allow-persist-queue.ts +438 -0
  20. package/telegram-plugin/gateway/approval-timeout-inbound-builders.ts +150 -0
  21. package/telegram-plugin/gateway/boot-card.ts +27 -0
  22. package/telegram-plugin/gateway/busy-ack.ts +106 -0
  23. package/telegram-plugin/gateway/clean-shutdown-marker.ts +68 -20
  24. package/telegram-plugin/gateway/gateway.ts +1618 -198
  25. package/telegram-plugin/gateway/inbound-spool.ts +2 -1
  26. package/telegram-plugin/gateway/inject-handler.test.ts +19 -0
  27. package/telegram-plugin/gateway/inject-handler.ts +17 -0
  28. package/telegram-plugin/gateway/ipc-protocol.ts +44 -2
  29. package/telegram-plugin/gateway/ipc-server.ts +40 -0
  30. package/telegram-plugin/gateway/mental-model-propose-diff.ts +61 -5
  31. package/telegram-plugin/gateway/model-command.ts +227 -54
  32. package/telegram-plugin/gateway/pending-card-expiry.ts +98 -0
  33. package/telegram-plugin/gateway/pending-card-store.ts +173 -0
  34. package/telegram-plugin/gateway/pending-inbound-buffer.ts +12 -2
  35. package/telegram-plugin/gateway/resume-inbound-builder.ts +240 -2
  36. package/telegram-plugin/gateway/session-model-file.ts +198 -0
  37. package/telegram-plugin/gateway/session-model-source.ts +73 -0
  38. package/telegram-plugin/gateway/status-pin-store.ts +82 -22
  39. package/telegram-plugin/gateway/worker-feed-dispatch.ts +24 -1
  40. package/telegram-plugin/gateway/worker-pin-reaper.ts +114 -0
  41. package/telegram-plugin/hooks/hooks.json +10 -10
  42. package/telegram-plugin/hooks/run-hook.sh +84 -0
  43. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +30 -7
  44. package/telegram-plugin/model-label.ts +69 -0
  45. package/telegram-plugin/model-unavailable.ts +26 -0
  46. package/telegram-plugin/operator-events.ts +24 -0
  47. package/telegram-plugin/permission-diff.ts +128 -0
  48. package/telegram-plugin/pty-partial-handler.ts +39 -0
  49. package/telegram-plugin/registry/subagents-schema.ts +80 -1
  50. package/telegram-plugin/registry/subagents.test.ts +90 -0
  51. package/telegram-plugin/render/rich-render.ts +79 -1
  52. package/telegram-plugin/retry-api-call.ts +62 -0
  53. package/telegram-plugin/session-tail.ts +28 -0
  54. package/telegram-plugin/shared/bot-runtime.ts +8 -1
  55. package/telegram-plugin/silence-poke.ts +14 -0
  56. package/telegram-plugin/silent-end.ts +49 -4
  57. package/telegram-plugin/stream-controller.ts +156 -38
  58. package/telegram-plugin/subagent-watcher.ts +222 -37
  59. package/telegram-plugin/tests/activity-card-store.test.ts +47 -2
  60. package/telegram-plugin/tests/always-allow-persist-queue.test.ts +529 -0
  61. package/telegram-plugin/tests/approval-card-restart-outcome.test.ts +218 -0
  62. package/telegram-plugin/tests/approval-timeout-inbound-builders.test.ts +94 -0
  63. package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +111 -0
  64. package/telegram-plugin/tests/busy-ack-wiring.test.ts +118 -0
  65. package/telegram-plugin/tests/busy-ack.test.ts +121 -0
  66. package/telegram-plugin/tests/button-tap-turn-gated.test.ts +263 -0
  67. package/telegram-plugin/tests/flood-circuit-breaker.test.ts +74 -0
  68. package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +85 -27
  69. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +179 -25
  70. package/telegram-plugin/tests/ipc-server-query-pending-permission.test.ts +157 -0
  71. package/telegram-plugin/tests/mental-model-name-entity-corruption.test.ts +119 -0
  72. package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -5
  73. package/telegram-plugin/tests/model-command.test.ts +203 -43
  74. package/telegram-plugin/tests/model-label.test.ts +64 -0
  75. package/telegram-plugin/tests/model-unavailable.test.ts +41 -0
  76. package/telegram-plugin/tests/operator-events.test.ts +1 -0
  77. package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +202 -0
  78. package/telegram-plugin/tests/pending-card-expiry.test.ts +190 -0
  79. package/telegram-plugin/tests/pending-card-store.test.ts +173 -0
  80. package/telegram-plugin/tests/permission-diff.test.ts +111 -0
  81. package/telegram-plugin/tests/pty-partial-handler.test.ts +56 -0
  82. package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +98 -0
  83. package/telegram-plugin/tests/resume-inbound-builder.test.ts +286 -0
  84. package/telegram-plugin/tests/retry-api-call.test.ts +59 -0
  85. package/telegram-plugin/tests/run-hook-wrapper.test.ts +132 -0
  86. package/telegram-plugin/tests/session-model-file.test.ts +132 -0
  87. package/telegram-plugin/tests/session-model-source.test.ts +67 -0
  88. package/telegram-plugin/tests/session-tail.test.ts +64 -0
  89. package/telegram-plugin/tests/silent-end.test.ts +46 -1
  90. package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +3 -3
  91. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
  92. package/telegram-plugin/tests/status-pin-store.test.ts +62 -6
  93. package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +122 -0
  94. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +39 -0
  95. package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +107 -4
  96. package/telegram-plugin/tests/subagent-watcher-handback-gaps.test.ts +42 -4
  97. package/telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts +47 -0
  98. package/telegram-plugin/tests/subagent-watcher-terminated-ids-cap.test.ts +150 -0
  99. package/telegram-plugin/tests/subagent-watcher.test.ts +54 -0
  100. package/telegram-plugin/tests/tool-activity-summary.test.ts +37 -0
  101. package/telegram-plugin/tests/typing-wrap.test.ts +23 -0
  102. package/telegram-plugin/tests/voice-send.test.ts +308 -0
  103. package/telegram-plugin/tests/worker-activity-feed.test.ts +11 -0
  104. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +126 -0
  105. package/telegram-plugin/tests/worker-pin-reaper.test.ts +132 -0
  106. package/telegram-plugin/tool-activity-summary.ts +22 -2
  107. package/telegram-plugin/typing-wrap.ts +72 -25
  108. package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +118 -0
  109. package/telegram-plugin/uat/scenarios/jtbd-midflight-busy-ack-dm.test.ts +201 -0
  110. package/telegram-plugin/uat/scenarios/jtbd-worker-pin-lifecycle-dm.test.ts +208 -0
  111. package/telegram-plugin/uat/scenarios/vault-card-survives-gateway-restart-dm.test.ts +140 -0
  112. package/telegram-plugin/uat/scenarios/vault-deny-resumes-turn-dm.test.ts +84 -0
  113. package/telegram-plugin/uat/scenarios/vault-timeout-wakes-agent-dm.test.ts +91 -0
  114. package/telegram-plugin/voice-ondemand.ts +25 -1
  115. package/telegram-plugin/voice-send.ts +154 -0
  116. package/telegram-plugin/worker-activity-feed.ts +9 -0
@@ -13741,6 +13741,7 @@ var init_schema = __esm(() => {
13741
13741
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
13742
13742
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
13743
13743
  resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
13744
+ boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart \u2014 a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart \u2014 but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
13744
13745
  session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
13745
13746
  session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
13746
13747
  }).optional();
@@ -26944,6 +26945,7 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
26944
26945
  handoffEnabled: agentConfig.session_continuity?.enabled !== false,
26945
26946
  resumeMode: agentConfig.session_continuity?.resume_mode ?? "handoff",
26946
26947
  resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000,
26948
+ bootResumeMode: agentConfig.session_continuity?.boot_resume ?? "in-flight",
26947
26949
  resumeModeHasContinuePath: (() => {
26948
26950
  const mode = agentConfig.session_continuity?.resume_mode ?? "handoff";
26949
26951
  return mode === "auto" || mode === "continue";
@@ -28046,7 +28048,8 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
28046
28048
  sessionMaxTurns: agentConfig.session?.max_turns,
28047
28049
  handoffEnabled: agentConfig.session_continuity?.enabled !== false,
28048
28050
  resumeMode: agentConfig.session_continuity?.resume_mode ?? "handoff",
28049
- resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000
28051
+ resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000,
28052
+ bootResumeMode: agentConfig.session_continuity?.boot_resume ?? "in-flight"
28050
28053
  };
28051
28054
  const beforeStartSh = existsSync15(startShPath) ? readFileSync13(startShPath, "utf-8") : "";
28052
28055
  const afterStartSh = renderTemplate(join10(basePath, "start.sh.hbs"), startShContext);
@@ -30047,7 +30050,25 @@ import { chownSync as chownSync2 } from "node:fs";
30047
30050
  import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
30048
30051
  import { homedir as homedir6 } from "node:os";
30049
30052
  import { basename as basename4, dirname as dirname6, join as join13 } from "node:path";
30050
- async function resolveLiteLLMConfirmedAgents(config) {
30053
+ function agentHadLiteLLMRouting(composeContent, agentName) {
30054
+ const lines = composeContent.split(`
30055
+ `);
30056
+ const header = ` agent-${agentName}:`;
30057
+ let i = lines.indexOf(header);
30058
+ if (i < 0)
30059
+ return false;
30060
+ for (i = i + 1;i < lines.length; i++) {
30061
+ const line = lines[i];
30062
+ if (/^ {2}\S/.test(line))
30063
+ break;
30064
+ if (/^\S/.test(line))
30065
+ break;
30066
+ if (/^\s+SWITCHROOM_LITELLM:\s*"1"\s*$/.test(line))
30067
+ return true;
30068
+ }
30069
+ return false;
30070
+ }
30071
+ async function resolveLiteLLMConfirmedAgents(config, previousCompose, deps) {
30051
30072
  const confirmed = new Set;
30052
30073
  const topEnabled = config.litellm?.enabled ?? false;
30053
30074
  const optedIn = [];
@@ -30062,16 +30083,52 @@ async function resolveLiteLLMConfirmedAgents(config) {
30062
30083
  }
30063
30084
  if (optedIn.length === 0)
30064
30085
  return confirmed;
30065
- try {
30066
- const { getViaBrokerStructured: getViaBrokerStructured2 } = await Promise.resolve().then(() => (init_client(), exports_client));
30067
- for (const name of optedIn) {
30068
- try {
30069
- const r = await getViaBrokerStructured2(`litellm/${name}/api-key`);
30070
- if (r.kind === "ok")
30071
- confirmed.add(name);
30072
- } catch {}
30086
+ let getKey = deps?.getKey ?? null;
30087
+ if (!getKey) {
30088
+ try {
30089
+ ({ getViaBrokerStructured: getKey } = await Promise.resolve().then(() => (init_client(), exports_client)));
30090
+ } catch {
30091
+ getKey = null;
30073
30092
  }
30074
- } catch {}
30093
+ }
30094
+ const unreachableAgents = [];
30095
+ for (const name of optedIn) {
30096
+ if (!getKey) {
30097
+ unreachableAgents.push(name);
30098
+ continue;
30099
+ }
30100
+ try {
30101
+ const r = await getKey(`litellm/${name}/api-key`);
30102
+ if (r.kind === "ok") {
30103
+ confirmed.add(name);
30104
+ } else if (r.kind === "unreachable") {
30105
+ unreachableAgents.push(name);
30106
+ } else if (r.kind === "denied" && r.code !== "DENIED") {
30107
+ unreachableAgents.push(name);
30108
+ }
30109
+ } catch {
30110
+ unreachableAgents.push(name);
30111
+ }
30112
+ }
30113
+ if (unreachableAgents.length > 0) {
30114
+ const preserved = [];
30115
+ const couldNotPreserve = [];
30116
+ for (const name of unreachableAgents) {
30117
+ if (previousCompose && agentHadLiteLLMRouting(previousCompose, name)) {
30118
+ confirmed.add(name);
30119
+ preserved.push(name);
30120
+ } else {
30121
+ couldNotPreserve.push(name);
30122
+ }
30123
+ }
30124
+ console.warn(`switchroom: vault-broker unreachable or unable to answer (locked/internal) while resolving LiteLLM key status for ${unreachableAgents.length} opted-in agent(s): ${unreachableAgents.join(", ")}. Refusing to silently strip proxy routing (that would boot them on untracked direct OAuth). Re-run \`switchroom apply\` once the broker is back to reconcile.`);
30125
+ if (preserved.length > 0) {
30126
+ console.warn(`switchroom: PRESERVED existing proxy routing from the on-disk compose for: ${preserved.join(", ")}.`);
30127
+ }
30128
+ if (couldNotPreserve.length > 0) {
30129
+ console.warn(`switchroom: no prior routing verdict on disk for: ${couldNotPreserve.join(", ")} ` + `\u2014 leaving them unrouted (they were not previously routed).`);
30130
+ }
30131
+ }
30075
30132
  return confirmed;
30076
30133
  }
30077
30134
  function resolveHostSwitchroomConfigPath(rawPath) {
@@ -30109,7 +30166,13 @@ async function computeComposeContent(opts) {
30109
30166
  const imageTag = resolveImageTag(release);
30110
30167
  const operatorUid = resolveOperatorUid();
30111
30168
  const resolvedConfigPath = opts.switchroomConfigPath !== undefined ? resolveHostSwitchroomConfigPath(opts.switchroomConfigPath) : undefined;
30112
- const litellmConfirmedAgents = await resolveLiteLLMConfirmedAgents(opts.config);
30169
+ let previous = null;
30170
+ try {
30171
+ previous = await readFile(opts.composePath, "utf8");
30172
+ } catch {
30173
+ previous = null;
30174
+ }
30175
+ const litellmConfirmedAgents = await resolveLiteLLMConfirmedAgents(opts.config, previous);
30113
30176
  const content = generateCompose({
30114
30177
  config: opts.config,
30115
30178
  imageTag,
@@ -30121,12 +30184,6 @@ async function computeComposeContent(opts) {
30121
30184
  switchroomConfigPath: resolvedConfigPath,
30122
30185
  operatorUid
30123
30186
  });
30124
- let previous = null;
30125
- try {
30126
- previous = await readFile(opts.composePath, "utf8");
30127
- } catch {
30128
- previous = null;
30129
- }
30130
30187
  const previousImageTag = previous ? AGENT_IMAGE_TAG_RE.exec(previous)?.[1] ?? null : null;
30131
30188
  return { content, imageTag, previous, previousImageTag };
30132
30189
  }
@@ -40412,19 +40469,19 @@ var init_thinking_effort_risk = __esm(() => {
40412
40469
 
40413
40470
  // src/manifest.ts
40414
40471
  import {
40415
- existsSync as existsSync57,
40416
- readFileSync as readFileSync52,
40472
+ existsSync as existsSync58,
40473
+ readFileSync as readFileSync53,
40417
40474
  readdirSync as readdirSync19
40418
40475
  } from "node:fs";
40419
- import { dirname as dirname17, join as join50 } from "node:path";
40476
+ import { dirname as dirname18, join as join51 } from "node:path";
40420
40477
  import { execSync as execSync2 } from "node:child_process";
40421
40478
  function locateManifestPath() {
40422
40479
  let dir = import.meta.dirname;
40423
40480
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
40424
- const candidate = join50(dir, "dependencies.json");
40425
- if (existsSync57(candidate))
40481
+ const candidate = join51(dir, "dependencies.json");
40482
+ if (existsSync58(candidate))
40426
40483
  return candidate;
40427
- dir = dirname17(dir);
40484
+ dir = dirname18(dir);
40428
40485
  }
40429
40486
  return null;
40430
40487
  }
@@ -40435,7 +40492,7 @@ function loadManifest(manifestPath) {
40435
40492
  }
40436
40493
  let raw;
40437
40494
  try {
40438
- raw = readFileSync52(path4, "utf-8");
40495
+ raw = readFileSync53(path4, "utf-8");
40439
40496
  } catch (err) {
40440
40497
  throw new Error(`Failed to read manifest at ${path4}: ${err.message}`);
40441
40498
  }
@@ -40491,16 +40548,16 @@ function probeClaudeVersion() {
40491
40548
  }
40492
40549
  function probePlaywrightMcpVersion() {
40493
40550
  const home2 = process.env.HOME ?? "";
40494
- const npxCache = join50(home2, ".npm/_npx");
40495
- if (!existsSync57(npxCache))
40551
+ const npxCache = join51(home2, ".npm/_npx");
40552
+ if (!existsSync58(npxCache))
40496
40553
  return null;
40497
40554
  try {
40498
40555
  const entries = readdirSync19(npxCache);
40499
40556
  for (const entry of entries) {
40500
- const pkgPath = join50(npxCache, entry, "node_modules/@playwright/mcp/package.json");
40501
- if (existsSync57(pkgPath)) {
40557
+ const pkgPath = join51(npxCache, entry, "node_modules/@playwright/mcp/package.json");
40558
+ if (existsSync58(pkgPath)) {
40502
40559
  try {
40503
- const pkg = JSON.parse(readFileSync52(pkgPath, "utf-8"));
40560
+ const pkg = JSON.parse(readFileSync53(pkgPath, "utf-8"));
40504
40561
  if (pkg.version)
40505
40562
  return pkg.version;
40506
40563
  } catch {}
@@ -40802,7 +40859,7 @@ var init_doctor_memory = __esm(() => {
40802
40859
  });
40803
40860
 
40804
40861
  // src/cli/doctor-docker.ts
40805
- import { readFileSync as readFileSync53 } from "node:fs";
40862
+ import { readFileSync as readFileSync54 } from "node:fs";
40806
40863
  import { spawnSync as spawnSync8 } from "node:child_process";
40807
40864
  function imageTagOf(ref) {
40808
40865
  if (!ref)
@@ -40818,7 +40875,7 @@ function isDockerMode(opts) {
40818
40875
  return true;
40819
40876
  if (opts?.composePath) {
40820
40877
  try {
40821
- readFileSync53(opts.composePath, "utf8");
40878
+ readFileSync54(opts.composePath, "utf8");
40822
40879
  return true;
40823
40880
  } catch {}
40824
40881
  }
@@ -41100,11 +41157,11 @@ var init_doctor_docker = __esm(() => {
41100
41157
  });
41101
41158
 
41102
41159
  // src/cli/doctor-auth-broker.ts
41103
- import { existsSync as existsSync58, readFileSync as readFileSync54 } from "node:fs";
41160
+ import { existsSync as existsSync59, readFileSync as readFileSync55 } from "node:fs";
41104
41161
  import { createHash as createHash11 } from "node:crypto";
41105
41162
  import { spawnSync as spawnSync9 } from "node:child_process";
41106
- import { homedir as homedir28 } from "node:os";
41107
- import { join as join51 } from "node:path";
41163
+ import { homedir as homedir29 } from "node:os";
41164
+ import { join as join52 } from "node:path";
41108
41165
  function defaultDockerInspect(container, format) {
41109
41166
  try {
41110
41167
  const r = spawnSync9("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
@@ -41204,8 +41261,8 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
41204
41261
  }
41205
41262
  function checkAuthBrokerDrift(deps = {}) {
41206
41263
  const stateDir = resolveStateDir(deps);
41207
- const indexPath = join51(stateDir, "sha-index.json");
41208
- if (!existsSync58(indexPath)) {
41264
+ const indexPath = join52(stateDir, "sha-index.json");
41265
+ if (!existsSync59(indexPath)) {
41209
41266
  return {
41210
41267
  name: "auth-broker: drift",
41211
41268
  status: "ok",
@@ -41214,7 +41271,7 @@ function checkAuthBrokerDrift(deps = {}) {
41214
41271
  }
41215
41272
  let index;
41216
41273
  try {
41217
- index = JSON.parse(readFileSync54(indexPath, "utf-8"));
41274
+ index = JSON.parse(readFileSync55(indexPath, "utf-8"));
41218
41275
  } catch (err) {
41219
41276
  return {
41220
41277
  name: "auth-broker: drift",
@@ -41223,18 +41280,18 @@ function checkAuthBrokerDrift(deps = {}) {
41223
41280
  fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
41224
41281
  };
41225
41282
  }
41226
- const home2 = deps.home ?? homedir28();
41283
+ const home2 = deps.home ?? homedir29();
41227
41284
  const divergent = [];
41228
41285
  const missingOnDisk = [];
41229
41286
  for (const [label, expected] of Object.entries(index)) {
41230
41287
  const credsPath = accountCredentialsPath(label, home2);
41231
- if (!existsSync58(credsPath)) {
41288
+ if (!existsSync59(credsPath)) {
41232
41289
  missingOnDisk.push(label);
41233
41290
  continue;
41234
41291
  }
41235
41292
  let got;
41236
41293
  try {
41237
- got = sha256Hex(readFileSync54(credsPath, "utf-8"));
41294
+ got = sha256Hex(readFileSync55(credsPath, "utf-8"));
41238
41295
  } catch (err) {
41239
41296
  divergent.push(`${label} (read failed: ${err.message})`);
41240
41297
  continue;
@@ -41265,8 +41322,8 @@ function checkAuthBrokerDrift(deps = {}) {
41265
41322
  }
41266
41323
  function checkAuthBrokerThresholdViolations(deps = {}) {
41267
41324
  const stateDir = resolveStateDir(deps);
41268
- const path4 = join51(stateDir, "threshold-violations.json");
41269
- if (!existsSync58(path4)) {
41325
+ const path4 = join52(stateDir, "threshold-violations.json");
41326
+ if (!existsSync59(path4)) {
41270
41327
  return {
41271
41328
  name: "auth-broker: threshold violations",
41272
41329
  status: "ok",
@@ -41275,7 +41332,7 @@ function checkAuthBrokerThresholdViolations(deps = {}) {
41275
41332
  }
41276
41333
  let violations;
41277
41334
  try {
41278
- violations = JSON.parse(readFileSync54(path4, "utf-8"));
41335
+ violations = JSON.parse(readFileSync55(path4, "utf-8"));
41279
41336
  } catch (err) {
41280
41337
  return {
41281
41338
  name: "auth-broker: threshold violations",
@@ -41309,9 +41366,9 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
41309
41366
  fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
41310
41367
  };
41311
41368
  }
41312
- const home2 = deps.home ?? homedir28();
41369
+ const home2 = deps.home ?? homedir29();
41313
41370
  const dir = accountDir(active, home2);
41314
- if (!existsSync58(dir)) {
41371
+ if (!existsSync59(dir)) {
41315
41372
  return {
41316
41373
  name: "auth-broker: fleet active account",
41317
41374
  status: "fail",
@@ -41320,7 +41377,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
41320
41377
  };
41321
41378
  }
41322
41379
  const creds = accountCredentialsPath(active, home2);
41323
- if (!existsSync58(creds)) {
41380
+ if (!existsSync59(creds)) {
41324
41381
  return {
41325
41382
  name: "auth-broker: fleet active account",
41326
41383
  status: "fail",
@@ -41457,17 +41514,17 @@ var init_doctor_hostd = () => {};
41457
41514
  import {
41458
41515
  accessSync,
41459
41516
  constants as fsConstants4,
41460
- existsSync as existsSync59,
41517
+ existsSync as existsSync60,
41461
41518
  realpathSync as realpathSync5,
41462
- statSync as statSync24
41519
+ statSync as statSync25
41463
41520
  } from "node:fs";
41464
- import { userInfo, homedir as homedir29 } from "node:os";
41465
- import { join as join52 } from "node:path";
41521
+ import { userInfo, homedir as homedir30 } from "node:os";
41522
+ import { join as join53 } from "node:path";
41466
41523
  function resolveVaultPath2(config) {
41467
41524
  return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
41468
41525
  }
41469
41526
  function defaultStatVault(path4) {
41470
- if (!existsSync59(path4)) {
41527
+ if (!existsSync60(path4)) {
41471
41528
  return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
41472
41529
  }
41473
41530
  let real = path4;
@@ -41477,7 +41534,7 @@ function defaultStatVault(path4) {
41477
41534
  let uid = -1;
41478
41535
  let mode = 0;
41479
41536
  try {
41480
- const s = statSync24(real);
41537
+ const s = statSync25(real);
41481
41538
  uid = s.uid;
41482
41539
  mode = s.mode & 511;
41483
41540
  } catch {
@@ -41600,7 +41657,7 @@ async function runSecretAccessChecks(config, deps = {}) {
41600
41657
  };
41601
41658
  const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
41602
41659
  if (!passphrase) {
41603
- const sock = deps.brokerOperatorSocket ?? join52(homedir29(), ".switchroom", "broker-operator", "sock");
41660
+ const sock = deps.brokerOperatorSocket ?? join53(homedir30(), ".switchroom", "broker-operator", "sock");
41604
41661
  const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
41605
41662
  for (const name of Object.keys(config.agents ?? {})) {
41606
41663
  const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
@@ -41685,8 +41742,8 @@ import {
41685
41742
  existsSync as realExistsSync,
41686
41743
  readFileSync as realReadFileSync
41687
41744
  } from "node:fs";
41688
- import { join as join53, resolve as resolve34 } from "node:path";
41689
- import { homedir as homedir30 } from "node:os";
41745
+ import { join as join54, resolve as resolve34 } from "node:path";
41746
+ import { homedir as homedir31 } from "node:os";
41690
41747
  function resolveDeps(config, deps) {
41691
41748
  let agentsDir = deps.agentsDir;
41692
41749
  if (agentsDir === undefined) {
@@ -41809,8 +41866,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
41809
41866
  });
41810
41867
  continue;
41811
41868
  }
41812
- const mcpPath = join53(agentDir, ".mcp.json");
41813
- const claudeJsonPath = join53(agentDir, ".claude", ".claude.json");
41869
+ const mcpPath = join54(agentDir, ".mcp.json");
41870
+ const claudeJsonPath = join54(agentDir, ".claude", ".claude.json");
41814
41871
  const mcpRead = readJson(d, mcpPath);
41815
41872
  const trustRead = readJson(d, claudeJsonPath);
41816
41873
  if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
@@ -41923,7 +41980,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
41923
41980
  }
41924
41981
  ];
41925
41982
  }
41926
- const sock = deps.brokerOperatorSocket ?? join53(homedir30(), ".switchroom", "broker-operator", "sock");
41983
+ const sock = deps.brokerOperatorSocket ?? join54(homedir31(), ".switchroom", "broker-operator", "sock");
41927
41984
  const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
41928
41985
  const results = [];
41929
41986
  for (const agent of driveAgents) {
@@ -41977,8 +42034,8 @@ import {
41977
42034
  readSync as realReadSync,
41978
42035
  closeSync as realCloseSync
41979
42036
  } from "node:fs";
41980
- import { join as join54 } from "node:path";
41981
- import { homedir as homedir31 } from "node:os";
42037
+ import { join as join55 } from "node:path";
42038
+ import { homedir as homedir32 } from "node:os";
41982
42039
  function defaultReadHead(p, n) {
41983
42040
  let fd;
41984
42041
  try {
@@ -42006,7 +42063,7 @@ function resolveDeps2(config, deps) {
42006
42063
  }
42007
42064
  }
42008
42065
  return {
42009
- homeDir: deps.homeDir ?? homedir31(),
42066
+ homeDir: deps.homeDir ?? homedir32(),
42010
42067
  agentsDir,
42011
42068
  existsSync: deps.existsSync ?? ((p) => realExistsSync2(p)),
42012
42069
  readFileSync: deps.readFileSync ?? ((p) => realReadFileSync2(p, "utf-8")),
@@ -42035,7 +42092,7 @@ function runWebkiteChecks(config, deps = {}) {
42035
42092
  return [];
42036
42093
  const d = resolveDeps2(config, deps);
42037
42094
  const results = [];
42038
- const binPath = join54(d.homeDir, ".switchroom", "bin", "webkite");
42095
+ const binPath = join55(d.homeDir, ".switchroom", "bin", "webkite");
42039
42096
  if (!d.existsSync(binPath)) {
42040
42097
  results.push({
42041
42098
  name: "webkite: binary",
@@ -42065,12 +42122,12 @@ function runWebkiteChecks(config, deps = {}) {
42065
42122
  });
42066
42123
  }
42067
42124
  }
42068
- const cloakDir = join54(d.homeDir, ".cloakbrowser");
42125
+ const cloakDir = join55(d.homeDir, ".cloakbrowser");
42069
42126
  let chromeFound = false;
42070
42127
  if (d.existsSync(cloakDir)) {
42071
42128
  try {
42072
42129
  for (const entry of d.readdirSync(cloakDir)) {
42073
- if (entry.startsWith("chromium-") && d.existsSync(join54(cloakDir, entry, "chrome"))) {
42130
+ if (entry.startsWith("chromium-") && d.existsSync(join55(cloakDir, entry, "chrome"))) {
42074
42131
  chromeFound = true;
42075
42132
  break;
42076
42133
  }
@@ -42096,9 +42153,9 @@ function runWebkiteChecks(config, deps = {}) {
42096
42153
  return results;
42097
42154
  }
42098
42155
  for (const agent of enabledAgents) {
42099
- const agentDir = join54(d.agentsDir, agent);
42100
- const settingsPath = join54(agentDir, ".claude", "settings.json");
42101
- const mcpPath = join54(agentDir, ".mcp.json");
42156
+ const agentDir = join55(d.agentsDir, agent);
42157
+ const settingsPath = join55(agentDir, ".claude", "settings.json");
42158
+ const mcpPath = join55(agentDir, ".mcp.json");
42102
42159
  if (!d.existsSync(settingsPath) && !d.existsSync(mcpPath)) {
42103
42160
  continue;
42104
42161
  }
@@ -42228,7 +42285,7 @@ var init_doctor_cron_session = __esm(() => {
42228
42285
  });
42229
42286
 
42230
42287
  // src/cli/doctor-scaffold-wiring.ts
42231
- import { join as join55, resolve as resolve36 } from "node:path";
42288
+ import { join as join56, resolve as resolve36 } from "node:path";
42232
42289
  function readJson2(d, path4) {
42233
42290
  if (!d.existsSync(path4))
42234
42291
  return { kind: "absent" };
@@ -42271,8 +42328,8 @@ function checkIntegrationScaffoldWiring(args) {
42271
42328
  });
42272
42329
  continue;
42273
42330
  }
42274
- const mcpPath = join55(agentDir, ".mcp.json");
42275
- const claudeJsonPath = join55(agentDir, ".claude", ".claude.json");
42331
+ const mcpPath = join56(agentDir, ".mcp.json");
42332
+ const claudeJsonPath = join56(agentDir, ".claude", ".claude.json");
42276
42333
  const mcpRead = readJson2(deps, mcpPath);
42277
42334
  const trustRead = readJson2(deps, claudeJsonPath);
42278
42335
  if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
@@ -42336,14 +42393,14 @@ import {
42336
42393
  existsSync as realExistsSync3,
42337
42394
  readFileSync as realReadFileSync3
42338
42395
  } from "node:fs";
42339
- import { join as join56 } from "node:path";
42340
- import { homedir as homedir32 } from "node:os";
42396
+ import { join as join57 } from "node:path";
42397
+ import { homedir as homedir33 } from "node:os";
42341
42398
  function resolveDeps3(deps) {
42342
- const home2 = deps.homeDir?.() ?? homedir32();
42399
+ const home2 = deps.homeDir?.() ?? homedir33();
42343
42400
  return {
42344
42401
  existsSync: deps.existsSync ?? realExistsSync3,
42345
42402
  readFileSync: deps.readFileSync ?? realReadFileSync3,
42346
- agentsDir: join56(home2, ".switchroom", "agents"),
42403
+ agentsDir: join57(home2, ".switchroom", "agents"),
42347
42404
  now: deps.now ?? Date.now
42348
42405
  };
42349
42406
  }
@@ -42426,7 +42483,7 @@ function checkOAuthClient2(config, anyAgentEnabled) {
42426
42483
  ];
42427
42484
  }
42428
42485
  function readHeartbeat(d, agentName) {
42429
- const path4 = join56(d.agentsDir, agentName, "m365-launcher.heartbeat.json");
42486
+ const path4 = join57(d.agentsDir, agentName, "m365-launcher.heartbeat.json");
42430
42487
  if (!d.existsSync(path4)) {
42431
42488
  return { error: "heartbeat file missing \u2014 launcher has not yet started" };
42432
42489
  }
@@ -42523,15 +42580,15 @@ import {
42523
42580
  readFileSync as realReadFileSync4,
42524
42581
  statSync as realStatSync2
42525
42582
  } from "node:fs";
42526
- import { join as join57 } from "node:path";
42527
- import { homedir as homedir33 } from "node:os";
42583
+ import { join as join58 } from "node:path";
42584
+ import { homedir as homedir34 } from "node:os";
42528
42585
  function resolveDeps4(deps) {
42529
- const home2 = deps.homeDir?.() ?? homedir33();
42586
+ const home2 = deps.homeDir?.() ?? homedir34();
42530
42587
  return {
42531
42588
  existsSync: deps.existsSync ?? realExistsSync4,
42532
42589
  readFileSync: deps.readFileSync ?? realReadFileSync4,
42533
42590
  statSync: deps.statSync ?? realStatSync2,
42534
- agentsDir: join57(home2, ".switchroom", "agents"),
42591
+ agentsDir: join58(home2, ".switchroom", "agents"),
42535
42592
  now: deps.now ?? Date.now,
42536
42593
  vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
42537
42594
  };
@@ -42656,7 +42713,7 @@ function checkLauncherHeartbeat2(notionAgents, d) {
42656
42713
  return [];
42657
42714
  const results = [];
42658
42715
  for (const name of notionAgents) {
42659
- const heartbeatPath = join57(d.agentsDir, name, "notion-launcher.heartbeat.json");
42716
+ const heartbeatPath = join58(d.agentsDir, name, "notion-launcher.heartbeat.json");
42660
42717
  if (!d.existsSync(heartbeatPath)) {
42661
42718
  results.push({
42662
42719
  name: `notion:launcher-heartbeat:${name}`,
@@ -42877,11 +42934,11 @@ import {
42877
42934
  readdirSync as realReaddirSync2,
42878
42935
  statSync as realStatSync3
42879
42936
  } from "node:fs";
42880
- import { homedir as homedir34 } from "node:os";
42881
- import { join as join58 } from "node:path";
42937
+ import { homedir as homedir35 } from "node:os";
42938
+ import { join as join59 } from "node:path";
42882
42939
  function runCredentialsMigrationChecks(config, deps = {}) {
42883
- const credDir = deps.credentialsDir ?? join58(homedir34(), ".switchroom", "credentials");
42884
- const existsSync60 = deps.existsSync ?? ((p) => realExistsSync5(p));
42940
+ const credDir = deps.credentialsDir ?? join59(homedir35(), ".switchroom", "credentials");
42941
+ const existsSync61 = deps.existsSync ?? ((p) => realExistsSync5(p));
42885
42942
  const readdirSync21 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
42886
42943
  const isDirectory = deps.isDirectory ?? ((p) => {
42887
42944
  try {
@@ -42890,7 +42947,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
42890
42947
  return false;
42891
42948
  }
42892
42949
  });
42893
- if (!existsSync60(credDir))
42950
+ if (!existsSync61(credDir))
42894
42951
  return [];
42895
42952
  const agentNames = new Set(Object.keys(config.agents ?? {}));
42896
42953
  let entries;
@@ -42908,7 +42965,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
42908
42965
  const flat = [];
42909
42966
  const perAgentDirs = [];
42910
42967
  for (const e of entries) {
42911
- const full = join58(credDir, e);
42968
+ const full = join59(credDir, e);
42912
42969
  if (isDirectory(full) && agentNames.has(e)) {
42913
42970
  perAgentDirs.push(e);
42914
42971
  } else {
@@ -43031,19 +43088,19 @@ var init_doctor_inlined_secrets = __esm(() => {
43031
43088
 
43032
43089
  // src/cli/doctor-audit-integrity.ts
43033
43090
  import { readFileSync as fsReadFileSync2 } from "node:fs";
43034
- import { homedir as homedir35 } from "node:os";
43035
- import { join as join59 } from "node:path";
43091
+ import { homedir as homedir36 } from "node:os";
43092
+ import { join as join60 } from "node:path";
43036
43093
  function rootWrittenLogs(home2) {
43037
43094
  return [
43038
- { label: "vault-broker", path: join59(home2, ".switchroom", "vault-audit.log") },
43095
+ { label: "vault-broker", path: join60(home2, ".switchroom", "vault-audit.log") },
43039
43096
  {
43040
43097
  label: "hostd",
43041
- path: join59(home2, ".switchroom", "host-control-audit.log")
43098
+ path: join60(home2, ".switchroom", "host-control-audit.log")
43042
43099
  }
43043
43100
  ];
43044
43101
  }
43045
43102
  function runAuditIntegrityChecks(deps = {}) {
43046
- const home2 = deps.homeDir ?? homedir35();
43103
+ const home2 = deps.homeDir ?? homedir36();
43047
43104
  const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
43048
43105
  const results = [];
43049
43106
  for (const { label, path: path4 } of rootWrittenLogs(home2)) {
@@ -43100,16 +43157,16 @@ var init_doctor_audit_integrity = __esm(() => {
43100
43157
  });
43101
43158
 
43102
43159
  // src/cli/doctor-agent-smoke.ts
43103
- import { existsSync as existsSync60 } from "node:fs";
43104
- import { homedir as homedir36 } from "node:os";
43105
- import { join as join60 } from "node:path";
43160
+ import { existsSync as existsSync61 } from "node:fs";
43161
+ import { homedir as homedir37 } from "node:os";
43162
+ import { join as join61 } from "node:path";
43106
43163
  import { randomUUID as randomUUID5 } from "node:crypto";
43107
43164
  async function runAgentSmokeChecks(config, deps = {}) {
43108
43165
  if (deps.fast)
43109
43166
  return [];
43110
- const home2 = deps.homeDir ?? homedir36();
43111
- const sock = deps.operatorSockPath ?? join60(home2, ".switchroom", "hostd", "operator", "sock");
43112
- if (!deps.hostdRequestImpl && !existsSync60(sock)) {
43167
+ const home2 = deps.homeDir ?? homedir37();
43168
+ const sock = deps.operatorSockPath ?? join61(home2, ".switchroom", "hostd", "operator", "sock");
43169
+ if (!deps.hostdRequestImpl && !existsSync61(sock)) {
43113
43170
  return [
43114
43171
  {
43115
43172
  name: "agent liveness",
@@ -43187,9 +43244,9 @@ var init_doctor_agent_smoke = __esm(() => {
43187
43244
 
43188
43245
  // src/cli/doctor-vault-broker-durability.ts
43189
43246
  import { execFileSync as execFileSync18 } from "node:child_process";
43190
- import { existsSync as existsSync61, statSync as statSync25 } from "node:fs";
43191
- import { homedir as homedir37 } from "node:os";
43192
- import { join as join61 } from "node:path";
43247
+ import { existsSync as existsSync62, statSync as statSync26 } from "node:fs";
43248
+ import { homedir as homedir38 } from "node:os";
43249
+ import { join as join62 } from "node:path";
43193
43250
  function probeBindMountInode(hostPath, brokerContainerPath, opts) {
43194
43251
  const statHost = opts?.statHost ?? defaultStatHost;
43195
43252
  const statBroker = opts?.statBroker ?? defaultStatBroker;
@@ -43211,10 +43268,10 @@ function probeBindMountInode(hostPath, brokerContainerPath, opts) {
43211
43268
  };
43212
43269
  }
43213
43270
  function defaultStatHost(p) {
43214
- if (!existsSync61(p))
43271
+ if (!existsSync62(p))
43215
43272
  return null;
43216
43273
  try {
43217
- const s = statSync25(p, { bigint: true });
43274
+ const s = statSync26(p, { bigint: true });
43218
43275
  return { ino: s.ino, size: Number(s.size) };
43219
43276
  } catch {
43220
43277
  return null;
@@ -43332,22 +43389,22 @@ function defaultBrokerStatusProbe() {
43332
43389
  }
43333
43390
  }
43334
43391
  function runVaultBrokerDurabilityChecks(_config, opts) {
43335
- const home2 = homedir37();
43392
+ const home2 = homedir38();
43336
43393
  const probe2 = opts?.inodeProbe ?? probeBindMountInode;
43337
43394
  return [
43338
43395
  probeBrokerUnlocked(opts?.statusProbe),
43339
43396
  probeAutoUnlockBlob(home2),
43340
43397
  probeMachineIdMount(),
43341
- formatBindMountResult("vault-broker: vault.enc bind mount", join61(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join61(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
43342
- formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join61(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join61(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
43343
- formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join61(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join61(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
43398
+ formatBindMountResult("vault-broker: vault.enc bind mount", join62(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join62(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
43399
+ formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join62(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join62(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
43400
+ formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join62(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join62(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
43344
43401
  probeKernelDbDurability(home2, {
43345
43402
  statBroker: opts?.kernelStatBroker
43346
43403
  })
43347
43404
  ];
43348
43405
  }
43349
43406
  function probeKernelDbDurability(home2, opts) {
43350
- const hostDir = join61(home2, ".switchroom", "approvals");
43407
+ const hostDir = join62(home2, ".switchroom", "approvals");
43351
43408
  const containerDir = "/state/approvals";
43352
43409
  const name = "approval-kernel: approvals bind mount (allow_always durability)";
43353
43410
  const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
@@ -43415,8 +43472,8 @@ function defaultKernelStatBroker(p) {
43415
43472
  return { kind: "ok-with-stat", ino: inoStr, size };
43416
43473
  }
43417
43474
  function probeAutoUnlockBlob(home2) {
43418
- const blobPath = join61(home2, ".switchroom", "vault-auto-unlock");
43419
- if (!existsSync61(blobPath)) {
43475
+ const blobPath = join62(home2, ".switchroom", "vault-auto-unlock");
43476
+ if (!existsSync62(blobPath)) {
43420
43477
  return {
43421
43478
  name: "vault-broker: auto-unlock blob",
43422
43479
  status: "warn",
@@ -43424,7 +43481,7 @@ function probeAutoUnlockBlob(home2) {
43424
43481
  fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
43425
43482
  };
43426
43483
  }
43427
- const sz = statSync25(blobPath).size;
43484
+ const sz = statSync26(blobPath).size;
43428
43485
  if (sz === 0) {
43429
43486
  return {
43430
43487
  name: "vault-broker: auto-unlock blob",
@@ -43440,7 +43497,7 @@ function probeAutoUnlockBlob(home2) {
43440
43497
  };
43441
43498
  }
43442
43499
  function probeMachineIdMount() {
43443
- const hostExists = existsSync61("/etc/machine-id");
43500
+ const hostExists = existsSync62("/etc/machine-id");
43444
43501
  if (!hostExists) {
43445
43502
  return {
43446
43503
  name: "vault-broker: machine-id passthrough",
@@ -43588,25 +43645,25 @@ import { execSync as execSync3, spawnSync as spawnSync11 } from "node:child_proc
43588
43645
  import {
43589
43646
  accessSync as accessSync2,
43590
43647
  constants as fsConstants5,
43591
- existsSync as existsSync62,
43648
+ existsSync as existsSync63,
43592
43649
  lstatSync as lstatSync7,
43593
- mkdirSync as mkdirSync32,
43594
- readFileSync as readFileSync55,
43650
+ mkdirSync as mkdirSync33,
43651
+ readFileSync as readFileSync56,
43595
43652
  readdirSync as readdirSync21,
43596
- statSync as statSync26
43653
+ statSync as statSync27
43597
43654
  } from "node:fs";
43598
- import { dirname as dirname18, join as join62, resolve as resolve37 } from "node:path";
43655
+ import { dirname as dirname19, join as join63, resolve as resolve37 } from "node:path";
43599
43656
  import { createPublicKey, createPrivateKey } from "node:crypto";
43600
43657
  function findInNvm(bin) {
43601
- const nvmRoot = join62(process.env.HOME ?? "", ".nvm", "versions", "node");
43602
- if (!existsSync62(nvmRoot))
43658
+ const nvmRoot = join63(process.env.HOME ?? "", ".nvm", "versions", "node");
43659
+ if (!existsSync63(nvmRoot))
43603
43660
  return null;
43604
43661
  try {
43605
43662
  const versions = readdirSync21(nvmRoot).sort().reverse();
43606
43663
  for (const v of versions) {
43607
- const candidate = join62(nvmRoot, v, "bin", bin);
43664
+ const candidate = join63(nvmRoot, v, "bin", bin);
43608
43665
  try {
43609
- const s = statSync26(candidate);
43666
+ const s = statSync27(candidate);
43610
43667
  if (s.isFile() || s.isSymbolicLink()) {
43611
43668
  return candidate;
43612
43669
  }
@@ -43769,21 +43826,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
43769
43826
  if (envBrowsersPath && envBrowsersPath.length > 0) {
43770
43827
  cacheLocations.push(envBrowsersPath);
43771
43828
  }
43772
- cacheLocations.push(join62(homeDir, ".cache", "ms-playwright"));
43829
+ cacheLocations.push(join63(homeDir, ".cache", "ms-playwright"));
43773
43830
  for (const cacheDir of cacheLocations) {
43774
- if (!existsSync62(cacheDir))
43831
+ if (!existsSync63(cacheDir))
43775
43832
  continue;
43776
43833
  try {
43777
43834
  const entries = readdirSync21(cacheDir).filter((e) => e.startsWith("chromium"));
43778
43835
  for (const entry of entries) {
43779
43836
  const candidates2 = [
43780
- join62(cacheDir, entry, "chrome-linux64", "chrome"),
43781
- join62(cacheDir, entry, "chrome-linux", "chrome"),
43782
- join62(cacheDir, entry, "chrome-linux64", "headless_shell"),
43783
- join62(cacheDir, entry, "chrome-linux", "headless_shell")
43837
+ join63(cacheDir, entry, "chrome-linux64", "chrome"),
43838
+ join63(cacheDir, entry, "chrome-linux", "chrome"),
43839
+ join63(cacheDir, entry, "chrome-linux64", "headless_shell"),
43840
+ join63(cacheDir, entry, "chrome-linux", "headless_shell")
43784
43841
  ];
43785
43842
  for (const path4 of candidates2) {
43786
- if (existsSync62(path4))
43843
+ if (existsSync63(path4))
43787
43844
  return path4;
43788
43845
  }
43789
43846
  }
@@ -43805,7 +43862,7 @@ function checkChromium() {
43805
43862
  }
43806
43863
  function checkDepsCacheWritable(depsRoot = resolvePath("~/.switchroom/deps")) {
43807
43864
  try {
43808
- mkdirSync32(depsRoot, { recursive: true });
43865
+ mkdirSync33(depsRoot, { recursive: true });
43809
43866
  accessSync2(depsRoot, fsConstants5.W_OK);
43810
43867
  return {
43811
43868
  name: "~/.switchroom/deps writable",
@@ -43912,7 +43969,7 @@ function checkDeployMounts(opts) {
43912
43969
  const home2 = opts?.home ?? process.env.HOME ?? "/root";
43913
43970
  const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
43914
43971
  const results = [];
43915
- const dockerComposePlugin = join62(home2, ".docker", "cli-plugins", "docker-compose");
43972
+ const dockerComposePlugin = join63(home2, ".docker", "cli-plugins", "docker-compose");
43916
43973
  const pluginKind = pathKind(dockerComposePlugin);
43917
43974
  if (pluginKind === "dir") {
43918
43975
  results.push({
@@ -43950,8 +44007,8 @@ function checkDeployMounts(opts) {
43950
44007
  function checkLegacyState() {
43951
44008
  const results = [];
43952
44009
  const h = process.env.HOME ?? "/root";
43953
- const clerkDir = join62(h, LEGACY_STATE_DIR);
43954
- const clerkPresent = existsSync62(clerkDir);
44010
+ const clerkDir = join63(h, LEGACY_STATE_DIR);
44011
+ const clerkPresent = existsSync63(clerkDir);
43955
44012
  results.push({
43956
44013
  name: "legacy ~/.clerk state",
43957
44014
  status: clerkPresent ? "warn" : "ok",
@@ -43960,7 +44017,7 @@ function checkLegacyState() {
43960
44017
  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."
43961
44018
  } : {}
43962
44019
  });
43963
- const legacySock = join62(h, ".switchroom", "vault-broker.sock");
44020
+ const legacySock = join63(h, ".switchroom", "vault-broker.sock");
43964
44021
  let sockStat = null;
43965
44022
  try {
43966
44023
  sockStat = lstatSync7(legacySock);
@@ -44081,7 +44138,7 @@ function checkVault(config) {
44081
44138
  detail: "Approval auth: passphrase (two-factor)"
44082
44139
  };
44083
44140
  const pairsResult = checkVaultBrokerSocketPairs(config);
44084
- if (!existsSync62(vaultPath)) {
44141
+ if (!existsSync63(vaultPath)) {
44085
44142
  return [
44086
44143
  postureResult,
44087
44144
  {
@@ -44359,8 +44416,8 @@ async function checkHindsight(config) {
44359
44416
  }
44360
44417
  function checkPendingRetainsQueue(dir) {
44361
44418
  const home2 = process.env.HOME ?? "";
44362
- const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join62(home2, ".hindsight", "pending-retains");
44363
- if (!existsSync62(pendingDir)) {
44419
+ const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join63(home2, ".hindsight", "pending-retains");
44420
+ if (!existsSync63(pendingDir)) {
44364
44421
  return {
44365
44422
  name: "pending-retains queue",
44366
44423
  status: "ok",
@@ -44419,7 +44476,7 @@ function classifyReadError(err) {
44419
44476
  }
44420
44477
  function tryReadHostFile(path4) {
44421
44478
  try {
44422
- return { kind: "ok", content: readFileSync55(path4, "utf-8") };
44479
+ return { kind: "ok", content: readFileSync56(path4, "utf-8") };
44423
44480
  } catch (err) {
44424
44481
  const kind = classifyReadError(err);
44425
44482
  const error = err?.message ?? String(err);
@@ -44431,11 +44488,11 @@ function tryReadHostFile(path4) {
44431
44488
  }
44432
44489
  }
44433
44490
  function parseEnvFile(path4) {
44434
- if (!existsSync62(path4))
44491
+ if (!existsSync63(path4))
44435
44492
  return {};
44436
44493
  let content;
44437
44494
  try {
44438
- content = readFileSync55(path4, "utf-8");
44495
+ content = readFileSync56(path4, "utf-8");
44439
44496
  } catch {
44440
44497
  return {};
44441
44498
  }
@@ -44490,7 +44547,7 @@ async function checkTelegram(config) {
44490
44547
  const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
44491
44548
  if (plugin !== "switchroom")
44492
44549
  continue;
44493
- const envPath = join62(agentsDir, name, "telegram", ".env");
44550
+ const envPath = join63(agentsDir, name, "telegram", ".env");
44494
44551
  const read = tryReadHostFile(envPath);
44495
44552
  if (read.kind === "eacces") {
44496
44553
  results.push({
@@ -44542,7 +44599,7 @@ async function checkTelegram(config) {
44542
44599
  }
44543
44600
  function checkStartShStale(agentName, startShPath) {
44544
44601
  const label = `${agentName}: start.sh scheduler block`;
44545
- if (!existsSync62(startShPath)) {
44602
+ if (!existsSync63(startShPath)) {
44546
44603
  return {
44547
44604
  name: label,
44548
44605
  status: "warn",
@@ -44552,7 +44609,7 @@ function checkStartShStale(agentName, startShPath) {
44552
44609
  }
44553
44610
  let content;
44554
44611
  try {
44555
- content = readFileSync55(startShPath, "utf-8");
44612
+ content = readFileSync56(startShPath, "utf-8");
44556
44613
  } catch (err) {
44557
44614
  return {
44558
44615
  name: label,
@@ -44573,7 +44630,7 @@ function checkStartShStale(agentName, startShPath) {
44573
44630
  }
44574
44631
  function checkLeakedHomeSwitchroom(agentName, agentDir) {
44575
44632
  const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
44576
- const path4 = join62(agentDir, "home", ".switchroom");
44633
+ const path4 = join63(agentDir, "home", ".switchroom");
44577
44634
  let stats;
44578
44635
  try {
44579
44636
  stats = lstatSync7(path4);
@@ -44610,8 +44667,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
44610
44667
  }
44611
44668
  function checkRepoHygiene(repoRoot) {
44612
44669
  const results = [];
44613
- const exportDir = join62(repoRoot, "clerk-export");
44614
- if (existsSync62(exportDir)) {
44670
+ const exportDir = join63(repoRoot, "clerk-export");
44671
+ if (existsSync63(exportDir)) {
44615
44672
  results.push({
44616
44673
  name: "repo hygiene: clerk-export/ on disk (#1072)",
44617
44674
  status: "warn",
@@ -44619,8 +44676,8 @@ function checkRepoHygiene(repoRoot) {
44619
44676
  fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
44620
44677
  });
44621
44678
  }
44622
- const knownTarball = join62(repoRoot, "clerk-export-with-secrets.tar.gz");
44623
- if (existsSync62(knownTarball)) {
44679
+ const knownTarball = join63(repoRoot, "clerk-export-with-secrets.tar.gz");
44680
+ if (existsSync63(knownTarball)) {
44624
44681
  results.push({
44625
44682
  name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
44626
44683
  status: "warn",
@@ -44637,7 +44694,7 @@ function checkRepoHygiene(repoRoot) {
44637
44694
  results.push({
44638
44695
  name: `repo hygiene: ${name} on disk (#1072)`,
44639
44696
  status: "warn",
44640
- detail: `${join62(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
44697
+ detail: `${join63(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
44641
44698
  fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
44642
44699
  });
44643
44700
  }
@@ -44660,12 +44717,12 @@ function checkRepoHygiene(repoRoot) {
44660
44717
  }
44661
44718
  function isSwitchroomCheckout(dir) {
44662
44719
  try {
44663
- if (!existsSync62(join62(dir, ".git")))
44720
+ if (!existsSync63(join63(dir, ".git")))
44664
44721
  return false;
44665
- const pkgPath = join62(dir, "package.json");
44666
- if (!existsSync62(pkgPath))
44722
+ const pkgPath = join63(dir, "package.json");
44723
+ if (!existsSync63(pkgPath))
44667
44724
  return false;
44668
- const pkg = JSON.parse(readFileSync55(pkgPath, "utf-8"));
44725
+ const pkg = JSON.parse(readFileSync56(pkgPath, "utf-8"));
44669
44726
  return pkg.name === "switchroom";
44670
44727
  } catch {
44671
44728
  return false;
@@ -44678,7 +44735,7 @@ function checkAgents(config, configPath) {
44678
44735
  const authStatuses = getAllAuthStatuses(config);
44679
44736
  for (const [name, agentConfig] of Object.entries(config.agents)) {
44680
44737
  const agentDir = resolve37(agentsDir, name);
44681
- if (!existsSync62(agentDir)) {
44738
+ if (!existsSync63(agentDir)) {
44682
44739
  results.push({
44683
44740
  name: `${name}: scaffold`,
44684
44741
  status: "fail",
@@ -44699,7 +44756,7 @@ function checkAgents(config, configPath) {
44699
44756
  fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
44700
44757
  });
44701
44758
  }
44702
- results.push(checkStartShStale(name, join62(agentDir, "start.sh")));
44759
+ results.push(checkStartShStale(name, join63(agentDir, "start.sh")));
44703
44760
  results.push(checkLeakedHomeSwitchroom(name, agentDir));
44704
44761
  const status = statuses[name];
44705
44762
  const active = status?.active ?? "unknown";
@@ -44776,8 +44833,8 @@ function checkAgents(config, configPath) {
44776
44833
  }
44777
44834
  }
44778
44835
  if (agentConfig.channels?.telegram?.plugin === "switchroom") {
44779
- const mcpJsonPath = join62(agentDir, ".mcp.json");
44780
- if (!existsSync62(mcpJsonPath)) {
44836
+ const mcpJsonPath = join63(agentDir, ".mcp.json");
44837
+ if (!existsSync63(mcpJsonPath)) {
44781
44838
  results.push({
44782
44839
  name: `${name}: .mcp.json`,
44783
44840
  status: "fail",
@@ -44786,7 +44843,7 @@ function checkAgents(config, configPath) {
44786
44843
  });
44787
44844
  } else {
44788
44845
  try {
44789
- const mcp = JSON.parse(readFileSync55(mcpJsonPath, "utf-8"));
44846
+ const mcp = JSON.parse(readFileSync56(mcpJsonPath, "utf-8"));
44790
44847
  const hasSwitchroomTelegram = !!mcp.mcpServers?.["switchroom-telegram"];
44791
44848
  const memoryEnabled = isHindsightEnabled(config);
44792
44849
  const hasHindsight = !!mcp.mcpServers?.hindsight;
@@ -44863,7 +44920,7 @@ function mffEnvPath(config) {
44863
44920
  return agent ? resolve37(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve37(home2, ".switchroom/credentials/my-family-finance/.env");
44864
44921
  }
44865
44922
  function mffEnvState(envPath) {
44866
- if (!existsSync62(envPath))
44923
+ if (!existsSync63(envPath))
44867
44924
  return "absent";
44868
44925
  try {
44869
44926
  accessSync2(envPath, fsConstants5.R_OK);
@@ -44881,7 +44938,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
44881
44938
  fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
44882
44939
  };
44883
44940
  }
44884
- if (!existsSync62(vaultPath)) {
44941
+ if (!existsSync63(vaultPath)) {
44885
44942
  return {
44886
44943
  name: "mff: vault key present",
44887
44944
  status: "fail",
@@ -44934,7 +44991,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
44934
44991
  }
44935
44992
  }
44936
44993
  function checkMffVaultKeyFormat(passphrase, vaultPath) {
44937
- if (!passphrase || !existsSync62(vaultPath)) {
44994
+ if (!passphrase || !existsSync63(vaultPath)) {
44938
44995
  return {
44939
44996
  name: "mff: vault key format",
44940
44997
  status: "warn",
@@ -45077,9 +45134,9 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
45077
45134
  detail: "skipped (MFF_API_URL not set)"
45078
45135
  };
45079
45136
  }
45080
- const credDir = dirname18(envPath);
45081
- const authScript = join62(credDir, "claude-auth.py");
45082
- if (!existsSync62(authScript)) {
45137
+ const credDir = dirname19(envPath);
45138
+ const authScript = join63(credDir, "claude-auth.py");
45139
+ if (!existsSync63(authScript)) {
45083
45140
  return {
45084
45141
  name: "mff: auth flow",
45085
45142
  status: "warn",
@@ -45281,11 +45338,11 @@ function runDockerSection(config) {
45281
45338
  let composeYaml;
45282
45339
  let dockerfileAgent;
45283
45340
  try {
45284
- composeYaml = readFileSync55(composePath, "utf8");
45341
+ composeYaml = readFileSync56(composePath, "utf8");
45285
45342
  } catch {}
45286
45343
  const dockerfilePath = resolve37(process.env.HOME ?? "", ".switchroom", "docker", "Dockerfile.agent");
45287
45344
  try {
45288
- dockerfileAgent = readFileSync55(dockerfilePath, "utf8");
45345
+ dockerfileAgent = readFileSync56(dockerfilePath, "utf8");
45289
45346
  } catch {}
45290
45347
  return runDockerChecks({
45291
45348
  config,
@@ -48605,8 +48662,8 @@ agents:
48605
48662
  var init_minimal = () => {};
48606
48663
 
48607
48664
  // src/agents/connection-health.ts
48608
- import { mkdirSync as mkdirSync43, writeFileSync as writeFileSync35 } from "node:fs";
48609
- import { join as join77 } from "node:path";
48665
+ import { mkdirSync as mkdirSync44, writeFileSync as writeFileSync36 } from "node:fs";
48666
+ import { join as join78 } from "node:path";
48610
48667
  async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
48611
48668
  const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
48612
48669
  if (reqs.length === 0)
@@ -48663,10 +48720,10 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
48663
48720
  return issues;
48664
48721
  }
48665
48722
  function writeConnectionHealthFile(agentDir, health, deps) {
48666
- const dir = join77(agentDir, ".claude");
48667
- const path7 = join77(dir, CONNECTION_HEALTH_FILENAME);
48668
- (deps?.mkdir ?? ((p, o) => mkdirSync43(p, o)))(dir, { recursive: true });
48669
- (deps?.writeFile ?? ((p, d) => writeFileSync35(p, d)))(path7, JSON.stringify(health, null, 2) + `
48723
+ const dir = join78(agentDir, ".claude");
48724
+ const path7 = join78(dir, CONNECTION_HEALTH_FILENAME);
48725
+ (deps?.mkdir ?? ((p, o) => mkdirSync44(p, o)))(dir, { recursive: true });
48726
+ (deps?.writeFile ?? ((p, d) => writeFileSync36(p, d)))(path7, JSON.stringify(health, null, 2) + `
48670
48727
  `);
48671
48728
  }
48672
48729
  async function refreshAgentConnectionHealth(config, agentName, agentDir, deps) {
@@ -48687,10 +48744,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
48687
48744
  var init_connection_health = () => {};
48688
48745
 
48689
48746
  // src/cli/update-prompt-hook.ts
48690
- import { existsSync as existsSync79, readFileSync as readFileSync67, writeFileSync as writeFileSync36, chmodSync as chmodSync10, mkdirSync as mkdirSync44 } from "node:fs";
48691
- import { join as join78 } from "node:path";
48747
+ import { existsSync as existsSync80, readFileSync as readFileSync68, writeFileSync as writeFileSync37, chmodSync as chmodSync10, mkdirSync as mkdirSync45 } from "node:fs";
48748
+ import { join as join79 } from "node:path";
48692
48749
  function containerHookCommand() {
48693
- return join78(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
48750
+ return join79(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
48694
48751
  }
48695
48752
  function updatePromptHookScript() {
48696
48753
  return `#!/bin/bash
@@ -48756,14 +48813,14 @@ exit 0
48756
48813
  `;
48757
48814
  }
48758
48815
  function installUpdatePromptHook(agentDir) {
48759
- const hooksDir = join78(agentDir, ".claude", "hooks");
48760
- mkdirSync44(hooksDir, { recursive: true });
48761
- const scriptPath = join78(hooksDir, HOOK_FILENAME);
48816
+ const hooksDir = join79(agentDir, ".claude", "hooks");
48817
+ mkdirSync45(hooksDir, { recursive: true });
48818
+ const scriptPath = join79(hooksDir, HOOK_FILENAME);
48762
48819
  const desired = updatePromptHookScript();
48763
48820
  let installed = false;
48764
- const existing = existsSync79(scriptPath) ? readFileSync67(scriptPath, "utf-8") : "";
48821
+ const existing = existsSync80(scriptPath) ? readFileSync68(scriptPath, "utf-8") : "";
48765
48822
  if (existing !== desired) {
48766
- writeFileSync36(scriptPath, desired, { mode: 493 });
48823
+ writeFileSync37(scriptPath, desired, { mode: 493 });
48767
48824
  chmodSync10(scriptPath, 493);
48768
48825
  installed = true;
48769
48826
  } else {
@@ -48771,11 +48828,11 @@ function installUpdatePromptHook(agentDir) {
48771
48828
  chmodSync10(scriptPath, 493);
48772
48829
  } catch {}
48773
48830
  }
48774
- const settingsPath = join78(agentDir, ".claude", "settings.json");
48775
- if (!existsSync79(settingsPath)) {
48831
+ const settingsPath = join79(agentDir, ".claude", "settings.json");
48832
+ if (!existsSync80(settingsPath)) {
48776
48833
  return { scriptPath, settingsPath, installed };
48777
48834
  }
48778
- const raw = readFileSync67(settingsPath, "utf-8");
48835
+ const raw = readFileSync68(settingsPath, "utf-8");
48779
48836
  let parsed;
48780
48837
  try {
48781
48838
  parsed = JSON.parse(raw);
@@ -48811,7 +48868,7 @@ function installUpdatePromptHook(agentDir) {
48811
48868
  if (mutated) {
48812
48869
  hooks.UserPromptSubmit = list2;
48813
48870
  parsed.hooks = hooks;
48814
- writeFileSync36(settingsPath, JSON.stringify(parsed, null, 2) + `
48871
+ writeFileSync37(settingsPath, JSON.stringify(parsed, null, 2) + `
48815
48872
  `, { mode: 384 });
48816
48873
  installed = true;
48817
48874
  } else if (!alreadyCorrect) {
@@ -48820,7 +48877,7 @@ function installUpdatePromptHook(agentDir) {
48820
48877
  });
48821
48878
  hooks.UserPromptSubmit = list2;
48822
48879
  parsed.hooks = hooks;
48823
- writeFileSync36(settingsPath, JSON.stringify(parsed, null, 2) + `
48880
+ writeFileSync37(settingsPath, JSON.stringify(parsed, null, 2) + `
48824
48881
  `, { mode: 384 });
48825
48882
  installed = true;
48826
48883
  }
@@ -48883,6 +48940,7 @@ var init_install_detect = () => {};
48883
48940
  // src/litellm/provision.ts
48884
48941
  var exports_provision = {};
48885
48942
  __export(exports_provision, {
48943
+ validateKey: () => validateKey,
48886
48944
  ensureTeam: () => ensureTeam,
48887
48945
  ensureKey: () => ensureKey,
48888
48946
  LiteLLMProvisionError: () => LiteLLMProvisionError
@@ -48904,6 +48962,12 @@ function looksLikeAlreadyExists(status, body) {
48904
48962
  return true;
48905
48963
  return false;
48906
48964
  }
48965
+ function looksLikeUnknownKey(status, body) {
48966
+ if (status !== 400 && status !== 401 && status !== 403 && status !== 404)
48967
+ return false;
48968
+ const t = body.toLowerCase();
48969
+ return t.includes("not found") || t.includes("does not exist") || t.includes("no such key") || t.includes("no key found");
48970
+ }
48907
48971
  async function ensureTeam(baseUrl, masterKey, teamName, fetchFn = fetch) {
48908
48972
  const url = `${normalizeBase(baseUrl)}/team/new`;
48909
48973
  let resp;
@@ -48923,7 +48987,7 @@ async function ensureTeam(baseUrl, masterKey, teamName, fetchFn = fetch) {
48923
48987
  return;
48924
48988
  throw new LiteLLMProvisionError(`LiteLLM /team/new returned ${resp.status}`, resp.status, body);
48925
48989
  }
48926
- async function ensureKey(opts, fetchFn = fetch) {
48990
+ async function generateKeyOnce(opts, fetchFn) {
48927
48991
  const url = `${normalizeBase(opts.baseUrl)}/key/generate`;
48928
48992
  const payload = {
48929
48993
  key_alias: opts.alias
@@ -48943,23 +49007,129 @@ async function ensureKey(opts, fetchFn = fetch) {
48943
49007
  body: JSON.stringify(payload)
48944
49008
  });
48945
49009
  } catch (err) {
48946
- throw new LiteLLMProvisionError(`LiteLLM /key/generate request failed: ${err.message}`);
49010
+ return {
49011
+ kind: "error",
49012
+ error: new LiteLLMProvisionError(`LiteLLM /key/generate request failed: ${err.message}`)
49013
+ };
48947
49014
  }
48948
49015
  if (!resp.ok) {
48949
49016
  const body = await safeText(resp);
48950
- throw new LiteLLMProvisionError(`LiteLLM /key/generate returned ${resp.status}`, resp.status, body);
49017
+ if (looksLikeAlreadyExists(resp.status, body)) {
49018
+ return { kind: "duplicate-alias", status: resp.status, body };
49019
+ }
49020
+ return {
49021
+ kind: "error",
49022
+ error: new LiteLLMProvisionError(`LiteLLM /key/generate returned ${resp.status}`, resp.status, body)
49023
+ };
48951
49024
  }
48952
49025
  let json;
48953
49026
  try {
48954
49027
  json = await resp.json();
48955
49028
  } catch (err) {
48956
- throw new LiteLLMProvisionError(`LiteLLM /key/generate returned non-JSON body: ${err.message}`, resp.status);
49029
+ return {
49030
+ kind: "error",
49031
+ error: new LiteLLMProvisionError(`LiteLLM /key/generate returned non-JSON body: ${err.message}`, resp.status)
49032
+ };
48957
49033
  }
48958
49034
  const key = json?.key;
48959
49035
  if (typeof key !== "string" || key.length === 0) {
48960
- throw new LiteLLMProvisionError(`LiteLLM /key/generate response missing a "key" field`, resp.status, JSON.stringify(json));
49036
+ return {
49037
+ kind: "error",
49038
+ error: new LiteLLMProvisionError(`LiteLLM /key/generate response missing a "key" field`, resp.status, JSON.stringify(json))
49039
+ };
49040
+ }
49041
+ return { kind: "ok", key };
49042
+ }
49043
+ async function resolveTokensForAlias(baseUrl, masterKey, alias, fetchFn) {
49044
+ const url = `${normalizeBase(baseUrl)}/key/list?key_alias=${encodeURIComponent(alias)}` + `&return_full_object=true&include_team_keys=true`;
49045
+ let resp;
49046
+ try {
49047
+ resp = await fetchFn(url, { method: "GET", headers: authHeaders3(masterKey) });
49048
+ } catch {
49049
+ return [];
49050
+ }
49051
+ if (!resp.ok)
49052
+ return [];
49053
+ let json;
49054
+ try {
49055
+ json = await resp.json();
49056
+ } catch {
49057
+ return [];
49058
+ }
49059
+ const keys = json?.keys;
49060
+ if (!Array.isArray(keys))
49061
+ return [];
49062
+ const tokens = [];
49063
+ for (const k of keys) {
49064
+ if (typeof k === "string" && k.length > 0)
49065
+ tokens.push(k);
49066
+ else if (k && typeof k === "object") {
49067
+ const tok = k.token;
49068
+ const raw = k.key;
49069
+ if (typeof tok === "string" && tok.length > 0)
49070
+ tokens.push(tok);
49071
+ else if (typeof raw === "string" && raw.length > 0)
49072
+ tokens.push(raw);
49073
+ }
49074
+ }
49075
+ return tokens;
49076
+ }
49077
+ async function deleteOrphanedAlias(opts, fetchFn, log) {
49078
+ const tokens = await resolveTokensForAlias(opts.baseUrl, opts.masterKey, opts.alias, fetchFn);
49079
+ const body = { key_aliases: [opts.alias] };
49080
+ if (tokens.length > 0)
49081
+ body.keys = tokens;
49082
+ const manual = `Manual remediation: delete the key with alias '${opts.alias}' in the LiteLLM ` + `admin UI, or run ` + `\`curl -X POST "${normalizeBase(opts.baseUrl)}/key/delete" -H "Authorization: Bearer <master-key>" ` + `-H "content-type: application/json" -d '{"key_aliases":["${opts.alias}"]}'\`, ` + `then re-run \`switchroom apply\`.`;
49083
+ const url = `${normalizeBase(opts.baseUrl)}/key/delete`;
49084
+ let resp;
49085
+ try {
49086
+ resp = await fetchFn(url, {
49087
+ method: "POST",
49088
+ headers: authHeaders3(opts.masterKey),
49089
+ body: JSON.stringify(body)
49090
+ });
49091
+ } catch (err) {
49092
+ throw new LiteLLMProvisionError(`LiteLLM /key/delete request failed while recovering orphaned alias ` + `'${opts.alias}': ${err.message}. ${manual}`);
49093
+ }
49094
+ if (!resp.ok) {
49095
+ const errBody = await safeText(resp);
49096
+ throw new LiteLLMProvisionError(`LiteLLM /key/delete returned ${resp.status} while recovering orphaned ` + `alias '${opts.alias}'. ${manual}`, resp.status, errBody);
49097
+ }
49098
+ log(`litellm: deleted orphaned key(s) for alias '${opts.alias}'` + (tokens.length > 0 ? ` (${tokens.length} token(s) resolved)` : "") + ` \u2014 regenerating`);
49099
+ }
49100
+ async function ensureKey(opts, fetchFn = fetch) {
49101
+ const log = opts.log ?? (() => {});
49102
+ const first = await generateKeyOnce(opts, fetchFn);
49103
+ if (first.kind === "ok")
49104
+ return { key: first.key };
49105
+ if (first.kind === "error")
49106
+ throw first.error;
49107
+ log(`litellm: key_alias '${opts.alias}' already exists in LiteLLM but the vault ` + `has no recoverable key (orphaned by a prior failed provision) \u2014 ` + `self-healing: deleting the orphan and regenerating`);
49108
+ await deleteOrphanedAlias(opts, fetchFn, log);
49109
+ const second = await generateKeyOnce(opts, fetchFn);
49110
+ if (second.kind === "ok") {
49111
+ log(`litellm: regenerated key for alias '${opts.alias}' after orphan recovery`);
49112
+ return { key: second.key };
49113
+ }
49114
+ if (second.kind === "duplicate-alias") {
49115
+ throw new LiteLLMProvisionError(`LiteLLM /key/generate still reports alias '${opts.alias}' already exists ` + `after deleting the orphan \u2014 the delete did not take effect. Manual ` + `remediation: remove the key with alias '${opts.alias}' via the LiteLLM ` + `admin UI (or POST /key/delete {"key_aliases":["${opts.alias}"]}) then ` + `re-run \`switchroom apply\`.`, second.status, second.body);
49116
+ }
49117
+ throw second.error;
49118
+ }
49119
+ async function validateKey(opts, fetchFn = fetch) {
49120
+ const url = `${normalizeBase(opts.baseUrl)}/key/info?key=${encodeURIComponent(opts.key)}`;
49121
+ let resp;
49122
+ try {
49123
+ resp = await fetchFn(url, { method: "GET", headers: authHeaders3(opts.masterKey) });
49124
+ } catch (err) {
49125
+ return { kind: "unreachable", detail: err.message };
48961
49126
  }
48962
- return { key };
49127
+ if (resp.ok)
49128
+ return { kind: "valid" };
49129
+ const body = await safeText(resp);
49130
+ if (looksLikeUnknownKey(resp.status, body))
49131
+ return { kind: "unknown" };
49132
+ return { kind: "unreachable", detail: `HTTP ${resp.status}: ${body.slice(0, 200)}` };
48963
49133
  }
48964
49134
  async function safeText(resp) {
48965
49135
  try {
@@ -48999,8 +49169,8 @@ __export(exports_voice_sidecar_token, {
48999
49169
  VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
49000
49170
  });
49001
49171
  import { randomBytes as randomBytes14 } from "node:crypto";
49002
- import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as existsSync81, mkdirSync as mkdirSync45, readFileSync as readFileSync68, rmSync as rmSync17, writeFileSync as writeFileSync37 } from "node:fs";
49003
- import { dirname as dirname27 } from "node:path";
49172
+ import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as existsSync82, mkdirSync as mkdirSync46, readFileSync as readFileSync69, rmSync as rmSync17, writeFileSync as writeFileSync38 } from "node:fs";
49173
+ import { dirname as dirname28 } from "node:path";
49004
49174
  async function defaultResolveOrSeedToken(home2, writeErr) {
49005
49175
  const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
49006
49176
  Promise.resolve().then(() => (init_client(), exports_client)),
@@ -49034,8 +49204,8 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
49034
49204
  const envPath = composeEnvPath(composePath);
49035
49205
  if (engine !== "local") {
49036
49206
  try {
49037
- if (existsSync81(envPath)) {
49038
- const body = readFileSync68(envPath, "utf-8");
49207
+ if (existsSync82(envPath)) {
49208
+ const body = readFileSync69(envPath, "utf-8");
49039
49209
  if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
49040
49210
  rmSync17(envPath);
49041
49211
  }
@@ -49054,11 +49224,11 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
49054
49224
  if (!token)
49055
49225
  return;
49056
49226
  try {
49057
- mkdirSync45(dirname27(envPath), { recursive: true });
49227
+ mkdirSync46(dirname28(envPath), { recursive: true });
49058
49228
  let body = "";
49059
49229
  try {
49060
- if (existsSync81(envPath))
49061
- body = readFileSync68(envPath, "utf-8");
49230
+ if (existsSync82(envPath))
49231
+ body = readFileSync69(envPath, "utf-8");
49062
49232
  } catch {}
49063
49233
  const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
49064
49234
  const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
@@ -49066,7 +49236,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
49066
49236
  `) ? body + `
49067
49237
  ` : body) + line + `
49068
49238
  `;
49069
- writeFileSync37(envPath, next, {
49239
+ writeFileSync38(envPath, next, {
49070
49240
  encoding: "utf-8",
49071
49241
  mode: 384
49072
49242
  });
@@ -49116,12 +49286,12 @@ __export(exports_apply, {
49116
49286
  DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
49117
49287
  COMPOSE_PROJECT: () => COMPOSE_PROJECT2
49118
49288
  });
49119
- import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync82, mkdirSync as mkdirSync46, readFileSync as readFileSync69, readdirSync as readdirSync28, renameSync as renameSync18, writeFileSync as writeFileSync38 } from "node:fs";
49289
+ import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync83, mkdirSync as mkdirSync47, readFileSync as readFileSync70, readdirSync as readdirSync28, renameSync as renameSync18, writeFileSync as writeFileSync39 } from "node:fs";
49120
49290
  import { mkdir as mkdir2 } from "node:fs/promises";
49121
49291
  import { spawnSync as childSpawnSync } from "node:child_process";
49122
49292
  import readline from "node:readline";
49123
- import { dirname as dirname28, join as join80, resolve as resolve50 } from "node:path";
49124
- import { homedir as homedir47 } from "node:os";
49293
+ import { dirname as dirname29, join as join81, resolve as resolve50 } from "node:path";
49294
+ import { homedir as homedir48 } from "node:os";
49125
49295
  import { execFileSync as execFileSync25 } from "node:child_process";
49126
49296
  function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
49127
49297
  return agentResolvedLitellm?.enabled ?? config.litellm?.enabled ?? false;
@@ -49132,7 +49302,7 @@ async function resolveOperatorVaultPassphrase(home2) {
49132
49302
  return envPass;
49133
49303
  try {
49134
49304
  const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
49135
- const blobPath = join80(home2, ".switchroom", "vault-auto-unlock");
49305
+ const blobPath = join81(home2, ".switchroom", "vault-auto-unlock");
49136
49306
  const pass = readAutoUnlockFile2(blobPath);
49137
49307
  return pass && pass.length > 0 ? pass : null;
49138
49308
  } catch {
@@ -49154,12 +49324,12 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49154
49324
  const needsHindsight = config.litellm?.enabled === true && config.memory?.backend === "hindsight";
49155
49325
  if (optedIn.length === 0 && !needsHindsight)
49156
49326
  return;
49157
- const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
49327
+ const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2, validateKey: validateKey2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
49158
49328
  Promise.resolve().then(() => (init_client(), exports_client)),
49159
49329
  Promise.resolve().then(() => (init_provision(), exports_provision)),
49160
49330
  Promise.resolve().then(() => (init_telegram_yaml(), exports_telegram_yaml))
49161
49331
  ]);
49162
- const passphrase = await resolveOperatorVaultPassphrase(ctx.home ?? homedir47());
49332
+ const passphrase = await resolveOperatorVaultPassphrase(ctx.home ?? homedir48());
49163
49333
  if (passphrase === null) {
49164
49334
  const { getViaBrokerStructured: getViaBrokerStructured3 } = await Promise.resolve().then(() => (init_client(), exports_client));
49165
49335
  const alreadyProvisioned = [];
@@ -49176,7 +49346,21 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49176
49346
  unprovisionedNew.push(name);
49177
49347
  }
49178
49348
  }
49179
- const hindsightPending = needsHindsight;
49349
+ let hindsightPending = false;
49350
+ if (needsHindsight) {
49351
+ const h = await getViaBrokerStructured3("litellm/hindsight/api-key");
49352
+ if (h.kind === "ok") {
49353
+ hindsightPending = false;
49354
+ writeOut(source_default.gray(` ~ litellm/hindsight: key already provisioned (skipped) \u2014 unaffected by missing passphrase.
49355
+ `));
49356
+ } else if (h.kind === "unreachable") {
49357
+ hindsightPending = false;
49358
+ ctx.writeErr(source_default.yellow(` ! litellm/hindsight: vault-broker unreachable \u2014 could not verify the ` + `service key; it keeps its previous state.
49359
+ `));
49360
+ } else {
49361
+ hindsightPending = true;
49362
+ }
49363
+ }
49180
49364
  if (alreadyProvisioned.length > 0) {
49181
49365
  writeOut(source_default.gray(` ~ litellm: ${alreadyProvisioned.length} agent(s) already provisioned (${alreadyProvisioned.join(", ")}) \u2014 unaffected by missing passphrase.
49182
49366
  `));
@@ -49206,9 +49390,9 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49206
49390
  const oauthAccount = config.auth?.active;
49207
49391
  let pendingConfigEdits = false;
49208
49392
  let configText = null;
49209
- if (switchroomConfigPath && existsSync82(switchroomConfigPath)) {
49393
+ if (switchroomConfigPath && existsSync83(switchroomConfigPath)) {
49210
49394
  try {
49211
- configText = readFileSync69(switchroomConfigPath, "utf-8");
49395
+ configText = readFileSync70(switchroomConfigPath, "utf-8");
49212
49396
  } catch (err) {
49213
49397
  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.
49214
49398
  `));
@@ -49227,43 +49411,62 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49227
49411
  continue;
49228
49412
  }
49229
49413
  const vaultKey = `litellm/${name}/api-key`;
49414
+ const alias = `agent:${name}`;
49230
49415
  const existing = await getViaBrokerStructured2(vaultKey);
49231
- if (existing.kind === "ok") {
49232
- writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned (skipped)
49233
- `));
49234
- if (configText !== null) {
49235
- const after = addAgentSecret2(configText, name, vaultKey);
49236
- if (after !== configText) {
49237
- configText = after;
49238
- pendingConfigEdits = true;
49239
- }
49240
- }
49241
- continue;
49242
- }
49243
49416
  if (existing.kind === "unreachable") {
49244
49417
  ctx.writeErr(source_default.yellow(` ! litellm/${name}: vault-broker unreachable (${existing.msg ?? "no detail"}) \u2014 ` + `skipping key provisioning; agent keeps its previous routing env.
49245
49418
  `));
49246
49419
  continue;
49247
49420
  }
49248
49421
  let masterKey = adminKeyRef;
49422
+ let masterKeyErr = null;
49249
49423
  if (isVaultReference(adminKeyRef)) {
49250
49424
  const refKey = parseVaultReference(adminKeyRef);
49251
49425
  const resolved = await getViaBrokerStructured2(refKey);
49252
49426
  if (resolved.kind !== "ok") {
49253
- failures.push({
49254
- agent: name,
49255
- message: `litellm: admin_key vault ref '${adminKeyRef}' did not resolve (${resolved.kind}${"msg" in resolved && resolved.msg ? `: ${resolved.msg}` : ""}).`
49256
- });
49257
- continue;
49427
+ masterKey = null;
49428
+ masterKeyErr = `litellm: admin_key vault ref '${adminKeyRef}' did not resolve (${resolved.kind}${"msg" in resolved && resolved.msg ? `: ${resolved.msg}` : ""}).`;
49429
+ } else if (resolved.entry.kind !== "string") {
49430
+ masterKey = null;
49431
+ masterKeyErr = `litellm: admin_key vault ref '${adminKeyRef}' is not a string secret.`;
49432
+ } else {
49433
+ masterKey = resolved.entry.value;
49258
49434
  }
49259
- if (resolved.entry.kind !== "string") {
49260
- failures.push({
49261
- agent: name,
49262
- message: `litellm: admin_key vault ref '${adminKeyRef}' is not a string secret.`
49263
- });
49435
+ }
49436
+ if (existing.kind === "ok") {
49437
+ const storedKey = existing.entry.kind === "string" ? existing.entry.value : null;
49438
+ let driftReprovision = false;
49439
+ if (storedKey && masterKey) {
49440
+ const validation = await validateKey2({ baseUrl, masterKey, key: storedKey });
49441
+ if (validation.kind === "unknown") {
49442
+ driftReprovision = true;
49443
+ ctx.writeErr(source_default.yellow(` ! litellm/${name}: stored virtual key not recognized by the proxy ` + `(DB drift) \u2014 re-provisioning a fresh key.
49444
+ `));
49445
+ } else if (validation.kind === "unreachable") {
49446
+ ctx.writeErr(source_default.yellow(` ! litellm/${name}: proxy unreachable during key validation (${validation.detail}) \u2014 keeping the stored key unverified.
49447
+ `));
49448
+ } else {
49449
+ writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned + validated (skipped)
49450
+ `));
49451
+ }
49452
+ } else {
49453
+ writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned (skipped)
49454
+ `));
49455
+ }
49456
+ if (!driftReprovision) {
49457
+ if (configText !== null) {
49458
+ const after = addAgentSecret2(configText, name, vaultKey);
49459
+ if (after !== configText) {
49460
+ configText = after;
49461
+ pendingConfigEdits = true;
49462
+ }
49463
+ }
49264
49464
  continue;
49265
49465
  }
49266
- masterKey = resolved.entry.value;
49466
+ }
49467
+ if (!masterKey) {
49468
+ failures.push({ agent: name, message: masterKeyErr ?? `litellm: admin_key unresolved` });
49469
+ continue;
49267
49470
  }
49268
49471
  await ensureTeam2(baseUrl, masterKey, team);
49269
49472
  const metadata = {
@@ -49281,9 +49484,11 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49281
49484
  const { key } = await ensureKey2({
49282
49485
  baseUrl,
49283
49486
  masterKey,
49284
- alias: `agent:${name}`,
49487
+ alias,
49285
49488
  team,
49286
- metadata
49489
+ metadata,
49490
+ log: (m) => ctx.writeErr(source_default.gray(` ~ litellm/${name}: ${m}
49491
+ `))
49287
49492
  });
49288
49493
  const put = await putViaBroker2(vaultKey, { kind: "string", value: key }, { passphrase });
49289
49494
  if (put.kind !== "ok") {
@@ -49310,7 +49515,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49310
49515
  }
49311
49516
  if (pendingConfigEdits && configText !== null && switchroomConfigPath) {
49312
49517
  try {
49313
- writeFileSync38(switchroomConfigPath, configText, "utf-8");
49518
+ writeFileSync39(switchroomConfigPath, configText, "utf-8");
49314
49519
  writeOut(source_default.gray(` ~ litellm: granted read-ACL on per-agent keys (updated ${switchroomConfigPath})
49315
49520
  `));
49316
49521
  } catch (err) {
@@ -49345,7 +49550,9 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49345
49550
  masterKey,
49346
49551
  alias: "service:hindsight",
49347
49552
  team,
49348
- metadata: { service: "hindsight", env: "fleet", ...oauthAccount ? { oauth_account: oauthAccount } : {} }
49553
+ metadata: { service: "hindsight", env: "fleet", ...oauthAccount ? { oauth_account: oauthAccount } : {} },
49554
+ log: (m) => ctx.writeErr(source_default.gray(` ~ litellm/hindsight: ${m}
49555
+ `))
49349
49556
  });
49350
49557
  const put = await putViaBroker2(hindsightVaultKey, { kind: "string", value: key }, { passphrase });
49351
49558
  if (put.kind === "ok") {
@@ -49365,12 +49572,12 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49365
49572
  function resolveVaultBindMountDir(homeDir, ctx) {
49366
49573
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
49367
49574
  if (isCustomPath && ctx.customVaultPath) {
49368
- return dirname28(ctx.customVaultPath);
49575
+ return dirname29(ctx.customVaultPath);
49369
49576
  }
49370
- return join80(homeDir, ".switchroom", "vault");
49577
+ return join81(homeDir, ".switchroom", "vault");
49371
49578
  }
49372
49579
  function inspectVaultBindMountDir(vaultDir) {
49373
- if (!existsSync82(vaultDir))
49580
+ if (!existsSync83(vaultDir))
49374
49581
  return { kind: "missing" };
49375
49582
  const entries = readdirSync28(vaultDir);
49376
49583
  const unknown = [];
@@ -49398,61 +49605,61 @@ function hasVaultRefs(value) {
49398
49605
  async function ensureHostMountSources(config) {
49399
49606
  const home2 = resolveHostHomeForCompose();
49400
49607
  const dirs = [
49401
- join80(home2, ".switchroom", "approvals"),
49402
- join80(home2, ".switchroom", "scheduler"),
49403
- join80(home2, ".switchroom", "logs"),
49404
- join80(home2, ".switchroom", "compose"),
49405
- join80(home2, ".switchroom", "broker-operator")
49608
+ join81(home2, ".switchroom", "approvals"),
49609
+ join81(home2, ".switchroom", "scheduler"),
49610
+ join81(home2, ".switchroom", "logs"),
49611
+ join81(home2, ".switchroom", "compose"),
49612
+ join81(home2, ".switchroom", "broker-operator")
49406
49613
  ];
49407
49614
  for (const name of Object.keys(config.agents)) {
49408
- dirs.push(join80(home2, ".switchroom", "agents", name));
49409
- dirs.push(join80(home2, ".switchroom", "logs", name));
49410
- dirs.push(join80(home2, ".claude", "projects", name));
49411
- dirs.push(join80(home2, ".switchroom", "audit", name));
49412
- if (existsSync82(join80(home2, ".switchroom-config"))) {
49413
- dirs.push(join80(home2, ".switchroom-config", "agents", name, "personal-skills"));
49615
+ dirs.push(join81(home2, ".switchroom", "agents", name));
49616
+ dirs.push(join81(home2, ".switchroom", "logs", name));
49617
+ dirs.push(join81(home2, ".claude", "projects", name));
49618
+ dirs.push(join81(home2, ".switchroom", "audit", name));
49619
+ if (existsSync83(join81(home2, ".switchroom-config"))) {
49620
+ dirs.push(join81(home2, ".switchroom-config", "agents", name, "personal-skills"));
49414
49621
  }
49415
49622
  }
49416
49623
  for (const dir of dirs) {
49417
49624
  await mkdir2(dir, { recursive: true });
49418
49625
  }
49419
- const autoUnlockPath = join80(home2, ".switchroom", "vault-auto-unlock");
49420
- if (!existsSync82(autoUnlockPath)) {
49421
- writeFileSync38(autoUnlockPath, "", { mode: 384 });
49626
+ const autoUnlockPath = join81(home2, ".switchroom", "vault-auto-unlock");
49627
+ if (!existsSync83(autoUnlockPath)) {
49628
+ writeFileSync39(autoUnlockPath, "", { mode: 384 });
49422
49629
  }
49423
- const auditLogPath = join80(home2, ".switchroom", "vault-audit.log");
49424
- if (!existsSync82(auditLogPath)) {
49425
- writeFileSync38(auditLogPath, "", { mode: 420 });
49630
+ const auditLogPath = join81(home2, ".switchroom", "vault-audit.log");
49631
+ if (!existsSync83(auditLogPath)) {
49632
+ writeFileSync39(auditLogPath, "", { mode: 420 });
49426
49633
  }
49427
- const grantsDbPath = join80(home2, ".switchroom", "vault-grants.db");
49428
- if (!existsSync82(grantsDbPath)) {
49429
- writeFileSync38(grantsDbPath, "", { mode: 384 });
49634
+ const grantsDbPath = join81(home2, ".switchroom", "vault-grants.db");
49635
+ if (!existsSync83(grantsDbPath)) {
49636
+ writeFileSync39(grantsDbPath, "", { mode: 384 });
49430
49637
  }
49431
- const hostdAuditLogPath = join80(home2, ".switchroom", "host-control-audit.log");
49432
- if (!existsSync82(hostdAuditLogPath)) {
49433
- writeFileSync38(hostdAuditLogPath, "", { mode: 420 });
49638
+ const hostdAuditLogPath = join81(home2, ".switchroom", "host-control-audit.log");
49639
+ if (!existsSync83(hostdAuditLogPath)) {
49640
+ writeFileSync39(hostdAuditLogPath, "", { mode: 420 });
49434
49641
  }
49435
49642
  for (const name of Object.keys(config.agents)) {
49436
- const tokenPath = join80(home2, ".switchroom", "agents", name, ".vault-token");
49437
- if (!existsSync82(tokenPath)) {
49438
- writeFileSync38(tokenPath, "", { mode: 384 });
49643
+ const tokenPath = join81(home2, ".switchroom", "agents", name, ".vault-token");
49644
+ if (!existsSync83(tokenPath)) {
49645
+ writeFileSync39(tokenPath, "", { mode: 384 });
49439
49646
  }
49440
49647
  try {
49441
49648
  const uid = allocateAgentUid(name);
49442
49649
  chownSync8(tokenPath, uid, uid);
49443
49650
  } catch {}
49444
49651
  }
49445
- const fleetDir = join80(home2, ".switchroom", "fleet");
49652
+ const fleetDir = join81(home2, ".switchroom", "fleet");
49446
49653
  await mkdir2(fleetDir, { recursive: true });
49447
- const invariantsPath = join80(fleetDir, "switchroom-invariants.md");
49654
+ const invariantsPath = join81(fleetDir, "switchroom-invariants.md");
49448
49655
  const invariantsCanonical = renderFleetInvariants();
49449
- const invariantsCurrent = existsSync82(invariantsPath) ? readFileSync69(invariantsPath, "utf-8") : null;
49656
+ const invariantsCurrent = existsSync83(invariantsPath) ? readFileSync70(invariantsPath, "utf-8") : null;
49450
49657
  if (invariantsCurrent !== invariantsCanonical) {
49451
- writeFileSync38(invariantsPath, invariantsCanonical, { mode: 420 });
49658
+ writeFileSync39(invariantsPath, invariantsCanonical, { mode: 420 });
49452
49659
  }
49453
- const fleetClaudePath = join80(fleetDir, "CLAUDE.md");
49454
- if (!existsSync82(fleetClaudePath)) {
49455
- writeFileSync38(fleetClaudePath, [
49660
+ const fleetClaudePath = join81(fleetDir, "CLAUDE.md");
49661
+ if (!existsSync83(fleetClaudePath)) {
49662
+ writeFileSync39(fleetClaudePath, [
49456
49663
  "# Switchroom fleet defaults",
49457
49664
  "",
49458
49665
  "Operator-owned fleet brain. Every agent reads this via",
@@ -49490,7 +49697,7 @@ function isInAgentContainer(vaultPresent, composeV2Present, env2 = process.env)
49490
49697
  function runApplyPreflight(config, opts = {}) {
49491
49698
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
49492
49699
  const detect = opts.detectComposeV2 ?? detectComposeV2;
49493
- const vaultMissing = hasVaultRefs(config) && !existsSync82(vaultPath);
49700
+ const vaultMissing = hasVaultRefs(config) && !existsSync83(vaultPath);
49494
49701
  const composeErr = detect();
49495
49702
  if ((vaultMissing || composeErr) && isInAgentContainer(!vaultMissing, composeErr === null)) {
49496
49703
  throw new Error(IN_AGENT_CONTAINER_APPLY_MSG);
@@ -49504,7 +49711,7 @@ function runApplyPreflight(config, opts = {}) {
49504
49711
  detectAndReportLegacyGdriveSlots(vaultPath);
49505
49712
  }
49506
49713
  function detectAndReportLegacyGdriveSlots(vaultPath) {
49507
- if (!existsSync82(vaultPath))
49714
+ if (!existsSync83(vaultPath))
49508
49715
  return;
49509
49716
  const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
49510
49717
  if (!passphrase)
@@ -49543,18 +49750,18 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
49543
49750
  `));
49544
49751
  }
49545
49752
  }
49546
- function writeInstallTypeCache(homeDir = homedir47()) {
49753
+ function writeInstallTypeCache(homeDir = homedir48()) {
49547
49754
  const ctx = detectInstallType();
49548
- const dir = join80(homeDir, ".switchroom");
49549
- const out = join80(dir, "install-type.json");
49755
+ const dir = join81(homeDir, ".switchroom");
49756
+ const out = join81(dir, "install-type.json");
49550
49757
  const tmp = `${out}.tmp`;
49551
- mkdirSync46(dir, { recursive: true });
49758
+ mkdirSync47(dir, { recursive: true });
49552
49759
  const payload = {
49553
49760
  install_type: ctx.install_type,
49554
49761
  detected_at: new Date().toISOString(),
49555
49762
  source_paths: ctx.source_paths
49556
49763
  };
49557
- writeFileSync38(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
49764
+ writeFileSync39(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
49558
49765
  renameSync18(tmp, out);
49559
49766
  return out;
49560
49767
  }
@@ -49613,17 +49820,17 @@ Applying switchroom config...
49613
49820
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
49614
49821
  `));
49615
49822
  try {
49616
- installUpdatePromptHook(join80(agentsDir, name));
49823
+ installUpdatePromptHook(join81(agentsDir, name));
49617
49824
  } catch (hookErr) {
49618
49825
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
49619
49826
  `));
49620
49827
  }
49621
- await refreshAgentConnectionHealth(config, name, join80(agentsDir, name), {
49828
+ await refreshAgentConnectionHealth(config, name, join81(agentsDir, name), {
49622
49829
  vaultAclReader: connHealthVaultAclReader
49623
49830
  });
49624
49831
  try {
49625
49832
  const uid = allocateAgentUid(name);
49626
- alignAgentUid(name, join80(agentsDir, name), uid, {
49833
+ alignAgentUid(name, join81(agentsDir, name), uid, {
49627
49834
  confirm: !options.nonInteractive,
49628
49835
  writeOut
49629
49836
  });
@@ -49660,7 +49867,7 @@ Applying switchroom config...
49660
49867
  writeOut,
49661
49868
  writeErr,
49662
49869
  failures,
49663
- home: homedir47()
49870
+ home: homedir48()
49664
49871
  });
49665
49872
  }
49666
49873
  await ensureHostMountSources(config);
@@ -49668,7 +49875,7 @@ Applying switchroom config...
49668
49875
  for (const name of agentNames) {
49669
49876
  try {
49670
49877
  const uid = allocateAgentUid(name);
49671
- alignAgentUid(name, join80(agentsDir, name), uid, {
49878
+ alignAgentUid(name, join81(agentsDir, name), uid, {
49672
49879
  confirm: !options.nonInteractive,
49673
49880
  writeOut
49674
49881
  });
@@ -49682,7 +49889,7 @@ Applying switchroom config...
49682
49889
  }
49683
49890
  const vaultPathConfigured = config.vault?.path;
49684
49891
  const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
49685
- const migrationResult = migrateVaultLayout(homedir47(), {
49892
+ const migrationResult = migrateVaultLayout(homedir48(), {
49686
49893
  customVaultPath
49687
49894
  });
49688
49895
  switch (migrationResult.kind) {
@@ -49708,7 +49915,7 @@ Applying switchroom config...
49708
49915
  writeErr(formatDivergentRecoveryMessage(migrationResult.details));
49709
49916
  process.exit(4);
49710
49917
  }
49711
- const postMigrationInspect = inspectVaultLayout(homedir47());
49918
+ const postMigrationInspect = inspectVaultLayout(homedir48());
49712
49919
  const acceptable = [
49713
49920
  "no-vault",
49714
49921
  "already-migrated",
@@ -49723,7 +49930,7 @@ Expected one of: ${acceptable.join(", ")}
49723
49930
  `));
49724
49931
  process.exit(5);
49725
49932
  }
49726
- const vaultDir = resolveVaultBindMountDir(homedir47(), {
49933
+ const vaultDir = resolveVaultBindMountDir(homedir48(), {
49727
49934
  migrationKind: migrationResult.kind,
49728
49935
  customVaultPath
49729
49936
  });
@@ -49754,7 +49961,7 @@ vault.enc.lock (PID-file flock from saveVault), and
49754
49961
  });
49755
49962
  if (!skipScaffold) {
49756
49963
  const { provisionVoiceSidecarToken: provisionVoiceSidecarToken2 } = await Promise.resolve().then(() => (init_voice_sidecar_token(), exports_voice_sidecar_token));
49757
- await provisionVoiceSidecarToken2(composePath, homedir47(), {
49964
+ await provisionVoiceSidecarToken2(composePath, homedir48(), {
49758
49965
  writeOut,
49759
49966
  writeErr,
49760
49967
  operatorUid
@@ -49770,7 +49977,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
49770
49977
  writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
49771
49978
  `));
49772
49979
  if (process.geteuid?.() === 0 && operatorUid !== undefined) {
49773
- const restored = restoreOperatorOwnership(homedir47(), operatorUid);
49980
+ const restored = restoreOperatorOwnership(homedir48(), operatorUid);
49774
49981
  if (restored.length > 0) {
49775
49982
  writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
49776
49983
  `));
@@ -49875,7 +50082,7 @@ Dry-run: validating switchroom config (no changes will be written)...
49875
50082
  }
49876
50083
  }
49877
50084
  try {
49878
- const probe2 = await probeVaultProvisioning(config, agentNames, homedir47());
50085
+ const probe2 = await probeVaultProvisioning(config, agentNames, homedir48());
49879
50086
  if (probe2.alreadyProvisioned.length > 0) {
49880
50087
  info.push(`litellm: ${probe2.alreadyProvisioned.length} agent(s) already provisioned (${probe2.alreadyProvisioned.join(", ")}).`);
49881
50088
  }
@@ -50006,18 +50213,18 @@ function copyExampleConfig2(name) {
50006
50213
  throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
50007
50214
  }
50008
50215
  const dest = resolve50(process.cwd(), "switchroom.yaml");
50009
- if (existsSync82(dest)) {
50216
+ if (existsSync83(dest)) {
50010
50217
  console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
50011
50218
  return;
50012
50219
  }
50013
50220
  const embedded = EMBEDDED_EXAMPLES[name];
50014
50221
  if (embedded !== undefined) {
50015
- writeFileSync38(dest, embedded, { encoding: "utf8" });
50222
+ writeFileSync39(dest, embedded, { encoding: "utf8" });
50016
50223
  console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
50017
50224
  return;
50018
50225
  }
50019
50226
  const exampleFile = resolve50(import.meta.dirname, `../../examples/${name}.yaml`);
50020
- if (!existsSync82(exampleFile)) {
50227
+ if (!existsSync83(exampleFile)) {
50021
50228
  throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
50022
50229
  }
50023
50230
  copyFileSync12(exampleFile, dest);
@@ -50028,8 +50235,8 @@ function findUnwritableAgentDirs(config, opts) {
50028
50235
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
50029
50236
  const unwritable = [];
50030
50237
  for (const name of targets) {
50031
- const startSh = join80(agentsDir, name, "start.sh");
50032
- if (!existsSync82(startSh))
50238
+ const startSh = join81(agentsDir, name, "start.sh");
50239
+ if (!existsSync83(startSh))
50033
50240
  continue;
50034
50241
  try {
50035
50242
  accessSync3(startSh, fsConstants6.W_OK);
@@ -50230,7 +50437,7 @@ var init_apply = __esm(() => {
50230
50437
  switchroom: switchroom_default,
50231
50438
  minimal: minimal_default
50232
50439
  };
50233
- DEFAULT_COMPOSE_PATH2 = join80(homedir47(), ".switchroom", "compose", "docker-compose.yml");
50440
+ DEFAULT_COMPOSE_PATH2 = join81(homedir48(), ".switchroom", "compose", "docker-compose.yml");
50234
50441
  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.
50235
50442
  ` + "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.";
50236
50443
  SELF_ELEVATE_PRESERVED_ENV = [
@@ -63677,7 +63884,7 @@ __export(exports_server2, {
63677
63884
  TOOLS: () => TOOLS2
63678
63885
  });
63679
63886
  import { randomBytes as randomBytes16 } from "node:crypto";
63680
- import { existsSync as existsSync91, readFileSync as readFileSync78 } from "node:fs";
63887
+ import { existsSync as existsSync92, readFileSync as readFileSync79 } from "node:fs";
63681
63888
  function selfSocketPath() {
63682
63889
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
63683
63890
  }
@@ -63695,7 +63902,7 @@ async function dispatchTool2(name, args) {
63695
63902
  return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
63696
63903
  }
63697
63904
  const sockPath = selfSocketPath();
63698
- if (!existsSync91(sockPath)) {
63905
+ if (!existsSync92(sockPath)) {
63699
63906
  return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
63700
63907
  }
63701
63908
  let req;
@@ -63883,18 +64090,18 @@ function resolveAuditLogPath() {
63883
64090
  if (process.env.HOSTD_AUDIT_LOG_PATH)
63884
64091
  return process.env.HOSTD_AUDIT_LOG_PATH;
63885
64092
  const bindMounted = "/host-home/.switchroom/host-control-audit.log";
63886
- if (existsSync91(bindMounted))
64093
+ if (existsSync92(bindMounted))
63887
64094
  return bindMounted;
63888
64095
  return defaultAuditLogPath2();
63889
64096
  }
63890
64097
  function getLastUpdateApplyStatus() {
63891
64098
  const path8 = resolveAuditLogPath();
63892
- if (!existsSync91(path8)) {
64099
+ if (!existsSync92(path8)) {
63893
64100
  return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
63894
64101
  }
63895
64102
  let raw;
63896
64103
  try {
63897
- raw = readFileSync78(path8, "utf-8");
64104
+ raw = readFileSync79(path8, "utf-8");
63898
64105
  } catch (err2) {
63899
64106
  return errorText2(`get_status: failed to read audit log at ${path8}: ${err2.message}`);
63900
64107
  }
@@ -64561,20 +64768,20 @@ __export(exports_scan, {
64561
64768
  ledgerPathForBase: () => ledgerPathForBase
64562
64769
  });
64563
64770
  import {
64564
- readFileSync as readFileSync80,
64771
+ readFileSync as readFileSync81,
64565
64772
  readdirSync as readdirSync36,
64566
- existsSync as existsSync94,
64567
- mkdirSync as mkdirSync53,
64568
- writeFileSync as writeFileSync44
64773
+ existsSync as existsSync95,
64774
+ mkdirSync as mkdirSync54,
64775
+ writeFileSync as writeFileSync45
64569
64776
  } from "node:fs";
64570
- import { resolve as resolve55, dirname as dirname31 } from "node:path";
64571
- import { homedir as homedir56 } from "node:os";
64572
- function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir56()) {
64777
+ import { resolve as resolve55, dirname as dirname32 } from "node:path";
64778
+ import { homedir as homedir57 } from "node:os";
64779
+ function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir57()) {
64573
64780
  return resolve55(home2, ".switchroom");
64574
64781
  }
64575
64782
  function listAgents(base) {
64576
64783
  const dir = resolve55(base, "agents");
64577
- if (!existsSync94(dir))
64784
+ if (!existsSync95(dir))
64578
64785
  return [];
64579
64786
  try {
64580
64787
  return readdirSync36(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
@@ -64603,16 +64810,16 @@ function runScan(opts = {}) {
64603
64810
  let gwText = "";
64604
64811
  let sawArtifact = false;
64605
64812
  try {
64606
- if (existsSync94(turnsPath)) {
64607
- turnsText = readFileSync80(turnsPath, "utf-8");
64813
+ if (existsSync95(turnsPath)) {
64814
+ turnsText = readFileSync81(turnsPath, "utf-8");
64608
64815
  sawArtifact = true;
64609
64816
  }
64610
64817
  } catch (e) {
64611
64818
  log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
64612
64819
  }
64613
64820
  try {
64614
- if (existsSync94(gwPath)) {
64615
- gwText = readFileSync80(gwPath, "utf-8");
64821
+ if (existsSync95(gwPath)) {
64822
+ gwText = readFileSync81(gwPath, "utf-8");
64616
64823
  sawArtifact = true;
64617
64824
  }
64618
64825
  } catch (e) {
@@ -64650,20 +64857,20 @@ function runScan(opts = {}) {
64650
64857
  function readLedgerIfPresent(base) {
64651
64858
  const path8 = ledgerPathForBase(base);
64652
64859
  try {
64653
- if (!existsSync94(path8))
64860
+ if (!existsSync95(path8))
64654
64861
  return null;
64655
- return JSON.parse(readFileSync80(path8, "utf-8"));
64862
+ return JSON.parse(readFileSync81(path8, "utf-8"));
64656
64863
  } catch {
64657
64864
  return null;
64658
64865
  }
64659
64866
  }
64660
64867
  function ledgerPathForBase(base) {
64661
- return fleetHealthLedgerPath(dirname31(base));
64868
+ return fleetHealthLedgerPath(dirname32(base));
64662
64869
  }
64663
64870
  function writeLedger(base, ledger) {
64664
64871
  const path8 = ledgerPathForBase(base);
64665
- mkdirSync53(dirname31(path8), { recursive: true });
64666
- writeFileSync44(path8, JSON.stringify(ledger, null, 2) + `
64872
+ mkdirSync54(dirname32(path8), { recursive: true });
64873
+ writeFileSync45(path8, JSON.stringify(ledger, null, 2) + `
64667
64874
  `, "utf-8");
64668
64875
  return path8;
64669
64876
  }
@@ -64682,8 +64889,8 @@ import { existsSync, readFileSync } from "node:fs";
64682
64889
  import { dirname, join } from "node:path";
64683
64890
 
64684
64891
  // src/build-info.ts
64685
- var VERSION = "0.18.6";
64686
- var COMMIT_SHA = "b62d558a";
64892
+ var VERSION = "0.18.8";
64893
+ var COMMIT_SHA = "9255fe22";
64687
64894
 
64688
64895
  // src/cli/resolve-version.ts
64689
64896
  function readPackageVersion() {
@@ -78919,6 +79126,8 @@ Demoting memory ${source_default.cyan(memoryId)}`));
78919
79126
 
78920
79127
  // src/cli/web.ts
78921
79128
  init_source();
79129
+ import { join as join50 } from "node:path";
79130
+ import { homedir as homedir28 } from "node:os";
78922
79131
 
78923
79132
  // src/web/server.ts
78924
79133
  init_merge();
@@ -79554,7 +79763,8 @@ var SUBAGENTS_SCHEMA_SQL = `
79554
79763
  status TEXT NOT NULL,
79555
79764
  result_summary TEXT,
79556
79765
  jsonl_agent_id TEXT,
79557
- parent_agent_id TEXT
79766
+ parent_agent_id TEXT,
79767
+ model TEXT
79558
79768
  );
79559
79769
  CREATE INDEX IF NOT EXISTS subagents_turn ON subagents(parent_turn_key);
79560
79770
  CREATE INDEX IF NOT EXISTS subagents_status ON subagents(status);
@@ -79570,6 +79780,10 @@ function applySubagentsSchema(db) {
79570
79780
  if (!hasParentAgentId) {
79571
79781
  db.exec("ALTER TABLE subagents ADD COLUMN parent_agent_id TEXT");
79572
79782
  }
79783
+ const hasModel = cols.some((c) => c.name === "model");
79784
+ if (!hasModel) {
79785
+ db.exec("ALTER TABLE subagents ADD COLUMN model TEXT");
79786
+ }
79573
79787
  db.exec("CREATE INDEX IF NOT EXISTS subagents_jsonl_id ON subagents(jsonl_agent_id)");
79574
79788
  }
79575
79789
  function mapSubagentRow(row) {
@@ -79586,7 +79800,8 @@ function mapSubagentRow(row) {
79586
79800
  status: row.status,
79587
79801
  result_summary: row.result_summary,
79588
79802
  jsonl_agent_id: row.jsonl_agent_id,
79589
- parent_agent_id: row.parent_agent_id ?? null
79803
+ parent_agent_id: row.parent_agent_id ?? null,
79804
+ model: row.model ?? null
79590
79805
  };
79591
79806
  }
79592
79807
  function listSubagents(db, opts = {}) {
@@ -82913,10 +83128,100 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
82913
83128
 
82914
83129
  // src/cli/web.ts
82915
83130
  init_helpers();
83131
+ init_loader();
83132
+
83133
+ // src/web/startup-guard.ts
83134
+ import { existsSync as existsSync56, readFileSync as readFileSync51, writeFileSync as writeFileSync26, mkdirSync as mkdirSync31, statSync as statSync24, unlinkSync as unlinkSync13 } from "node:fs";
83135
+ import { dirname as dirname16 } from "node:path";
83136
+ function detectConfigMountFault(configPath, deps = {}) {
83137
+ const stat = deps.stat ?? ((p) => statSync24(p));
83138
+ let st;
83139
+ try {
83140
+ st = stat(configPath);
83141
+ } catch {
83142
+ return null;
83143
+ }
83144
+ if (!st.isDirectory())
83145
+ return null;
83146
+ return {
83147
+ kind: "directory-inode",
83148
+ path: configPath,
83149
+ message: `switchroom-web: the config bind source ${configPath} resolved to a DIRECTORY, ` + `not a file \u2014 reads fail with EISDIR and the server cannot start.
83150
+
83151
+ ` + `Cause: the single-file bind mount was (re)created while the host source was ` + `momentarily absent, so Docker auto-created an empty directory, OR a stale ` + `directory inode survived a plain \`docker restart\` inside the container's mount ` + `namespace (a restart does NOT re-resolve a single-file bind).
83152
+
83153
+ ` + `Remedy: RECREATE the container (not restart) so the bind re-resolves to the file:
83154
+ ` + ` docker compose -p switchroom-web -f ~/.switchroom/web/docker-compose.yml up -d --force-recreate
83155
+ ` + `and if the host source itself is a directory, remove it and restore the file first.`
83156
+ };
83157
+ }
83158
+ function nextCrashBackoff(prior, now, options = {}) {
83159
+ const baseMs = options.baseMs ?? 1000;
83160
+ const maxMs = options.maxMs ?? 60000;
83161
+ const resetAfterMs = options.resetAfterMs ?? 300000;
83162
+ const withinWindow = prior !== null && now - prior.lastTs <= resetAfterMs && now >= prior.lastTs;
83163
+ const count = withinWindow ? prior.count + 1 : 1;
83164
+ const exp = Math.min(count - 1, 30);
83165
+ const delayMs = count <= 1 ? 0 : Math.min(baseMs * 2 ** (exp - 1), maxMs);
83166
+ return { state: { count, lastTs: now }, delayMs };
83167
+ }
83168
+ function readCrashState(statePath) {
83169
+ try {
83170
+ if (!existsSync56(statePath))
83171
+ return null;
83172
+ const raw = JSON.parse(readFileSync51(statePath, "utf-8"));
83173
+ if (typeof raw.count !== "number" || typeof raw.lastTs !== "number")
83174
+ return null;
83175
+ return { count: raw.count, lastTs: raw.lastTs };
83176
+ } catch {
83177
+ return null;
83178
+ }
83179
+ }
83180
+ function writeCrashState(statePath, state) {
83181
+ try {
83182
+ mkdirSync31(dirname16(statePath), { recursive: true });
83183
+ writeFileSync26(statePath, JSON.stringify(state), { mode: 384 });
83184
+ } catch {}
83185
+ }
83186
+ function clearCrashState(statePath) {
83187
+ try {
83188
+ if (existsSync56(statePath))
83189
+ unlinkSync13(statePath);
83190
+ } catch {}
83191
+ }
83192
+
83193
+ // src/cli/web.ts
82916
83194
  init_posthog();
83195
+ async function handleWebStartupFailure(err, configPath, stateFile) {
83196
+ if (err instanceof ConfigError && configPath) {
83197
+ const fault = detectConfigMountFault(configPath);
83198
+ if (fault) {
83199
+ console.error(source_default.red(fault.message));
83200
+ }
83201
+ }
83202
+ const { state, delayMs } = nextCrashBackoff(readCrashState(stateFile), Date.now());
83203
+ writeCrashState(stateFile, state);
83204
+ if (delayMs > 0) {
83205
+ console.error(source_default.yellow(`switchroom-web: startup has failed ${state.count}\u00d7 in a row \u2014 backing off ` + `${Math.round(delayMs / 1000)}s before exit so \`restart: always\` doesn't ` + `crash-loop the host (issue #2915).`));
83206
+ await new Promise((r) => setTimeout(r, delayMs));
83207
+ }
83208
+ throw err;
83209
+ }
83210
+ function webCrashStatePath() {
83211
+ return join50(homedir28(), ".switchroom", "web", ".crashloop-state.json");
83212
+ }
82917
83213
  function registerWebCommand(program3) {
82918
83214
  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) => {
82919
- const config = getConfig(program3);
83215
+ const configPath = getConfigPath(program3);
83216
+ const stateFile = webCrashStatePath();
83217
+ let config;
83218
+ try {
83219
+ config = getConfig(program3);
83220
+ } catch (err) {
83221
+ await handleWebStartupFailure(err, configPath, stateFile);
83222
+ throw err;
83223
+ }
83224
+ clearCrashState(stateFile);
82920
83225
  const port = parseInt(opts.port, 10);
82921
83226
  const hostname = opts.bind;
82922
83227
  if (isNaN(port) || port < 1 || port > 65535) {
@@ -82968,8 +83273,8 @@ Starting Switchroom dashboard...
82968
83273
  init_source();
82969
83274
  init_loader();
82970
83275
  init_scaffold();
82971
- import { existsSync as existsSync56, copyFileSync as copyFileSync9, readFileSync as readFileSync51, writeFileSync as writeFileSync26, mkdirSync as mkdirSync31 } from "node:fs";
82972
- import { resolve as resolve33, dirname as dirname16 } from "node:path";
83276
+ import { existsSync as existsSync57, copyFileSync as copyFileSync9, readFileSync as readFileSync52, writeFileSync as writeFileSync27, mkdirSync as mkdirSync32 } from "node:fs";
83277
+ import { resolve as resolve33, dirname as dirname17 } from "node:path";
82973
83278
  init_state();
82974
83279
  init_vault();
82975
83280
  init_manager();
@@ -83239,7 +83544,7 @@ async function stepConfigFile(configPath, nonInteractive) {
83239
83544
  existingConfig = null;
83240
83545
  }
83241
83546
  }
83242
- if (existingConfig && existsSync56(existingConfig)) {
83547
+ if (existingConfig && existsSync57(existingConfig)) {
83243
83548
  if (!nonInteractive) {
83244
83549
  const useExisting = await askYesNo(` Found ${source_default.cyan(existingConfig)}. Use it?`, true);
83245
83550
  if (!useExisting) {
@@ -83271,10 +83576,10 @@ async function copyExampleConfig(nonInteractive) {
83271
83576
  }
83272
83577
  const srcFile = resolve33(examplesDir, `${choice}.yaml`);
83273
83578
  const destFile = resolvePath("~/.switchroom/switchroom.yaml");
83274
- if (!existsSync56(srcFile)) {
83579
+ if (!existsSync57(srcFile)) {
83275
83580
  throw new ConfigError(`Example config not found: ${choice}.yaml`);
83276
83581
  }
83277
- mkdirSync31(dirname16(destFile), { recursive: true });
83582
+ mkdirSync32(dirname17(destFile), { recursive: true });
83278
83583
  copyFileSync9(srcFile, destFile);
83279
83584
  console.log(source_default.green(` Copied ${choice}.yaml -> ${destFile}`));
83280
83585
  console.log(source_default.yellow(` Edit ${destFile} to customize, then re-run switchroom setup.`));
@@ -83286,10 +83591,10 @@ async function copyExampleConfig(nonInteractive) {
83286
83591
  async function writeDetectedTimezone(destFile, nonInteractive, detect = detectServerTimezone, prompt = ask) {
83287
83592
  const detected = detect();
83288
83593
  if (detected !== undefined && detected !== "UTC" && isValidTimezone(detected)) {
83289
- const before2 = readFileSync51(destFile, "utf-8");
83594
+ const before2 = readFileSync52(destFile, "utf-8");
83290
83595
  const after2 = setSwitchroomTimezone(before2, detected);
83291
83596
  if (after2 !== before2)
83292
- writeFileSync26(destFile, after2);
83597
+ writeFileSync27(destFile, after2);
83293
83598
  console.log(source_default.green(` Detected timezone ${detected} \u2014 wrote switchroom.timezone.`));
83294
83599
  return;
83295
83600
  }
@@ -83307,10 +83612,10 @@ async function writeDetectedTimezone(destFile, nonInteractive, detect = detectSe
83307
83612
  console.error(source_default.yellow(` \u26a0 "${zone}" is not a valid IANA zone (expected Region/City like ` + '"Australia/Melbourne"). Skipping \u2014 agents will use UTC. ' + "Edit switchroom.timezone in switchroom.yaml by hand."));
83308
83613
  return;
83309
83614
  }
83310
- const before = readFileSync51(destFile, "utf-8");
83615
+ const before = readFileSync52(destFile, "utf-8");
83311
83616
  const after = setSwitchroomTimezone(before, zone);
83312
83617
  if (after !== before)
83313
- writeFileSync26(destFile, after);
83618
+ writeFileSync27(destFile, after);
83314
83619
  console.log(source_default.green(` Wrote switchroom.timezone: ${zone}.`));
83315
83620
  }
83316
83621
  async function stepBotToken(config, nonInteractive) {
@@ -83386,7 +83691,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
83386
83691
  try {
83387
83692
  const { openVault: openVault2 } = await Promise.resolve().then(() => (init_vault(), exports_vault));
83388
83693
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
83389
- if (existsSync56(vaultPath)) {
83694
+ if (existsSync57(vaultPath)) {
83390
83695
  const secrets = openVault2(passphrase, vaultPath);
83391
83696
  const key = rawToken.replace("vault:", "");
83392
83697
  const entry = secrets[key];
@@ -83414,7 +83719,7 @@ async function resolveOrPromptToken(rawToken, label, config, nonInteractive) {
83414
83719
  async function storeTokenInVault(config, vaultRef, token) {
83415
83720
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
83416
83721
  const key = vaultRef.replace("vault:", "");
83417
- if (!existsSync56(vaultPath)) {
83722
+ if (!existsSync57(vaultPath)) {
83418
83723
  console.log(source_default.gray(" Creating encrypted vault..."));
83419
83724
  let passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
83420
83725
  if (!passphrase) {
@@ -83600,7 +83905,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath) {
83600
83905
  try {
83601
83906
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
83602
83907
  const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
83603
- if (passphrase && existsSync56(vaultPath)) {
83908
+ if (passphrase && existsSync57(vaultPath)) {
83604
83909
  const existing = getStringSecret(passphrase, vaultPath, "hindsight-api-key");
83605
83910
  if (existing) {
83606
83911
  console.log(source_default.gray(" Note: legacy 'hindsight-api-key' is in your vault but is no longer used. You can remove it with `switchroom vault rm hindsight-api-key`."));
@@ -83816,13 +84121,13 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
83816
84121
  return;
83817
84122
  }
83818
84123
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
83819
- if (!existsSync56(vaultPath)) {
84124
+ if (!existsSync57(vaultPath)) {
83820
84125
  console.log(source_default.gray(" Skipping (vault not created yet)."));
83821
84126
  return;
83822
84127
  }
83823
84128
  const credPathRaw = config.vault?.broker?.autoUnlockCredentialPath ?? "~/.switchroom/vault-auto-unlock";
83824
84129
  const credPath = resolvePath(credPathRaw);
83825
- if (config.vault?.broker?.autoUnlock === true && existsSync56(credPath)) {
84130
+ if (config.vault?.broker?.autoUnlock === true && existsSync57(credPath)) {
83826
84131
  console.log(source_default.green(` ${STEP_DONE} Already configured (${credPath})`));
83827
84132
  return;
83828
84133
  }
@@ -83888,12 +84193,12 @@ async function stepAutoUnlock(config, switchroomConfigPath, nonInteractive) {
83888
84193
  const choice = await askChoice(" Approval posture", [PASSPHRASE_CHOICE, TELEGRAM_ID_CHOICE]);
83889
84194
  if (choice === TELEGRAM_ID_CHOICE) {
83890
84195
  try {
83891
- const yamlPath = existsSync56(resolve33(process.cwd(), "switchroom.yaml")) ? resolve33(process.cwd(), "switchroom.yaml") : resolve33(process.cwd(), "switchroom.yml");
83892
- if (existsSync56(yamlPath)) {
83893
- const content = readFileSync51(yamlPath, "utf-8");
84196
+ const yamlPath = existsSync57(resolve33(process.cwd(), "switchroom.yaml")) ? resolve33(process.cwd(), "switchroom.yaml") : resolve33(process.cwd(), "switchroom.yml");
84197
+ if (existsSync57(yamlPath)) {
84198
+ const content = readFileSync52(yamlPath, "utf-8");
83894
84199
  const result = insertVaultBrokerApprovalAuth(content, "telegram-id");
83895
84200
  if (result.kind === "rewritten") {
83896
- writeFileSync26(yamlPath, result.content, "utf-8");
84201
+ writeFileSync27(yamlPath, result.content, "utf-8");
83897
84202
  console.log(source_default.green(` ${STEP_DONE} Set vault.broker.approvalAuth: telegram-id in ${yamlPath}`));
83898
84203
  } else if (result.kind === "already-set") {
83899
84204
  console.log(source_default.gray(" approvalAuth already set \u2014 leaving it alone."));
@@ -83924,8 +84229,8 @@ async function stepDangerousMode(config, nonInteractive) {
83924
84229
  resolve33(process.cwd(), "switchroom.yml")
83925
84230
  ];
83926
84231
  for (const configPath of configPaths) {
83927
- if (existsSync56(configPath)) {
83928
- let content = readFileSync51(configPath, "utf-8");
84232
+ if (existsSync57(configPath)) {
84233
+ let content = readFileSync52(configPath, "utf-8");
83929
84234
  const agentNames = Object.keys(config.agents);
83930
84235
  for (const name of agentNames) {
83931
84236
  const agentPattern = new RegExp(`(^ ${name}:\\s*\\n)`, "m");
@@ -83939,7 +84244,7 @@ async function stepDangerousMode(config, nonInteractive) {
83939
84244
  }
83940
84245
  config.agents[name].dangerous_mode = true;
83941
84246
  }
83942
- writeFileSync26(configPath, content, "utf-8");
84247
+ writeFileSync27(configPath, content, "utf-8");
83943
84248
  console.log(source_default.green(` ${STEP_DONE} Enabled dangerous_mode for all agents in ${configPath}`));
83944
84249
  break;
83945
84250
  }
@@ -84055,10 +84360,10 @@ init_source();
84055
84360
  init_loader();
84056
84361
  init_lifecycle();
84057
84362
  init_compose_env();
84058
- import { cpSync as cpSync2, existsSync as existsSync63, mkdirSync as mkdirSync33, readFileSync as readFileSync56, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync28, chownSync as chownSync5 } from "node:fs";
84363
+ import { cpSync as cpSync2, existsSync as existsSync64, mkdirSync as mkdirSync34, readFileSync as readFileSync57, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync29, chownSync as chownSync5 } from "node:fs";
84059
84364
  import { spawnSync as spawnSync12 } from "node:child_process";
84060
- import { join as join63, dirname as dirname19, resolve as resolve38 } from "node:path";
84061
- import { homedir as homedir38 } from "node:os";
84365
+ import { join as join64, dirname as dirname20, resolve as resolve38 } from "node:path";
84366
+ import { homedir as homedir39 } from "node:os";
84062
84367
 
84063
84368
  // src/cli/release-yaml.ts
84064
84369
  var import_yaml19 = __toESM(require_dist(), 1);
@@ -84083,7 +84388,7 @@ init_operator_uid();
84083
84388
  init_atomic();
84084
84389
 
84085
84390
  // src/cli/preflight-mounts.ts
84086
- import { statSync as statSync27 } from "node:fs";
84391
+ import { statSync as statSync28 } from "node:fs";
84087
84392
  function parseHostBindSources(composeText) {
84088
84393
  const out = [];
84089
84394
  const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
@@ -84110,7 +84415,7 @@ function parseHostBindSources(composeText) {
84110
84415
  return out;
84111
84416
  }
84112
84417
  function validateBindSources(composeText, deps = {}) {
84113
- const stat = deps.stat ?? ((p) => statSync27(p));
84418
+ const stat = deps.stat ?? ((p) => statSync28(p));
84114
84419
  const sources = parseHostBindSources(composeText);
84115
84420
  const issues = [];
84116
84421
  const seen = new Set;
@@ -84150,11 +84455,11 @@ ${lines.join(`
84150
84455
  function defaultPersistPin(configPath) {
84151
84456
  return (pin) => {
84152
84457
  const path4 = configPath ?? findConfigFile();
84153
- const before = readFileSync56(path4, "utf8");
84458
+ const before = readFileSync57(path4, "utf8");
84154
84459
  const after = setReleasePinInConfig(before, pin);
84155
84460
  if (after === before)
84156
84461
  return;
84157
- writeConfigFileSync(path4, after, statSync28(path4).mode & 511);
84462
+ writeConfigFileSync(path4, after, statSync29(path4).mode & 511);
84158
84463
  try {
84159
84464
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
84160
84465
  const uid = resolveOperatorUid();
@@ -84164,18 +84469,18 @@ function defaultPersistPin(configPath) {
84164
84469
  } catch {}
84165
84470
  };
84166
84471
  }
84167
- var DEFAULT_COMPOSE_PATH = join63(homedir38(), ".switchroom", "compose", "docker-compose.yml");
84472
+ var DEFAULT_COMPOSE_PATH = join64(homedir39(), ".switchroom", "compose", "docker-compose.yml");
84168
84473
  function runningFromSwitchroomCheckout(scriptPath) {
84169
- let dir = dirname19(scriptPath);
84474
+ let dir = dirname20(scriptPath);
84170
84475
  for (let i = 0;i < 12; i++) {
84171
- if (existsSync63(join63(dir, ".git"))) {
84476
+ if (existsSync64(join64(dir, ".git"))) {
84172
84477
  try {
84173
- const pkg = JSON.parse(readFileSync56(join63(dir, "package.json"), "utf-8"));
84478
+ const pkg = JSON.parse(readFileSync57(join64(dir, "package.json"), "utf-8"));
84174
84479
  if (pkg.name === "switchroom")
84175
84480
  return true;
84176
84481
  } catch {}
84177
84482
  }
84178
- const parent = dirname19(dir);
84483
+ const parent = dirname20(dir);
84179
84484
  if (parent === dir)
84180
84485
  break;
84181
84486
  dir = parent;
@@ -84255,7 +84560,7 @@ function planUpdate(opts) {
84255
84560
  steps.push({
84256
84561
  name: "pull-images",
84257
84562
  description: "Pull broker / kernel / agent images from GHCR",
84258
- skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync63(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
84563
+ skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync64(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
84259
84564
  run: () => {
84260
84565
  const r = runner("docker", [
84261
84566
  "compose",
@@ -84381,17 +84686,17 @@ function planUpdate(opts) {
84381
84686
  return;
84382
84687
  }
84383
84688
  const source = resolve38(import.meta.dirname, "../../skills");
84384
- const dest = join63(homedir38(), ".switchroom", "skills", "_bundled");
84385
- if (!existsSync63(source)) {
84689
+ const dest = join64(homedir39(), ".switchroom", "skills", "_bundled");
84690
+ if (!existsSync64(source)) {
84386
84691
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
84387
84692
  `);
84388
84693
  return;
84389
84694
  }
84390
84695
  try {
84391
- if (existsSync63(dest)) {
84696
+ if (existsSync64(dest)) {
84392
84697
  rmSync12(dest, { recursive: true, force: true });
84393
84698
  }
84394
- mkdirSync33(dirname19(dest), { recursive: true });
84699
+ mkdirSync34(dirname20(dest), { recursive: true });
84395
84700
  cpSync2(source, dest, { recursive: true, dereference: false });
84396
84701
  } catch (err) {
84397
84702
  throw new Error(`sync-bundled-skills failed: ${err.message}`);
@@ -84427,7 +84732,7 @@ function planUpdate(opts) {
84427
84732
  description: "docker compose up -d --remove-orphans (recreates services with new images / compose)",
84428
84733
  run: () => {
84429
84734
  try {
84430
- const composeText = readFileSync56(composePath, "utf8");
84735
+ const composeText = readFileSync57(composePath, "utf8");
84431
84736
  const pf = validateBindSources(composeText);
84432
84737
  if (!pf.ok)
84433
84738
  throw new Error(formatPreflightError(pf));
@@ -84502,14 +84807,14 @@ function defaultStatusProbe(composePath) {
84502
84807
  } catch {}
84503
84808
  if (scriptPath) {
84504
84809
  try {
84505
- cliBuiltAt = new Date(statSync28(scriptPath).mtimeMs).toISOString();
84810
+ cliBuiltAt = new Date(statSync29(scriptPath).mtimeMs).toISOString();
84506
84811
  } catch {}
84507
- let dir = dirname19(scriptPath);
84812
+ let dir = dirname20(scriptPath);
84508
84813
  for (let i = 0;i < 8; i++) {
84509
- const pkgPath = join63(dir, "package.json");
84510
- if (existsSync63(pkgPath)) {
84814
+ const pkgPath = join64(dir, "package.json");
84815
+ if (existsSync64(pkgPath)) {
84511
84816
  try {
84512
- const pkg = JSON.parse(readFileSync56(pkgPath, "utf-8"));
84817
+ const pkg = JSON.parse(readFileSync57(pkgPath, "utf-8"));
84513
84818
  if (typeof pkg.version === "string")
84514
84819
  cliVersion = pkg.version;
84515
84820
  } catch (err) {
@@ -84517,7 +84822,7 @@ function defaultStatusProbe(composePath) {
84517
84822
  }
84518
84823
  break;
84519
84824
  }
84520
- const parent = dirname19(dir);
84825
+ const parent = dirname20(dir);
84521
84826
  if (parent === dir)
84522
84827
  break;
84523
84828
  dir = parent;
@@ -84530,7 +84835,7 @@ function defaultStatusProbe(composePath) {
84530
84835
  warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
84531
84836
  }
84532
84837
  const services = [];
84533
- if (!existsSync63(composePath)) {
84838
+ if (!existsSync64(composePath)) {
84534
84839
  warnings.push(`compose file not found at ${composePath}; service status unknown`);
84535
84840
  return { cliVersion, cliBuiltAt, services, warnings };
84536
84841
  }
@@ -84726,8 +85031,8 @@ function registerUpdateCommand(program3) {
84726
85031
  // src/cli/rollout.ts
84727
85032
  init_helpers();
84728
85033
  import { spawnSync as spawnSync13 } from "node:child_process";
84729
- import { readFileSync as readFileSync57, chownSync as chownSync6, statSync as statSync29 } from "node:fs";
84730
- import { homedir as homedir39 } from "node:os";
85034
+ import { readFileSync as readFileSync58, chownSync as chownSync6, statSync as statSync30 } from "node:fs";
85035
+ import { homedir as homedir40 } from "node:os";
84731
85036
  init_operator_uid();
84732
85037
  init_atomic();
84733
85038
  init_audit_reader();
@@ -84956,10 +85261,10 @@ function executeRollout(steps, target, deps, execOpts = {}) {
84956
85261
  return { ok: true, rolled, warnings };
84957
85262
  }
84958
85263
  function resolveRollbackTarget(auditLogPath) {
84959
- const logPath = auditLogPath ?? defaultAuditLogPath2(homedir39());
85264
+ const logPath = auditLogPath ?? defaultAuditLogPath2(homedir40());
84960
85265
  let raw;
84961
85266
  try {
84962
- raw = readFileSync57(logPath, "utf8");
85267
+ raw = readFileSync58(logPath, "utf8");
84963
85268
  } catch {
84964
85269
  return null;
84965
85270
  }
@@ -85073,11 +85378,11 @@ function registerRolloutCommand(program3) {
85073
85378
  emitPhase: (phase) => process.stdout.write(encodeRolloutPhaseLine(phase) + `
85074
85379
  `),
85075
85380
  persistPin: (pin) => {
85076
- const before = readFileSync57(configPath, "utf8");
85381
+ const before = readFileSync58(configPath, "utf8");
85077
85382
  const after = setReleasePinInConfig(before, pin);
85078
85383
  if (after === before)
85079
85384
  return;
85080
- writeConfigFileSync(configPath, after, statSync29(configPath).mode & 511);
85385
+ writeConfigFileSync(configPath, after, statSync30(configPath).mode & 511);
85081
85386
  try {
85082
85387
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
85083
85388
  const uid = resolveOperatorUid();
@@ -85147,8 +85452,8 @@ init_source();
85147
85452
  init_helpers();
85148
85453
  init_lifecycle();
85149
85454
  import { execSync as execSync4 } from "node:child_process";
85150
- import { existsSync as existsSync64, readFileSync as readFileSync58 } from "node:fs";
85151
- import { dirname as dirname20, join as join64 } from "node:path";
85455
+ import { existsSync as existsSync65, readFileSync as readFileSync59 } from "node:fs";
85456
+ import { dirname as dirname21, join as join65 } from "node:path";
85152
85457
  function getClaudeCodeVersion() {
85153
85458
  try {
85154
85459
  const out = execSync4("claude --version 2>/dev/null", {
@@ -85198,16 +85503,16 @@ function formatUptime3(timestamp) {
85198
85503
  function locateSwitchroomInstallDir() {
85199
85504
  let dir = import.meta.dirname;
85200
85505
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
85201
- const pkgPath = join64(dir, "package.json");
85202
- if (existsSync64(pkgPath)) {
85506
+ const pkgPath = join65(dir, "package.json");
85507
+ if (existsSync65(pkgPath)) {
85203
85508
  try {
85204
- const pkg = JSON.parse(readFileSync58(pkgPath, "utf-8"));
85205
- if (pkg.name === "switchroom" && existsSync64(join64(dir, ".git"))) {
85509
+ const pkg = JSON.parse(readFileSync59(pkgPath, "utf-8"));
85510
+ if (pkg.name === "switchroom" && existsSync65(join65(dir, ".git"))) {
85206
85511
  return dir;
85207
85512
  }
85208
85513
  } catch {}
85209
85514
  }
85210
- dir = dirname20(dir);
85515
+ dir = dirname21(dir);
85211
85516
  }
85212
85517
  return null;
85213
85518
  }
@@ -85380,18 +85685,18 @@ import { resolve as resolve40 } from "node:path";
85380
85685
 
85381
85686
  // src/agents/session-retention.ts
85382
85687
  import {
85383
- existsSync as existsSync65,
85688
+ existsSync as existsSync66,
85384
85689
  readdirSync as readdirSync22,
85385
- statSync as statSync30,
85386
- unlinkSync as unlinkSync13
85690
+ statSync as statSync31,
85691
+ unlinkSync as unlinkSync14
85387
85692
  } from "node:fs";
85388
- import { join as join65 } from "node:path";
85693
+ import { join as join66 } from "node:path";
85389
85694
  var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
85390
85695
  var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
85391
85696
  var MIN_KEEP = 2;
85392
85697
  function collectSessionJsonl(claudeConfigDir) {
85393
- const projects = join65(claudeConfigDir, "projects");
85394
- if (!existsSync65(projects))
85698
+ const projects = join66(claudeConfigDir, "projects");
85699
+ if (!existsSync66(projects))
85395
85700
  return [];
85396
85701
  const found = [];
85397
85702
  const walk2 = (dir) => {
@@ -85402,10 +85707,10 @@ function collectSessionJsonl(claudeConfigDir) {
85402
85707
  return;
85403
85708
  }
85404
85709
  for (const name of entries) {
85405
- const full = join65(dir, name);
85710
+ const full = join66(dir, name);
85406
85711
  let st;
85407
85712
  try {
85408
- st = statSync30(full);
85713
+ st = statSync31(full);
85409
85714
  } catch {
85410
85715
  continue;
85411
85716
  }
@@ -85452,7 +85757,7 @@ function pruneSessionJsonl(claudeConfigDir, opts = {}) {
85452
85757
  if (!eligible)
85453
85758
  continue;
85454
85759
  try {
85455
- unlinkSync13(f.path);
85760
+ unlinkSync14(f.path);
85456
85761
  deleted++;
85457
85762
  } catch (err) {
85458
85763
  onWarn(`session-retention: could not delete ${f.path}: ${err.message}`);
@@ -85522,18 +85827,18 @@ function registerHandoffCommand(program3) {
85522
85827
  // src/issues/store.ts
85523
85828
  import {
85524
85829
  closeSync as closeSync12,
85525
- existsSync as existsSync66,
85526
- mkdirSync as mkdirSync34,
85830
+ existsSync as existsSync67,
85831
+ mkdirSync as mkdirSync35,
85527
85832
  openSync as openSync12,
85528
85833
  readdirSync as readdirSync23,
85529
- readFileSync as readFileSync59,
85834
+ readFileSync as readFileSync60,
85530
85835
  renameSync as renameSync15,
85531
- statSync as statSync31,
85532
- unlinkSync as unlinkSync14,
85533
- writeFileSync as writeFileSync27,
85836
+ statSync as statSync32,
85837
+ unlinkSync as unlinkSync15,
85838
+ writeFileSync as writeFileSync28,
85534
85839
  writeSync as writeSync9
85535
85840
  } from "node:fs";
85536
- import { join as join66 } from "node:path";
85841
+ import { join as join67 } from "node:path";
85537
85842
  import { randomBytes as randomBytes12 } from "node:crypto";
85538
85843
  import { execSync as execSync5 } from "node:child_process";
85539
85844
 
@@ -85931,12 +86236,12 @@ function redactedMarker(ruleId) {
85931
86236
  var ISSUES_FILE = "issues.jsonl";
85932
86237
  var ISSUES_LOCK = "issues.lock";
85933
86238
  function readAll(stateDir) {
85934
- const path4 = join66(stateDir, ISSUES_FILE);
85935
- if (!existsSync66(path4))
86239
+ const path4 = join67(stateDir, ISSUES_FILE);
86240
+ if (!existsSync67(path4))
85936
86241
  return [];
85937
86242
  let raw;
85938
86243
  try {
85939
- raw = readFileSync59(path4, "utf-8");
86244
+ raw = readFileSync60(path4, "utf-8");
85940
86245
  } catch {
85941
86246
  return [];
85942
86247
  }
@@ -86009,7 +86314,7 @@ function record(stateDir, input, nowFn = Date.now) {
86009
86314
  });
86010
86315
  }
86011
86316
  function resolve41(stateDir, fingerprint, nowFn = Date.now) {
86012
- if (!existsSync66(join66(stateDir, ISSUES_FILE)))
86317
+ if (!existsSync67(join67(stateDir, ISSUES_FILE)))
86013
86318
  return 0;
86014
86319
  return withLock(stateDir, () => {
86015
86320
  const all = readAll(stateDir);
@@ -86027,7 +86332,7 @@ function resolve41(stateDir, fingerprint, nowFn = Date.now) {
86027
86332
  });
86028
86333
  }
86029
86334
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
86030
- if (!existsSync66(join66(stateDir, ISSUES_FILE)))
86335
+ if (!existsSync67(join67(stateDir, ISSUES_FILE)))
86031
86336
  return 0;
86032
86337
  return withLock(stateDir, () => {
86033
86338
  const all = readAll(stateDir);
@@ -86045,7 +86350,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
86045
86350
  });
86046
86351
  }
86047
86352
  function prune(stateDir, opts = {}) {
86048
- if (!existsSync66(join66(stateDir, ISSUES_FILE)))
86353
+ if (!existsSync67(join67(stateDir, ISSUES_FILE)))
86049
86354
  return 0;
86050
86355
  return withLock(stateDir, () => {
86051
86356
  const all = readAll(stateDir);
@@ -86075,16 +86380,16 @@ function prune(stateDir, opts = {}) {
86075
86380
  });
86076
86381
  }
86077
86382
  function ensureDir(stateDir) {
86078
- mkdirSync34(stateDir, { recursive: true });
86383
+ mkdirSync35(stateDir, { recursive: true });
86079
86384
  }
86080
86385
  function writeAll(stateDir, events) {
86081
- const path4 = join66(stateDir, ISSUES_FILE);
86386
+ const path4 = join67(stateDir, ISSUES_FILE);
86082
86387
  sweepOrphanTmpFiles(stateDir);
86083
86388
  const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
86084
86389
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
86085
86390
  `) + `
86086
86391
  `;
86087
- writeFileSync27(tmp, body, "utf-8");
86392
+ writeFileSync28(tmp, body, "utf-8");
86088
86393
  renameSync15(tmp, path4);
86089
86394
  }
86090
86395
  var ORPHAN_TMP_TTL_MS = 60000;
@@ -86100,11 +86405,11 @@ function sweepOrphanTmpFiles(stateDir) {
86100
86405
  for (const entry of entries) {
86101
86406
  if (!entry.startsWith(TMP_PREFIX))
86102
86407
  continue;
86103
- const tmpPath = join66(stateDir, entry);
86408
+ const tmpPath = join67(stateDir, entry);
86104
86409
  try {
86105
- const stat = statSync31(tmpPath);
86410
+ const stat = statSync32(tmpPath);
86106
86411
  if (stat.mtimeMs < cutoff) {
86107
- unlinkSync14(tmpPath);
86412
+ unlinkSync15(tmpPath);
86108
86413
  }
86109
86414
  } catch {}
86110
86415
  }
@@ -86112,7 +86417,7 @@ function sweepOrphanTmpFiles(stateDir) {
86112
86417
  var LOCK_RETRY_MS = 25;
86113
86418
  var LOCK_TIMEOUT_MS = 1e4;
86114
86419
  function withLock(stateDir, fn) {
86115
- const lockPath = join66(stateDir, ISSUES_LOCK);
86420
+ const lockPath = join67(stateDir, ISSUES_LOCK);
86116
86421
  const startedAt = Date.now();
86117
86422
  let fd = null;
86118
86423
  while (fd === null) {
@@ -86140,27 +86445,27 @@ function withLock(stateDir, fn) {
86140
86445
  closeSync12(fd);
86141
86446
  } catch {}
86142
86447
  try {
86143
- unlinkSync14(lockPath);
86448
+ unlinkSync15(lockPath);
86144
86449
  } catch {}
86145
86450
  }
86146
86451
  }
86147
86452
  function tryStealStaleLock(lockPath) {
86148
86453
  let pidStr;
86149
86454
  try {
86150
- pidStr = readFileSync59(lockPath, "utf-8").trim();
86455
+ pidStr = readFileSync60(lockPath, "utf-8").trim();
86151
86456
  } catch {
86152
86457
  return true;
86153
86458
  }
86154
86459
  const pid = Number(pidStr);
86155
86460
  if (!Number.isFinite(pid) || pid <= 0) {
86156
86461
  try {
86157
- unlinkSync14(lockPath);
86462
+ unlinkSync15(lockPath);
86158
86463
  } catch {}
86159
86464
  return true;
86160
86465
  }
86161
86466
  if (pid === process.pid) {
86162
86467
  try {
86163
- unlinkSync14(lockPath);
86468
+ unlinkSync15(lockPath);
86164
86469
  } catch {}
86165
86470
  return true;
86166
86471
  }
@@ -86175,7 +86480,7 @@ function tryStealStaleLock(lockPath) {
86175
86480
  return false;
86176
86481
  }
86177
86482
  try {
86178
- unlinkSync14(lockPath);
86483
+ unlinkSync15(lockPath);
86179
86484
  } catch {}
86180
86485
  return true;
86181
86486
  }
@@ -86395,21 +86700,21 @@ function relTime(deltaMs) {
86395
86700
 
86396
86701
  // src/cli/deps.ts
86397
86702
  init_source();
86398
- import { existsSync as existsSync69 } from "node:fs";
86399
- import { homedir as homedir42 } from "node:os";
86400
- import { join as join69, resolve as resolve42 } from "node:path";
86703
+ import { existsSync as existsSync70 } from "node:fs";
86704
+ import { homedir as homedir43 } from "node:os";
86705
+ import { join as join70, resolve as resolve42 } from "node:path";
86401
86706
 
86402
86707
  // src/deps/python.ts
86403
86708
  import { createHash as createHash12 } from "node:crypto";
86404
86709
  import {
86405
- existsSync as existsSync67,
86406
- mkdirSync as mkdirSync35,
86407
- readFileSync as readFileSync60,
86710
+ existsSync as existsSync68,
86711
+ mkdirSync as mkdirSync36,
86712
+ readFileSync as readFileSync61,
86408
86713
  rmSync as rmSync13,
86409
- writeFileSync as writeFileSync28
86714
+ writeFileSync as writeFileSync29
86410
86715
  } from "node:fs";
86411
- import { dirname as dirname21, join as join67 } from "node:path";
86412
- import { homedir as homedir40 } from "node:os";
86716
+ import { dirname as dirname22, join as join68 } from "node:path";
86717
+ import { homedir as homedir41 } from "node:os";
86413
86718
  import { execFileSync as execFileSync19 } from "node:child_process";
86414
86719
 
86415
86720
  class PythonEnvError extends Error {
@@ -86421,26 +86726,26 @@ class PythonEnvError extends Error {
86421
86726
  }
86422
86727
  }
86423
86728
  function defaultPythonCacheRoot() {
86424
- return join67(homedir40(), ".switchroom", "deps", "python");
86729
+ return join68(homedir41(), ".switchroom", "deps", "python");
86425
86730
  }
86426
86731
  function hashFile(path4) {
86427
- return createHash12("sha256").update(readFileSync60(path4)).digest("hex");
86732
+ return createHash12("sha256").update(readFileSync61(path4)).digest("hex");
86428
86733
  }
86429
86734
  function ensurePythonEnv(opts) {
86430
86735
  const { skillName, requirementsPath, force = false } = opts;
86431
86736
  const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
86432
86737
  const hostPython = opts.pythonBin ?? "python3";
86433
- if (!existsSync67(requirementsPath)) {
86738
+ if (!existsSync68(requirementsPath)) {
86434
86739
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
86435
86740
  }
86436
- const venvDir = join67(cacheRoot, skillName);
86437
- const stampPath = join67(venvDir, ".requirements.sha256");
86438
- const binDir = join67(venvDir, "bin");
86439
- const pythonBin = join67(binDir, "python");
86440
- const pipBin = join67(binDir, "pip");
86741
+ const venvDir = join68(cacheRoot, skillName);
86742
+ const stampPath = join68(venvDir, ".requirements.sha256");
86743
+ const binDir = join68(venvDir, "bin");
86744
+ const pythonBin = join68(binDir, "python");
86745
+ const pipBin = join68(binDir, "pip");
86441
86746
  const targetHash = hashFile(requirementsPath);
86442
- if (!force && existsSync67(stampPath) && existsSync67(pythonBin)) {
86443
- const existingHash = readFileSync60(stampPath, "utf8").trim();
86747
+ if (!force && existsSync68(stampPath) && existsSync68(pythonBin)) {
86748
+ const existingHash = readFileSync61(stampPath, "utf8").trim();
86444
86749
  if (existingHash === targetHash) {
86445
86750
  return {
86446
86751
  skillName,
@@ -86452,10 +86757,10 @@ function ensurePythonEnv(opts) {
86452
86757
  };
86453
86758
  }
86454
86759
  }
86455
- if (existsSync67(venvDir)) {
86760
+ if (existsSync68(venvDir)) {
86456
86761
  rmSync13(venvDir, { recursive: true, force: true });
86457
86762
  }
86458
- mkdirSync35(dirname21(venvDir), { recursive: true });
86763
+ mkdirSync36(dirname22(venvDir), { recursive: true });
86459
86764
  try {
86460
86765
  execFileSync19(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
86461
86766
  } catch (err) {
@@ -86474,7 +86779,7 @@ function ensurePythonEnv(opts) {
86474
86779
  const e = err;
86475
86780
  throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
86476
86781
  }
86477
- writeFileSync28(stampPath, targetHash + `
86782
+ writeFileSync29(stampPath, targetHash + `
86478
86783
  `);
86479
86784
  return {
86480
86785
  skillName,
@@ -86490,14 +86795,14 @@ function ensurePythonEnv(opts) {
86490
86795
  import { createHash as createHash13 } from "node:crypto";
86491
86796
  import {
86492
86797
  copyFileSync as copyFileSync10,
86493
- existsSync as existsSync68,
86494
- mkdirSync as mkdirSync36,
86495
- readFileSync as readFileSync61,
86798
+ existsSync as existsSync69,
86799
+ mkdirSync as mkdirSync37,
86800
+ readFileSync as readFileSync62,
86496
86801
  rmSync as rmSync14,
86497
- writeFileSync as writeFileSync29
86802
+ writeFileSync as writeFileSync30
86498
86803
  } from "node:fs";
86499
- import { dirname as dirname22, join as join68 } from "node:path";
86500
- import { homedir as homedir41 } from "node:os";
86804
+ import { dirname as dirname23, join as join69 } from "node:path";
86805
+ import { homedir as homedir42 } from "node:os";
86501
86806
  import { execFileSync as execFileSync20 } from "node:child_process";
86502
86807
 
86503
86808
  class NodeEnvError extends Error {
@@ -86520,23 +86825,23 @@ var LOCKFILES_FOR = {
86520
86825
  npm: ["package-lock.json"]
86521
86826
  };
86522
86827
  function defaultNodeCacheRoot() {
86523
- return join68(homedir41(), ".switchroom", "deps", "node");
86828
+ return join69(homedir42(), ".switchroom", "deps", "node");
86524
86829
  }
86525
86830
  function hashDepInputs(packageJsonPath) {
86526
- const sourceDir = dirname22(packageJsonPath);
86831
+ const sourceDir = dirname23(packageJsonPath);
86527
86832
  const hasher = createHash13("sha256");
86528
86833
  hasher.update(`package.json
86529
86834
  `);
86530
- hasher.update(readFileSync61(packageJsonPath));
86835
+ hasher.update(readFileSync62(packageJsonPath));
86531
86836
  for (const lockName of ALL_LOCKFILES) {
86532
- const lockPath = join68(sourceDir, lockName);
86533
- if (existsSync68(lockPath)) {
86837
+ const lockPath = join69(sourceDir, lockName);
86838
+ if (existsSync69(lockPath)) {
86534
86839
  hasher.update(`
86535
86840
  `);
86536
86841
  hasher.update(lockName);
86537
86842
  hasher.update(`
86538
86843
  `);
86539
- hasher.update(readFileSync61(lockPath));
86844
+ hasher.update(readFileSync62(lockPath));
86540
86845
  }
86541
86846
  }
86542
86847
  return hasher.digest("hex");
@@ -86545,17 +86850,17 @@ function ensureNodeEnv(opts) {
86545
86850
  const { skillName, packageJsonPath, force = false } = opts;
86546
86851
  const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
86547
86852
  const installer = opts.installer ?? "bun";
86548
- if (!existsSync68(packageJsonPath)) {
86853
+ if (!existsSync69(packageJsonPath)) {
86549
86854
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
86550
86855
  }
86551
- const sourceDir = dirname22(packageJsonPath);
86552
- const envDir = join68(cacheRoot, skillName);
86553
- const stampPath = join68(envDir, ".package.sha256");
86554
- const nodeModulesDir = join68(envDir, "node_modules");
86555
- const binDir = join68(nodeModulesDir, ".bin");
86856
+ const sourceDir = dirname23(packageJsonPath);
86857
+ const envDir = join69(cacheRoot, skillName);
86858
+ const stampPath = join69(envDir, ".package.sha256");
86859
+ const nodeModulesDir = join69(envDir, "node_modules");
86860
+ const binDir = join69(nodeModulesDir, ".bin");
86556
86861
  const targetHash = hashDepInputs(packageJsonPath);
86557
- if (!force && existsSync68(stampPath) && existsSync68(nodeModulesDir)) {
86558
- const existingHash = readFileSync61(stampPath, "utf8").trim();
86862
+ if (!force && existsSync69(stampPath) && existsSync69(nodeModulesDir)) {
86863
+ const existingHash = readFileSync62(stampPath, "utf8").trim();
86559
86864
  if (existingHash === targetHash) {
86560
86865
  return {
86561
86866
  skillName,
@@ -86566,16 +86871,16 @@ function ensureNodeEnv(opts) {
86566
86871
  };
86567
86872
  }
86568
86873
  }
86569
- if (existsSync68(envDir)) {
86874
+ if (existsSync69(envDir)) {
86570
86875
  rmSync14(envDir, { recursive: true, force: true });
86571
86876
  }
86572
- mkdirSync36(envDir, { recursive: true });
86573
- copyFileSync10(packageJsonPath, join68(envDir, "package.json"));
86877
+ mkdirSync37(envDir, { recursive: true });
86878
+ copyFileSync10(packageJsonPath, join69(envDir, "package.json"));
86574
86879
  let copiedLockfile = false;
86575
86880
  for (const lockName of LOCKFILES_FOR[installer]) {
86576
- const lockPath = join68(sourceDir, lockName);
86577
- if (existsSync68(lockPath)) {
86578
- copyFileSync10(lockPath, join68(envDir, lockName));
86881
+ const lockPath = join69(sourceDir, lockName);
86882
+ if (existsSync69(lockPath)) {
86883
+ copyFileSync10(lockPath, join69(envDir, lockName));
86579
86884
  copiedLockfile = true;
86580
86885
  }
86581
86886
  }
@@ -86591,7 +86896,7 @@ function ensureNodeEnv(opts) {
86591
86896
  const e = err;
86592
86897
  throw new NodeEnvError(`Failed to install node deps for skill "${skillName}" with ${installer}: ${e.message}`, e.stderr?.toString());
86593
86898
  }
86594
- writeFileSync29(stampPath, targetHash + `
86899
+ writeFileSync30(stampPath, targetHash + `
86595
86900
  `);
86596
86901
  return {
86597
86902
  skillName,
@@ -86604,28 +86909,28 @@ function ensureNodeEnv(opts) {
86604
86909
 
86605
86910
  // src/cli/deps.ts
86606
86911
  function builtinSkillsRoot() {
86607
- return resolve42(homedir42(), ".switchroom/skills/_bundled");
86912
+ return resolve42(homedir43(), ".switchroom/skills/_bundled");
86608
86913
  }
86609
86914
  function registerDepsCommand(program3) {
86610
86915
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
86611
86916
  deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
86612
86917
  const skillsRoot = builtinSkillsRoot();
86613
- if (!existsSync69(skillsRoot)) {
86918
+ if (!existsSync70(skillsRoot)) {
86614
86919
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
86615
86920
  process.exit(1);
86616
86921
  }
86617
- const skillDir = join69(skillsRoot, skill);
86618
- if (!existsSync69(skillDir)) {
86922
+ const skillDir = join70(skillsRoot, skill);
86923
+ if (!existsSync70(skillDir)) {
86619
86924
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
86620
86925
  process.exit(1);
86621
86926
  }
86622
- const requirementsPath = join69(skillDir, "requirements.txt");
86623
- const packageJsonPath = join69(skillDir, "package.json");
86624
- const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync69(requirementsPath));
86625
- const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync69(packageJsonPath));
86927
+ const requirementsPath = join70(skillDir, "requirements.txt");
86928
+ const packageJsonPath = join70(skillDir, "package.json");
86929
+ const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync70(requirementsPath));
86930
+ const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync70(packageJsonPath));
86626
86931
  let did = 0;
86627
86932
  if (wantPython) {
86628
- if (!existsSync69(requirementsPath)) {
86933
+ if (!existsSync70(requirementsPath)) {
86629
86934
  console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
86630
86935
  process.exit(1);
86631
86936
  }
@@ -86649,7 +86954,7 @@ function registerDepsCommand(program3) {
86649
86954
  }
86650
86955
  }
86651
86956
  if (wantNode) {
86652
- if (!existsSync69(packageJsonPath)) {
86957
+ if (!existsSync70(packageJsonPath)) {
86653
86958
  console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
86654
86959
  process.exit(1);
86655
86960
  }
@@ -86682,7 +86987,7 @@ function registerDepsCommand(program3) {
86682
86987
  // src/cli/workspace.ts
86683
86988
  init_helpers();
86684
86989
  init_loader();
86685
- import { existsSync as existsSync70 } from "node:fs";
86990
+ import { existsSync as existsSync71 } from "node:fs";
86686
86991
  import { resolve as resolve43, sep as sep3 } from "node:path";
86687
86992
  import { spawnSync as spawnSync14 } from "node:child_process";
86688
86993
 
@@ -87459,7 +87764,7 @@ function registerWorkspaceCommand(program3) {
87459
87764
  if (!dir)
87460
87765
  return;
87461
87766
  const gitDir = resolve43(dir, ".git");
87462
- if (!existsSync70(gitDir)) {
87767
+ if (!existsSync71(gitDir)) {
87463
87768
  process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
87464
87769
  `);
87465
87770
  return;
@@ -87513,7 +87818,7 @@ function registerWorkspaceCommand(program3) {
87513
87818
  if (!dir)
87514
87819
  return;
87515
87820
  const gitDir = resolve43(dir, ".git");
87516
- if (!existsSync70(gitDir)) {
87821
+ if (!existsSync71(gitDir)) {
87517
87822
  process.stdout.write(`Workspace is not a git repository.
87518
87823
  `);
87519
87824
  return;
@@ -87538,7 +87843,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
87538
87843
  const agentsDir = resolveAgentsDir(config);
87539
87844
  const agentDir = resolve43(agentsDir, agentName);
87540
87845
  const dir = resolveAgentWorkspaceDir(agentDir);
87541
- if (!existsSync70(dir)) {
87846
+ if (!existsSync71(dir)) {
87542
87847
  process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
87543
87848
  `);
87544
87849
  return;
@@ -87574,8 +87879,8 @@ function safeParseInt(value, fallback) {
87574
87879
  init_helpers();
87575
87880
  init_loader();
87576
87881
  init_merge();
87577
- import { copyFileSync as copyFileSync11, existsSync as existsSync71, readFileSync as readFileSync62, writeFileSync as writeFileSync30 } from "node:fs";
87578
- import { join as join70, resolve as resolve44 } from "node:path";
87882
+ import { copyFileSync as copyFileSync11, existsSync as existsSync72, readFileSync as readFileSync63, writeFileSync as writeFileSync31 } from "node:fs";
87883
+ import { join as join71, resolve as resolve44 } from "node:path";
87579
87884
  init_scaffold();
87580
87885
  init_profiles();
87581
87886
  init_schema();
@@ -87592,7 +87897,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
87592
87897
  const agentsDir = resolveAgentsDir(config);
87593
87898
  const agentDir = resolve44(agentsDir, agentName);
87594
87899
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
87595
- if (!existsSync71(workspaceDir)) {
87900
+ if (!existsSync72(workspaceDir)) {
87596
87901
  console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
87597
87902
  process.exit(1);
87598
87903
  }
@@ -87601,7 +87906,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
87601
87906
  profileName,
87602
87907
  profilePath,
87603
87908
  workspaceDir,
87604
- soulPath: join70(workspaceDir, "SOUL.md"),
87909
+ soulPath: join71(workspaceDir, "SOUL.md"),
87605
87910
  soul: merged.soul
87606
87911
  };
87607
87912
  }
@@ -87618,11 +87923,11 @@ function registerSoulCommand(program3) {
87618
87923
  const t = resolveSoulTargetOrExit(program3, agentName);
87619
87924
  if (!t)
87620
87925
  return;
87621
- if (!existsSync71(t.soulPath)) {
87926
+ if (!existsSync72(t.soulPath)) {
87622
87927
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
87623
87928
  process.exit(1);
87624
87929
  }
87625
- process.stdout.write(readFileSync62(t.soulPath, "utf-8"));
87930
+ process.stdout.write(readFileSync63(t.soulPath, "utf-8"));
87626
87931
  }));
87627
87932
  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) => {
87628
87933
  const t = resolveSoulTargetOrExit(program3, agentName);
@@ -87633,7 +87938,7 @@ function registerSoulCommand(program3) {
87633
87938
  console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
87634
87939
  process.exit(1);
87635
87940
  }
87636
- const exists = existsSync71(t.soulPath);
87941
+ const exists = existsSync72(t.soulPath);
87637
87942
  if (exists && !opts.yes) {
87638
87943
  if (!isInteractive()) {
87639
87944
  console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
@@ -87648,12 +87953,12 @@ function registerSoulCommand(program3) {
87648
87953
  let backupPath;
87649
87954
  if (exists) {
87650
87955
  backupPath = `${t.soulPath}.bak`;
87651
- if (existsSync71(backupPath)) {
87956
+ if (existsSync72(backupPath)) {
87652
87957
  backupPath = `${t.soulPath}.bak.${Date.now()}`;
87653
87958
  }
87654
87959
  copyFileSync11(t.soulPath, backupPath);
87655
87960
  }
87656
- writeFileSync30(t.soulPath, content, "utf-8");
87961
+ writeFileSync31(t.soulPath, content, "utf-8");
87657
87962
  if (backupPath) {
87658
87963
  console.log(`soul: re-seeded ${agentName}'s SOUL.md from profile ` + `"${t.profileName}".
87659
87964
  ` + ` Previous version saved to ${backupPath}`);
@@ -87667,8 +87972,8 @@ function registerSoulCommand(program3) {
87667
87972
  // src/cli/debug.ts
87668
87973
  init_helpers();
87669
87974
  init_loader();
87670
- import { existsSync as existsSync72, readFileSync as readFileSync63, readdirSync as readdirSync24, statSync as statSync32 } from "node:fs";
87671
- import { resolve as resolve45, join as join71 } from "node:path";
87975
+ import { existsSync as existsSync73, readFileSync as readFileSync64, readdirSync as readdirSync24, statSync as statSync33 } from "node:fs";
87976
+ import { resolve as resolve45, join as join72 } from "node:path";
87672
87977
  import { createHash as createHash14 } from "node:crypto";
87673
87978
  init_merge();
87674
87979
  init_hindsight2();
@@ -87679,11 +87984,11 @@ function estimateTokens(bytes) {
87679
87984
  return Math.round(bytes / 3.7);
87680
87985
  }
87681
87986
  function readMcpServerNames(agentDir) {
87682
- const mcpPath = join71(agentDir, ".mcp.json");
87683
- if (!existsSync72(mcpPath))
87987
+ const mcpPath = join72(agentDir, ".mcp.json");
87988
+ if (!existsSync73(mcpPath))
87684
87989
  return [];
87685
87990
  try {
87686
- const parsed = JSON.parse(readFileSync63(mcpPath, "utf-8"));
87991
+ const parsed = JSON.parse(readFileSync64(mcpPath, "utf-8"));
87687
87992
  return Object.keys(parsed.mcpServers ?? {});
87688
87993
  } catch {
87689
87994
  return null;
@@ -87693,8 +87998,8 @@ function sha256(content) {
87693
87998
  return createHash14("sha256").update(content).digest("hex").slice(0, 16);
87694
87999
  }
87695
88000
  function findLatestTranscriptJsonl(claudeConfigDir) {
87696
- const projectsDir = join71(claudeConfigDir, "projects");
87697
- if (!existsSync72(projectsDir))
88001
+ const projectsDir = join72(claudeConfigDir, "projects");
88002
+ if (!existsSync73(projectsDir))
87698
88003
  return;
87699
88004
  try {
87700
88005
  const entries = readdirSync24(projectsDir, { withFileTypes: true });
@@ -87702,11 +88007,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
87702
88007
  for (const entry of entries) {
87703
88008
  if (!entry.isDirectory())
87704
88009
  continue;
87705
- const projectPath = join71(projectsDir, entry.name);
87706
- const transcriptPath = join71(projectPath, "transcript.jsonl");
87707
- if (!existsSync72(transcriptPath))
88010
+ const projectPath = join72(projectsDir, entry.name);
88011
+ const transcriptPath = join72(projectPath, "transcript.jsonl");
88012
+ if (!existsSync73(transcriptPath))
87708
88013
  continue;
87709
- const stat3 = statSync32(transcriptPath);
88014
+ const stat3 = statSync33(transcriptPath);
87710
88015
  if (!latest || stat3.mtimeMs > latest.mtime) {
87711
88016
  latest = { path: transcriptPath, mtime: stat3.mtimeMs };
87712
88017
  }
@@ -87718,7 +88023,7 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
87718
88023
  }
87719
88024
  function extractLatestUserMessage(transcriptPath) {
87720
88025
  try {
87721
- const content = readFileSync63(transcriptPath, "utf-8");
88026
+ const content = readFileSync64(transcriptPath, "utf-8");
87722
88027
  const lines = content.trim().split(`
87723
88028
  `).filter(Boolean);
87724
88029
  for (let i = lines.length - 1;i >= 0; i--) {
@@ -87767,16 +88072,16 @@ function registerDebugCommand(program3) {
87767
88072
  }
87768
88073
  const agentsDir = resolveAgentsDir(config);
87769
88074
  const agentDir = resolve45(agentsDir, agentName);
87770
- if (!existsSync72(agentDir)) {
88075
+ if (!existsSync73(agentDir)) {
87771
88076
  console.error(`Agent directory not found: ${agentDir}`);
87772
88077
  process.exit(1);
87773
88078
  }
87774
88079
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
87775
- const claudeConfigDir = join71(agentDir, ".claude");
87776
- const claudeMdPath = join71(agentDir, "CLAUDE.md");
87777
- const soulMdPath = join71(agentDir, "SOUL.md");
87778
- const workspaceSoulMdPath = join71(workspaceDir, "SOUL.md");
87779
- const handoffPath = join71(agentDir, ".handoff.md");
88080
+ const claudeConfigDir = join72(agentDir, ".claude");
88081
+ const claudeMdPath = join72(agentDir, "CLAUDE.md");
88082
+ const soulMdPath = join72(agentDir, "SOUL.md");
88083
+ const workspaceSoulMdPath = join72(workspaceDir, "SOUL.md");
88084
+ const handoffPath = join72(agentDir, ".handoff.md");
87780
88085
  const lastN = parseInt(opts.last, 10);
87781
88086
  if (isNaN(lastN) || lastN < 1) {
87782
88087
  console.error("--last must be a positive integer");
@@ -87822,7 +88127,7 @@ function registerDebugCommand(program3) {
87822
88127
  }
87823
88128
  console.log(`=== Append System Prompt (per-session) ===
87824
88129
  `);
87825
- const handoffContent = existsSync72(handoffPath) ? readFileSync63(handoffPath, "utf-8") : "";
88130
+ const handoffContent = existsSync73(handoffPath) ? readFileSync64(handoffPath, "utf-8") : "";
87826
88131
  if (handoffContent.trim().length > 0) {
87827
88132
  console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
87828
88133
  console.log(handoffContent);
@@ -87833,7 +88138,7 @@ function registerDebugCommand(program3) {
87833
88138
  }
87834
88139
  console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
87835
88140
  `);
87836
- const claudeMdContent = existsSync72(claudeMdPath) ? readFileSync63(claudeMdPath, "utf-8") : "";
88141
+ const claudeMdContent = existsSync73(claudeMdPath) ? readFileSync64(claudeMdPath, "utf-8") : "";
87837
88142
  if (claudeMdContent.trim().length > 0) {
87838
88143
  console.log(`(${formatBytes(claudeMdContent.length)})`);
87839
88144
  console.log(claudeMdContent);
@@ -87844,7 +88149,7 @@ function registerDebugCommand(program3) {
87844
88149
  }
87845
88150
  console.log(`=== Persona (SOUL.md) ===
87846
88151
  `);
87847
- const soulMdContent = existsSync72(soulMdPath) ? readFileSync63(soulMdPath, "utf-8") : existsSync72(workspaceSoulMdPath) ? readFileSync63(workspaceSoulMdPath, "utf-8") : "";
88152
+ const soulMdContent = existsSync73(soulMdPath) ? readFileSync64(soulMdPath, "utf-8") : existsSync73(workspaceSoulMdPath) ? readFileSync64(workspaceSoulMdPath, "utf-8") : "";
87848
88153
  if (soulMdContent.trim().length > 0) {
87849
88154
  console.log(`(${formatBytes(soulMdContent.length)})`);
87850
88155
  console.log(soulMdContent);
@@ -87905,11 +88210,11 @@ function registerDebugCommand(program3) {
87905
88210
  const soulMdBytes = soulMdContent.length;
87906
88211
  const perTurnBytes = dynamicResult.concatenated.length;
87907
88212
  const userBytes = userMessage?.text.length ?? 0;
87908
- const fleetDir = join71(agentsDir, "..", "fleet");
87909
- const fleetInvPath = join71(fleetDir, "switchroom-invariants.md");
87910
- const fleetClaudePath = join71(fleetDir, "CLAUDE.md");
87911
- const fleetInvBytes = existsSync72(fleetInvPath) ? readFileSync63(fleetInvPath, "utf-8").length : 0;
87912
- const fleetClaudeBytes = existsSync72(fleetClaudePath) ? readFileSync63(fleetClaudePath, "utf-8").length : 0;
88213
+ const fleetDir = join72(agentsDir, "..", "fleet");
88214
+ const fleetInvPath = join72(fleetDir, "switchroom-invariants.md");
88215
+ const fleetClaudePath = join72(fleetDir, "CLAUDE.md");
88216
+ const fleetInvBytes = existsSync73(fleetInvPath) ? readFileSync64(fleetInvPath, "utf-8").length : 0;
88217
+ const fleetClaudeBytes = existsSync73(fleetClaudePath) ? readFileSync64(fleetClaudePath, "utf-8").length : 0;
87913
88218
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
87914
88219
  const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
87915
88220
  console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
@@ -87942,44 +88247,44 @@ init_source();
87942
88247
 
87943
88248
  // src/worktree/claim.ts
87944
88249
  import { execFileSync as execFileSync21 } from "node:child_process";
87945
- import { closeSync as closeSync13, mkdirSync as mkdirSync38, openSync as openSync13, existsSync as existsSync74, unlinkSync as unlinkSync16 } from "node:fs";
87946
- import { join as join73, resolve as resolve47 } from "node:path";
87947
- import { homedir as homedir44 } from "node:os";
88250
+ import { closeSync as closeSync13, mkdirSync as mkdirSync39, openSync as openSync13, existsSync as existsSync75, unlinkSync as unlinkSync17 } from "node:fs";
88251
+ import { join as join74, resolve as resolve47 } from "node:path";
88252
+ import { homedir as homedir45 } from "node:os";
87948
88253
  import { randomBytes as randomBytes13 } from "node:crypto";
87949
88254
 
87950
88255
  // src/worktree/registry.ts
87951
88256
  import {
87952
- mkdirSync as mkdirSync37,
87953
- writeFileSync as writeFileSync31,
87954
- readFileSync as readFileSync64,
88257
+ mkdirSync as mkdirSync38,
88258
+ writeFileSync as writeFileSync32,
88259
+ readFileSync as readFileSync65,
87955
88260
  readdirSync as readdirSync25,
87956
- unlinkSync as unlinkSync15,
87957
- existsSync as existsSync73,
88261
+ unlinkSync as unlinkSync16,
88262
+ existsSync as existsSync74,
87958
88263
  renameSync as renameSync16
87959
88264
  } from "node:fs";
87960
- import { join as join72, resolve as resolve46 } from "node:path";
87961
- import { homedir as homedir43 } from "node:os";
88265
+ import { join as join73, resolve as resolve46 } from "node:path";
88266
+ import { homedir as homedir44 } from "node:os";
87962
88267
  function registryDir() {
87963
- return resolve46(process.env.SWITCHROOM_WORKTREE_DIR ?? join72(homedir43(), ".switchroom", "worktrees"));
88268
+ return resolve46(process.env.SWITCHROOM_WORKTREE_DIR ?? join73(homedir44(), ".switchroom", "worktrees"));
87964
88269
  }
87965
88270
  function recordPath(id) {
87966
- return join72(registryDir(), `${id}.json`);
88271
+ return join73(registryDir(), `${id}.json`);
87967
88272
  }
87968
88273
  function ensureDir2() {
87969
- mkdirSync37(registryDir(), { recursive: true });
88274
+ mkdirSync38(registryDir(), { recursive: true });
87970
88275
  }
87971
88276
  function writeRecord(record2) {
87972
88277
  ensureDir2();
87973
88278
  const target = recordPath(record2.id);
87974
88279
  const tmp = `${target}.tmp${process.pid}`;
87975
- writeFileSync31(tmp, JSON.stringify(record2, null, 2) + `
88280
+ writeFileSync32(tmp, JSON.stringify(record2, null, 2) + `
87976
88281
  `, { mode: 384 });
87977
88282
  renameSync16(tmp, target);
87978
88283
  }
87979
88284
  function readRecord(id) {
87980
88285
  const path7 = recordPath(id);
87981
88286
  try {
87982
- const raw = readFileSync64(path7, "utf8");
88287
+ const raw = readFileSync65(path7, "utf8");
87983
88288
  return JSON.parse(raw);
87984
88289
  } catch {
87985
88290
  return null;
@@ -87988,7 +88293,7 @@ function readRecord(id) {
87988
88293
  function deleteRecord(id) {
87989
88294
  const path7 = recordPath(id);
87990
88295
  try {
87991
- unlinkSync15(path7);
88296
+ unlinkSync16(path7);
87992
88297
  } catch {}
87993
88298
  }
87994
88299
  function listRecords() {
@@ -88012,9 +88317,9 @@ function countByRepo(repoPath) {
88012
88317
  // src/worktree/claim.ts
88013
88318
  function acquireRepoLock(repoPath) {
88014
88319
  const lockDir = registryDir();
88015
- mkdirSync38(lockDir, { recursive: true });
88320
+ mkdirSync39(lockDir, { recursive: true });
88016
88321
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
88017
- const lockPath = join73(lockDir, `.lock-${lockName}`);
88322
+ const lockPath = join74(lockDir, `.lock-${lockName}`);
88018
88323
  const deadline = Date.now() + 5000;
88019
88324
  let fd = null;
88020
88325
  while (fd === null) {
@@ -88035,13 +88340,13 @@ function acquireRepoLock(repoPath) {
88035
88340
  closeSync13(fd);
88036
88341
  } catch {}
88037
88342
  try {
88038
- unlinkSync16(lockPath);
88343
+ unlinkSync17(lockPath);
88039
88344
  } catch {}
88040
88345
  };
88041
88346
  }
88042
88347
  var DEFAULT_CONCURRENCY = 5;
88043
88348
  function worktreesBaseDir() {
88044
- return resolve47(process.env.SWITCHROOM_WORKTREE_BASE ?? join73(homedir44(), ".switchroom", "worktree-checkouts"));
88349
+ return resolve47(process.env.SWITCHROOM_WORKTREE_BASE ?? join74(homedir45(), ".switchroom", "worktree-checkouts"));
88045
88350
  }
88046
88351
  function shortId() {
88047
88352
  return randomBytes13(4).toString("hex");
@@ -88063,12 +88368,12 @@ function resolveRepoPath(repo, codeRepos) {
88063
88368
  }
88064
88369
  function expandHome(p) {
88065
88370
  if (p.startsWith("~/"))
88066
- return join73(homedir44(), p.slice(2));
88371
+ return join74(homedir45(), p.slice(2));
88067
88372
  return p;
88068
88373
  }
88069
88374
  async function claimWorktree(input, codeRepos) {
88070
88375
  const repoPath = resolveRepoPath(input.repo, codeRepos);
88071
- if (!existsSync74(repoPath)) {
88376
+ if (!existsSync75(repoPath)) {
88072
88377
  throw new Error(`Repository path does not exist: ${repoPath}`);
88073
88378
  }
88074
88379
  let concurrencyCap = DEFAULT_CONCURRENCY;
@@ -88090,8 +88395,8 @@ async function claimWorktree(input, codeRepos) {
88090
88395
  const taskSuffix = input.taskName ? sanitizeTaskName(input.taskName) : "task";
88091
88396
  branch = `task/${taskSuffix}-${id}`;
88092
88397
  const baseDir = worktreesBaseDir();
88093
- mkdirSync38(baseDir, { recursive: true });
88094
- worktreePath = join73(baseDir, `${id}-${taskSuffix}`);
88398
+ mkdirSync39(baseDir, { recursive: true });
88399
+ worktreePath = join74(baseDir, `${id}-${taskSuffix}`);
88095
88400
  const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
88096
88401
  const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
88097
88402
  const now = new Date().toISOString();
@@ -88124,7 +88429,7 @@ async function claimWorktree(input, codeRepos) {
88124
88429
 
88125
88430
  // src/worktree/release.ts
88126
88431
  import { execFileSync as execFileSync22 } from "node:child_process";
88127
- import { existsSync as existsSync75 } from "node:fs";
88432
+ import { existsSync as existsSync76 } from "node:fs";
88128
88433
  function releaseWorktree(input) {
88129
88434
  const { id } = input;
88130
88435
  const record2 = readRecord(id);
@@ -88132,7 +88437,7 @@ function releaseWorktree(input) {
88132
88437
  return { released: true };
88133
88438
  }
88134
88439
  let gitSuccess = true;
88135
- if (existsSync75(record2.path)) {
88440
+ if (existsSync76(record2.path)) {
88136
88441
  try {
88137
88442
  execFileSync22("git", ["worktree", "remove", "--force", record2.path], {
88138
88443
  cwd: record2.repo,
@@ -88171,7 +88476,7 @@ function listWorktrees() {
88171
88476
 
88172
88477
  // src/worktree/reaper.ts
88173
88478
  import { execFileSync as execFileSync23 } from "node:child_process";
88174
- import { existsSync as existsSync76 } from "node:fs";
88479
+ import { existsSync as existsSync77 } from "node:fs";
88175
88480
  var STALE_THRESHOLD_MS = 10 * 60 * 1000;
88176
88481
  function isPathInUse(path7) {
88177
88482
  try {
@@ -88198,7 +88503,7 @@ function hasUncommittedChanges(repoPath, worktreePath) {
88198
88503
  function reapRecord(record2) {
88199
88504
  const { id, path: path7, repo, branch, ownerAgent } = record2;
88200
88505
  let warning = null;
88201
- if (existsSync76(path7)) {
88506
+ if (existsSync77(path7)) {
88202
88507
  if (hasUncommittedChanges(repo, path7)) {
88203
88508
  warning = `[worktree-reaper] Reaped worktree with uncommitted changes: ` + `id=${id} branch=${branch} agent=${ownerAgent ?? "unknown"} path=${path7}`;
88204
88509
  }
@@ -88219,7 +88524,7 @@ function runReaper(nowMs) {
88219
88524
  const warnings = [];
88220
88525
  for (const record2 of records) {
88221
88526
  const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
88222
- const worktreeExists = existsSync76(record2.path);
88527
+ const worktreeExists = existsSync77(record2.path);
88223
88528
  if (!worktreeExists) {
88224
88529
  deleteRecord(record2.id);
88225
88530
  reaped.push(record2.id);
@@ -88239,16 +88544,16 @@ function runReaper(nowMs) {
88239
88544
  // src/worktree/gc.ts
88240
88545
  import { execFileSync as execFileSync24 } from "node:child_process";
88241
88546
  import {
88242
- existsSync as existsSync77,
88243
- readFileSync as readFileSync65,
88547
+ existsSync as existsSync78,
88548
+ readFileSync as readFileSync66,
88244
88549
  readdirSync as readdirSync26,
88245
- statSync as statSync33,
88550
+ statSync as statSync34,
88246
88551
  renameSync as renameSync17,
88247
- mkdirSync as mkdirSync39,
88552
+ mkdirSync as mkdirSync40,
88248
88553
  rmSync as rmSync15
88249
88554
  } from "node:fs";
88250
- import { homedir as homedir45 } from "node:os";
88251
- import { join as join74, resolve as resolve48 } from "node:path";
88555
+ import { homedir as homedir46 } from "node:os";
88556
+ import { join as join75, resolve as resolve48 } from "node:path";
88252
88557
  function parseGitdirPointer(dotGitFileContents) {
88253
88558
  const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
88254
88559
  return m ? m[1] : null;
@@ -88363,17 +88668,17 @@ function defaultPrSignal(repo, branch, exec) {
88363
88668
  }
88364
88669
  }
88365
88670
  function trashRoot() {
88366
- return resolve48(process.env.SWITCHROOM_WORKTREE_TRASH ?? join74(homedir45(), ".switchroom", "worktree-gc-trash"));
88671
+ return resolve48(process.env.SWITCHROOM_WORKTREE_TRASH ?? join75(homedir46(), ".switchroom", "worktree-gc-trash"));
88367
88672
  }
88368
88673
  function planGc(roots, deps = {}) {
88369
- const exists = deps.existsSync ?? existsSync77;
88674
+ const exists = deps.existsSync ?? existsSync78;
88370
88675
  const readDir = deps.readDir ?? ((p) => readdirSync26(p));
88371
- const readFile4 = deps.readFile ?? ((p) => readFileSync65(p, "utf8"));
88372
- const stat3 = deps.stat ?? ((p) => statSync33(p));
88676
+ const readFile4 = deps.readFile ?? ((p) => readFileSync66(p, "utf8"));
88677
+ const stat3 = deps.stat ?? ((p) => statSync34(p));
88373
88678
  const exec = deps.exec ?? defaultExec;
88374
88679
  const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
88375
88680
  const stamp = deps.dateStamp ?? "undated";
88376
- const trash = join74(trashRoot(), stamp);
88681
+ const trash = join75(trashRoot(), stamp);
88377
88682
  let claimed;
88378
88683
  try {
88379
88684
  claimed = new Set(listRecords().map((r) => resolve48(r.path)));
@@ -88409,10 +88714,10 @@ function planGc(roots, deps = {}) {
88409
88714
  continue;
88410
88715
  }
88411
88716
  for (const name of entries) {
88412
- const dir = join74(root, name);
88717
+ const dir = join75(root, name);
88413
88718
  if (isEphemeralPath(dir))
88414
88719
  continue;
88415
- const dotGit = join74(dir, ".git");
88720
+ const dotGit = join75(dir, ".git");
88416
88721
  if (!exists(dotGit))
88417
88722
  continue;
88418
88723
  let st;
@@ -88441,7 +88746,7 @@ function planGc(roots, deps = {}) {
88441
88746
  ownerRepos.add(repoRoot);
88442
88747
  if (exists(ptr))
88443
88748
  continue;
88444
- orphans.push({ dir, owner: repoRoot, dest: join74(trash, name) });
88749
+ orphans.push({ dir, owner: repoRoot, dest: join75(trash, name) });
88445
88750
  }
88446
88751
  }
88447
88752
  const registered = [];
@@ -88496,7 +88801,7 @@ function planGc(roots, deps = {}) {
88496
88801
  }
88497
88802
  function applyGc(plan, deps = {}) {
88498
88803
  const exec = deps.exec ?? defaultExec;
88499
- const mkdirp = deps.mkdirp ?? ((p) => void mkdirSync39(p, { recursive: true }));
88804
+ const mkdirp = deps.mkdirp ?? ((p) => void mkdirSync40(p, { recursive: true }));
88500
88805
  const move = deps.move ?? ((src, dest) => {
88501
88806
  try {
88502
88807
  renameSync17(src, dest);
@@ -88545,14 +88850,14 @@ function selectPurgeTargets(entries, olderThanDays) {
88545
88850
  return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
88546
88851
  }
88547
88852
  function listTrashEntries(nowMs, deps = {}) {
88548
- const exists = deps.existsSync ?? existsSync77;
88853
+ const exists = deps.existsSync ?? existsSync78;
88549
88854
  const readDir = deps.readDir ?? ((p) => readdirSync26(p));
88550
88855
  const root = trashRoot();
88551
88856
  if (!exists(root))
88552
88857
  return [];
88553
88858
  const out = [];
88554
88859
  for (const stamp of readDir(root)) {
88555
- const stampDir = join74(root, stamp);
88860
+ const stampDir = join75(root, stamp);
88556
88861
  let names;
88557
88862
  try {
88558
88863
  names = readDir(stampDir);
@@ -88560,10 +88865,10 @@ function listTrashEntries(nowMs, deps = {}) {
88560
88865
  continue;
88561
88866
  }
88562
88867
  for (const name of names) {
88563
- const p = join74(stampDir, name);
88868
+ const p = join75(stampDir, name);
88564
88869
  let mtimeMs = nowMs;
88565
88870
  try {
88566
- mtimeMs = statSync33(p).mtimeMs;
88871
+ mtimeMs = statSync34(p).mtimeMs;
88567
88872
  } catch {}
88568
88873
  out.push({ path: p, ageDays: (nowMs - mtimeMs) / 86400000 });
88569
88874
  }
@@ -88584,7 +88889,7 @@ function purgeTrash(paths) {
88584
88889
  return { deleted, errors: errors2 };
88585
88890
  }
88586
88891
  function defaultRoots() {
88587
- return [join74(homedir45(), "code")];
88892
+ return [join75(homedir46(), "code")];
88588
88893
  }
88589
88894
 
88590
88895
  // src/cli/worktree.ts
@@ -88773,12 +89078,12 @@ init_drive();
88773
89078
  init_scaffold_integration();
88774
89079
  import {
88775
89080
  chmodSync as chmodSync9,
88776
- mkdirSync as mkdirSync40,
89081
+ mkdirSync as mkdirSync41,
88777
89082
  readdirSync as readdirSync27,
88778
89083
  rmSync as rmSync16,
88779
- writeFileSync as writeFileSync32
89084
+ writeFileSync as writeFileSync33
88780
89085
  } from "node:fs";
88781
- import { join as join75 } from "node:path";
89086
+ import { join as join76 } from "node:path";
88782
89087
  function encodeCredentialsFilename(email) {
88783
89088
  const SAFE = new Set([
88784
89089
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -88968,17 +89273,17 @@ function resolveCredentialsDir(env2) {
88968
89273
  if (explicit && explicit.length > 0)
88969
89274
  return explicit;
88970
89275
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
88971
- return join75(stateBase, "google-workspace-mcp", "credentials");
89276
+ return join76(stateBase, "google-workspace-mcp", "credentials");
88972
89277
  }
88973
89278
  function writeSeedFile(dir, email, seed) {
88974
- mkdirSync40(dir, { recursive: true, mode: 448 });
89279
+ mkdirSync41(dir, { recursive: true, mode: 448 });
88975
89280
  chmodSync9(dir, 448);
88976
89281
  for (const name of readdirSync27(dir)) {
88977
- rmSync16(join75(dir, name), { force: true, recursive: true });
89282
+ rmSync16(join76(dir, name), { force: true, recursive: true });
88978
89283
  }
88979
89284
  const filename = encodeCredentialsFilename(email);
88980
- const filePath = join75(dir, filename);
88981
- writeFileSync32(filePath, JSON.stringify(seed), { mode: 384 });
89285
+ const filePath = join76(dir, filename);
89286
+ writeFileSync33(filePath, JSON.stringify(seed), { mode: 384 });
88982
89287
  chmodSync9(filePath, 384);
88983
89288
  return filePath;
88984
89289
  }
@@ -89136,11 +89441,19 @@ function registerDriveMcpLauncherCommand(program3) {
89136
89441
  // src/cli/m365-mcp-launcher.ts
89137
89442
  init_scaffold_integration();
89138
89443
  import { spawn as spawn5 } from "node:child_process";
89139
- import { writeFileSync as writeFileSync33, mkdirSync as mkdirSync41 } from "node:fs";
89140
- import { dirname as dirname23, join as join76 } from "node:path";
89444
+ import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync42 } from "node:fs";
89445
+ import { dirname as dirname24, join as join77 } from "node:path";
89141
89446
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
89142
89447
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
89143
89448
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
89449
+ var SOFTERIA_RESTART_BASE_MS = 1000;
89450
+ var SOFTERIA_RESTART_MAX_MS = 30 * 1000;
89451
+ var SOFTERIA_STABLE_MS = 60 * 1000;
89452
+ var SOFTERIA_MAX_RESTARTS = 6;
89453
+ function computeRestartBackoffMs(attempt) {
89454
+ const raw = SOFTERIA_RESTART_BASE_MS * 2 ** Math.max(0, attempt);
89455
+ return Math.min(raw, SOFTERIA_RESTART_MAX_MS);
89456
+ }
89144
89457
  function buildSofteriaArgs(opts = {}) {
89145
89458
  const pkg = `${MICROSOFT_WORKSPACE_MCP_PACKAGE}@${MICROSOFT_WORKSPACE_MCP_PINNED_VERSION}`;
89146
89459
  const args = ["-y", pkg];
@@ -89162,14 +89475,14 @@ function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS)
89162
89475
  function writeRefreshHeartbeat(agentName, data) {
89163
89476
  const path7 = heartbeatPath(agentName);
89164
89477
  try {
89165
- mkdirSync41(dirname23(path7), { recursive: true });
89166
- writeFileSync33(path7, JSON.stringify(data, null, 2), { mode: 420 });
89478
+ mkdirSync42(dirname24(path7), { recursive: true });
89479
+ writeFileSync34(path7, JSON.stringify(data, null, 2), { mode: 420 });
89167
89480
  } catch {}
89168
89481
  }
89169
89482
  function heartbeatPath(agentName) {
89170
89483
  const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
89171
89484
  if (override) {
89172
- return join76(override, `m365-launcher-${agentName}.heartbeat.json`);
89485
+ return join77(override, `m365-launcher-${agentName}.heartbeat.json`);
89173
89486
  }
89174
89487
  return "/state/agent/m365-launcher.heartbeat.json";
89175
89488
  }
@@ -89228,13 +89541,23 @@ async function runMs365McpLauncher(opts, rt) {
89228
89541
  let currentChild = null;
89229
89542
  let teardownStdio = null;
89230
89543
  let refreshTimer = null;
89544
+ let restartTimer = null;
89231
89545
  let restartingForRefresh = false;
89546
+ let shuttingDown = false;
89232
89547
  let resolveLauncher = null;
89548
+ let currentCreds = null;
89549
+ let restartAttempts = 0;
89550
+ let lastSpawnMs = 0;
89233
89551
  const exitLauncher = (code) => {
89552
+ shuttingDown = true;
89234
89553
  if (refreshTimer) {
89235
89554
  clearTimer(refreshTimer);
89236
89555
  refreshTimer = null;
89237
89556
  }
89557
+ if (restartTimer) {
89558
+ clearTimer(restartTimer);
89559
+ restartTimer = null;
89560
+ }
89238
89561
  if (resolveLauncher) {
89239
89562
  const r = resolveLauncher;
89240
89563
  resolveLauncher = null;
@@ -89244,6 +89567,7 @@ async function runMs365McpLauncher(opts, rt) {
89244
89567
  const launchChild = (accessToken) => {
89245
89568
  const env2 = buildSofteriaEnv(accessToken);
89246
89569
  const child = rt.spawnSofteria(env2);
89570
+ lastSpawnMs = now();
89247
89571
  teardownStdio = wireStdio(child);
89248
89572
  child.once("exit", (code, signal) => {
89249
89573
  if (teardownStdio) {
@@ -89254,6 +89578,35 @@ async function runMs365McpLauncher(opts, rt) {
89254
89578
  return;
89255
89579
  }
89256
89580
  const resolved = code ?? (signal ? 128 : 0);
89581
+ if (shuttingDown)
89582
+ return;
89583
+ const crashed = resolved !== 0;
89584
+ const tokenValid = currentCreds != null && currentCreds.expiresAt > now();
89585
+ if (crashed && tokenValid) {
89586
+ if (now() - lastSpawnMs >= SOFTERIA_STABLE_MS)
89587
+ restartAttempts = 0;
89588
+ if (restartAttempts < SOFTERIA_MAX_RESTARTS) {
89589
+ const delayMs = computeRestartBackoffMs(restartAttempts);
89590
+ restartAttempts += 1;
89591
+ currentChild = null;
89592
+ log(`m365-launcher: softeria exited (code=${resolved} signal=${signal}); re-spawning with cached token in ${Math.round(delayMs / 1000)}s (attempt ${restartAttempts}/${SOFTERIA_MAX_RESTARTS}, no broker call)`);
89593
+ restartTimer = setTimer(() => {
89594
+ restartTimer = null;
89595
+ if (shuttingDown)
89596
+ return;
89597
+ if (currentCreds != null && currentCreds.expiresAt > now()) {
89598
+ currentChild = launchChild(currentCreds.accessToken);
89599
+ } else {
89600
+ log("m365-launcher: cached token expired during backoff \u2014 exiting for fresh creds");
89601
+ exitLauncher(resolved);
89602
+ }
89603
+ }, delayMs);
89604
+ return;
89605
+ }
89606
+ log(`m365-launcher: softeria crash-looped ${restartAttempts}x within ${Math.round(SOFTERIA_STABLE_MS / 1000)}s \u2014 giving up, exiting`);
89607
+ exitLauncher(resolved);
89608
+ return;
89609
+ }
89257
89610
  log(`m365-launcher: softeria exited unexpectedly code=${resolved} signal=${signal}`);
89258
89611
  exitLauncher(resolved);
89259
89612
  });
@@ -89271,6 +89624,10 @@ async function runMs365McpLauncher(opts, rt) {
89271
89624
  refreshTimer = setTimer(async () => {
89272
89625
  try {
89273
89626
  log("m365-launcher: refreshing token + restarting softeria");
89627
+ if (restartTimer) {
89628
+ clearTimer(restartTimer);
89629
+ restartTimer = null;
89630
+ }
89274
89631
  restartingForRefresh = true;
89275
89632
  try {
89276
89633
  process.stdin.pause();
@@ -89280,6 +89637,8 @@ async function runMs365McpLauncher(opts, rt) {
89280
89637
  await killChild(currentChild);
89281
89638
  }
89282
89639
  restartingForRefresh = false;
89640
+ currentCreds = fresh;
89641
+ restartAttempts = 0;
89283
89642
  currentChild = launchChild(fresh.accessToken);
89284
89643
  try {
89285
89644
  process.stdin.resume();
@@ -89306,6 +89665,7 @@ async function runMs365McpLauncher(opts, rt) {
89306
89665
  log(`m365-launcher: initial broker call failed \u2014 ${msg}`);
89307
89666
  return 1;
89308
89667
  }
89668
+ currentCreds = initial;
89309
89669
  currentChild = launchChild(initial.accessToken);
89310
89670
  scheduleRefresh(initial.expiresAt);
89311
89671
  const onSignal = (signal) => {
@@ -89354,8 +89714,8 @@ function registerM365McpLauncherCommand(program3) {
89354
89714
  // src/cli/notion-mcp-launcher.ts
89355
89715
  init_scaffold_integration();
89356
89716
  import { spawn as spawn6 } from "node:child_process";
89357
- import { existsSync as existsSync78, mkdirSync as mkdirSync42, writeFileSync as writeFileSync34 } from "node:fs";
89358
- import { dirname as dirname24 } from "node:path";
89717
+ import { existsSync as existsSync79, mkdirSync as mkdirSync43, writeFileSync as writeFileSync35 } from "node:fs";
89718
+ import { dirname as dirname25 } from "node:path";
89359
89719
  var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
89360
89720
  var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
89361
89721
  var DEFAULT_VAULT_KEY = "notion/integration-token";
@@ -89365,10 +89725,10 @@ function buildNotionMcpArgs(opts) {
89365
89725
  }
89366
89726
  function defaultWriteHeartbeat(path7, contents) {
89367
89727
  try {
89368
- const dir = dirname24(path7);
89369
- if (!existsSync78(dir))
89370
- mkdirSync42(dir, { recursive: true });
89371
- writeFileSync34(path7, contents);
89728
+ const dir = dirname25(path7);
89729
+ if (!existsSync79(dir))
89730
+ mkdirSync43(dir, { recursive: true });
89731
+ writeFileSync35(path7, contents);
89372
89732
  } catch {}
89373
89733
  }
89374
89734
  async function runNotionMcpLauncher(opts, runtime) {
@@ -89478,7 +89838,7 @@ function registerNotionMcpLauncherCommand(program3) {
89478
89838
 
89479
89839
  // src/cli/deliver-file.ts
89480
89840
  init_client2();
89481
- import { readFileSync as readFileSync66, statSync as statSync34 } from "node:fs";
89841
+ import { readFileSync as readFileSync67, statSync as statSync35 } from "node:fs";
89482
89842
  import { basename as basename11 } from "node:path";
89483
89843
 
89484
89844
  // src/delivery/onedrive.ts
@@ -89817,8 +90177,8 @@ async function defaultResolveProvider() {
89817
90177
  }
89818
90178
  async function runDeliverFile(localPath, deps = {}) {
89819
90179
  const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
89820
- const sizeOf = deps.fileSize ?? ((p) => statSync34(p).size);
89821
- const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync66(p)));
90180
+ const sizeOf = deps.fileSize ?? ((p) => statSync35(p).size);
90181
+ const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync67(p)));
89822
90182
  const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
89823
90183
  let size;
89824
90184
  try {
@@ -90139,9 +90499,9 @@ function runRedactStdin() {
90139
90499
  }
90140
90500
 
90141
90501
  // src/cli/status-ask.ts
90142
- import { readFileSync as readFileSync70, existsSync as existsSync83, readdirSync as readdirSync29 } from "node:fs";
90143
- import { join as join81 } from "node:path";
90144
- import { homedir as homedir48 } from "node:os";
90502
+ import { readFileSync as readFileSync71, existsSync as existsSync84, readdirSync as readdirSync29 } from "node:fs";
90503
+ import { join as join82 } from "node:path";
90504
+ import { homedir as homedir49 } from "node:os";
90145
90505
 
90146
90506
  // src/status-ask/report.ts
90147
90507
  function parseJsonl(content) {
@@ -90415,7 +90775,7 @@ function runReport(opts) {
90415
90775
  for (const src of sources) {
90416
90776
  let content;
90417
90777
  try {
90418
- content = readFileSync70(src.path, "utf-8");
90778
+ content = readFileSync71(src.path, "utf-8");
90419
90779
  } catch (err) {
90420
90780
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
90421
90781
  `);
@@ -90462,7 +90822,7 @@ function runReport(opts) {
90462
90822
  function resolveSources(explicitPath) {
90463
90823
  if (explicitPath != null && explicitPath.trim() !== "") {
90464
90824
  const trimmed = explicitPath.trim();
90465
- if (!existsSync83(trimmed)) {
90825
+ if (!existsSync84(trimmed)) {
90466
90826
  process.stderr.write(`status-ask report: ${trimmed}: file not found
90467
90827
  `);
90468
90828
  process.exit(1);
@@ -90476,9 +90836,9 @@ function resolveSources(explicitPath) {
90476
90836
  const config = loadConfig();
90477
90837
  agentsDir = resolveAgentsDir(config);
90478
90838
  } catch {
90479
- agentsDir = join81(homedir48(), ".switchroom", "agents");
90839
+ agentsDir = join82(homedir49(), ".switchroom", "agents");
90480
90840
  }
90481
- if (!existsSync83(agentsDir))
90841
+ if (!existsSync84(agentsDir))
90482
90842
  return [];
90483
90843
  const sources = [];
90484
90844
  let entries;
@@ -90488,8 +90848,8 @@ function resolveSources(explicitPath) {
90488
90848
  return [];
90489
90849
  }
90490
90850
  for (const name of entries) {
90491
- const path8 = join81(agentsDir, name, "runtime-metrics.jsonl");
90492
- if (existsSync83(path8)) {
90851
+ const path8 = join82(agentsDir, name, "runtime-metrics.jsonl");
90852
+ if (existsSync84(path8)) {
90493
90853
  sources.push({ path: path8, agent: name });
90494
90854
  }
90495
90855
  }
@@ -90519,45 +90879,45 @@ var import_yaml21 = __toESM(require_dist(), 1);
90519
90879
  init_paths();
90520
90880
  import {
90521
90881
  closeSync as closeSync14,
90522
- existsSync as existsSync84,
90882
+ existsSync as existsSync85,
90523
90883
  fsyncSync as fsyncSync8,
90524
- mkdirSync as mkdirSync47,
90884
+ mkdirSync as mkdirSync48,
90525
90885
  openSync as openSync14,
90526
90886
  readdirSync as readdirSync30,
90527
- readFileSync as readFileSync71,
90887
+ readFileSync as readFileSync72,
90528
90888
  renameSync as renameSync19,
90529
- statSync as statSync35,
90530
- unlinkSync as unlinkSync17,
90889
+ statSync as statSync36,
90890
+ unlinkSync as unlinkSync18,
90531
90891
  writeSync as writeSync10
90532
90892
  } from "node:fs";
90533
- import { join as join82, resolve as resolve51 } from "node:path";
90893
+ import { join as join83, resolve as resolve51 } from "node:path";
90534
90894
  var STAGING_SUBDIR = ".staging";
90535
90895
  function overlayPathsFor(agent, opts = {}) {
90536
90896
  const base = opts.root ? resolve51(opts.root, agent) : resolve51(resolveDualPath(`~/.switchroom/agents/${agent}`));
90537
- const scheduleDir = join82(base, "schedule.d");
90538
- const scheduleStagingDir = join82(scheduleDir, STAGING_SUBDIR);
90539
- const skillsDir = join82(base, "skills.d");
90540
- const skillsStagingDir = join82(skillsDir, STAGING_SUBDIR);
90897
+ const scheduleDir = join83(base, "schedule.d");
90898
+ const scheduleStagingDir = join83(scheduleDir, STAGING_SUBDIR);
90899
+ const skillsDir = join83(base, "skills.d");
90900
+ const skillsStagingDir = join83(skillsDir, STAGING_SUBDIR);
90541
90901
  return {
90542
90902
  agentRoot: base,
90543
90903
  scheduleDir,
90544
90904
  scheduleStagingDir,
90545
90905
  skillsDir,
90546
90906
  skillsStagingDir,
90547
- lockPath: join82(base, ".lock"),
90907
+ lockPath: join83(base, ".lock"),
90548
90908
  stagingDir: scheduleStagingDir
90549
90909
  };
90550
90910
  }
90551
90911
  function ensureDirs(paths) {
90552
- mkdirSync47(paths.scheduleDir, { recursive: true });
90553
- mkdirSync47(paths.scheduleStagingDir, { recursive: true });
90912
+ mkdirSync48(paths.scheduleDir, { recursive: true });
90913
+ mkdirSync48(paths.scheduleStagingDir, { recursive: true });
90554
90914
  }
90555
90915
  function ensureSkillsDirs(paths) {
90556
- mkdirSync47(paths.skillsDir, { recursive: true });
90557
- mkdirSync47(paths.skillsStagingDir, { recursive: true });
90916
+ mkdirSync48(paths.skillsDir, { recursive: true });
90917
+ mkdirSync48(paths.skillsStagingDir, { recursive: true });
90558
90918
  }
90559
90919
  function withAgentLock(paths, fn) {
90560
- mkdirSync47(paths.agentRoot, { recursive: true });
90920
+ mkdirSync48(paths.agentRoot, { recursive: true });
90561
90921
  const start = Date.now();
90562
90922
  const TIMEOUT_MS = 5000;
90563
90923
  let fd = null;
@@ -90570,9 +90930,9 @@ function withAgentLock(paths, fn) {
90570
90930
  if (e.code !== "EEXIST")
90571
90931
  throw err;
90572
90932
  try {
90573
- const age = Date.now() - statSync35(paths.lockPath).mtimeMs;
90933
+ const age = Date.now() - statSync36(paths.lockPath).mtimeMs;
90574
90934
  if (age > 30000) {
90575
- unlinkSync17(paths.lockPath);
90935
+ unlinkSync18(paths.lockPath);
90576
90936
  continue;
90577
90937
  }
90578
90938
  } catch {}
@@ -90590,7 +90950,7 @@ function withAgentLock(paths, fn) {
90590
90950
  closeSync14(fd);
90591
90951
  } catch {}
90592
90952
  try {
90593
- unlinkSync17(paths.lockPath);
90953
+ unlinkSync18(paths.lockPath);
90594
90954
  } catch {}
90595
90955
  }
90596
90956
  }
@@ -90598,8 +90958,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
90598
90958
  const paths = overlayPathsFor(agent, opts);
90599
90959
  return withAgentLock(paths, () => {
90600
90960
  ensureDirs(paths);
90601
- const stagingPath = join82(paths.scheduleStagingDir, `${slug}.yaml`);
90602
- const finalPath = join82(paths.scheduleDir, `${slug}.yaml`);
90961
+ const stagingPath = join83(paths.scheduleStagingDir, `${slug}.yaml`);
90962
+ const finalPath = join83(paths.scheduleDir, `${slug}.yaml`);
90603
90963
  const fd = openSync14(stagingPath, "w", 384);
90604
90964
  try {
90605
90965
  writeSync10(fd, yamlText);
@@ -90615,8 +90975,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90615
90975
  const paths = overlayPathsFor(agent, opts);
90616
90976
  return withAgentLock(paths, () => {
90617
90977
  ensureSkillsDirs(paths);
90618
- const stagingPath = join82(paths.skillsStagingDir, `${slug}.yaml`);
90619
- const finalPath = join82(paths.skillsDir, `${slug}.yaml`);
90978
+ const stagingPath = join83(paths.skillsStagingDir, `${slug}.yaml`);
90979
+ const finalPath = join83(paths.skillsDir, `${slug}.yaml`);
90620
90980
  const fd = openSync14(stagingPath, "w", 384);
90621
90981
  try {
90622
90982
  writeSync10(fd, yamlText);
@@ -90631,24 +90991,24 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90631
90991
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
90632
90992
  const paths = overlayPathsFor(agent, opts);
90633
90993
  return withAgentLock(paths, () => {
90634
- const finalPath = join82(paths.skillsDir, `${slug}.yaml`);
90635
- if (!existsSync84(finalPath))
90994
+ const finalPath = join83(paths.skillsDir, `${slug}.yaml`);
90995
+ if (!existsSync85(finalPath))
90636
90996
  return false;
90637
- unlinkSync17(finalPath);
90997
+ unlinkSync18(finalPath);
90638
90998
  return true;
90639
90999
  });
90640
91000
  }
90641
91001
  function listSkillsOverlayEntries(agent, opts = {}) {
90642
91002
  const paths = overlayPathsFor(agent, opts);
90643
- if (!existsSync84(paths.skillsDir))
91003
+ if (!existsSync85(paths.skillsDir))
90644
91004
  return [];
90645
91005
  const out = [];
90646
91006
  for (const name of readdirSync30(paths.skillsDir)) {
90647
91007
  if (!/\.ya?ml$/i.test(name))
90648
91008
  continue;
90649
- const full = join82(paths.skillsDir, name);
91009
+ const full = join83(paths.skillsDir, name);
90650
91010
  try {
90651
- const raw = readFileSync71(full, "utf-8");
91011
+ const raw = readFileSync72(full, "utf-8");
90652
91012
  const slug = name.replace(/\.ya?ml$/i, "");
90653
91013
  out.push({ slug, path: full, raw });
90654
91014
  } catch {}
@@ -90658,24 +91018,24 @@ function listSkillsOverlayEntries(agent, opts = {}) {
90658
91018
  function deleteOverlayEntry(agent, slug, opts = {}) {
90659
91019
  const paths = overlayPathsFor(agent, opts);
90660
91020
  return withAgentLock(paths, () => {
90661
- const finalPath = join82(paths.scheduleDir, `${slug}.yaml`);
90662
- if (!existsSync84(finalPath))
91021
+ const finalPath = join83(paths.scheduleDir, `${slug}.yaml`);
91022
+ if (!existsSync85(finalPath))
90663
91023
  return false;
90664
- unlinkSync17(finalPath);
91024
+ unlinkSync18(finalPath);
90665
91025
  return true;
90666
91026
  });
90667
91027
  }
90668
91028
  function listOverlayEntries(agent, opts = {}) {
90669
91029
  const paths = overlayPathsFor(agent, opts);
90670
- if (!existsSync84(paths.scheduleDir))
91030
+ if (!existsSync85(paths.scheduleDir))
90671
91031
  return [];
90672
91032
  const out = [];
90673
91033
  for (const name of readdirSync30(paths.scheduleDir)) {
90674
91034
  if (!/\.ya?ml$/i.test(name))
90675
91035
  continue;
90676
- const full = join82(paths.scheduleDir, name);
91036
+ const full = join83(paths.scheduleDir, name);
90677
91037
  try {
90678
- const raw = readFileSync71(full, "utf-8");
91038
+ const raw = readFileSync72(full, "utf-8");
90679
91039
  const slug = name.replace(/\.ya?ml$/i, "");
90680
91040
  out.push({ slug, path: full, raw });
90681
91041
  } catch {}
@@ -90826,27 +91186,27 @@ function reconcileAgentCronOnly(agent) {
90826
91186
  // src/cli/agent-config-pending.ts
90827
91187
  import {
90828
91188
  closeSync as closeSync15,
90829
- existsSync as existsSync85,
91189
+ existsSync as existsSync86,
90830
91190
  fsyncSync as fsyncSync9,
90831
- mkdirSync as mkdirSync48,
91191
+ mkdirSync as mkdirSync49,
90832
91192
  openSync as openSync15,
90833
91193
  readdirSync as readdirSync31,
90834
- readFileSync as readFileSync72,
91194
+ readFileSync as readFileSync73,
90835
91195
  renameSync as renameSync20,
90836
- unlinkSync as unlinkSync18,
90837
- writeFileSync as writeFileSync39,
91196
+ unlinkSync as unlinkSync19,
91197
+ writeFileSync as writeFileSync40,
90838
91198
  writeSync as writeSync11
90839
91199
  } from "node:fs";
90840
- import { join as join83 } from "node:path";
91200
+ import { join as join84 } from "node:path";
90841
91201
  import { randomBytes as randomBytes15 } from "node:crypto";
90842
91202
  var STAGE_ID_PREFIX = "cap_";
90843
91203
  function pendingDir(agent, opts = {}) {
90844
91204
  const paths = overlayPathsFor(agent, opts);
90845
- return join83(paths.scheduleDir, ".pending");
91205
+ return join84(paths.scheduleDir, ".pending");
90846
91206
  }
90847
91207
  function ensurePendingDir(agent, opts = {}) {
90848
91208
  const dir = pendingDir(agent, opts);
90849
- mkdirSync48(dir, { recursive: true });
91209
+ mkdirSync49(dir, { recursive: true });
90850
91210
  return dir;
90851
91211
  }
90852
91212
  function newStageId() {
@@ -90855,8 +91215,8 @@ function newStageId() {
90855
91215
  function stagePendingScheduleEntry(opts) {
90856
91216
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
90857
91217
  const stageId = opts.stageId ?? newStageId();
90858
- const yamlPath = join83(dir, `${stageId}.yaml`);
90859
- const metaPath = join83(dir, `${stageId}.meta.json`);
91218
+ const yamlPath = join84(dir, `${stageId}.yaml`);
91219
+ const metaPath = join84(dir, `${stageId}.meta.json`);
90860
91220
  const meta = {
90861
91221
  v: 1,
90862
91222
  stage_id: stageId,
@@ -90877,25 +91237,25 @@ function stagePendingScheduleEntry(opts) {
90877
91237
  }
90878
91238
  renameSync20(yamlTmp, yamlPath);
90879
91239
  }
90880
- writeFileSync39(metaPath, JSON.stringify(meta, null, 2) + `
91240
+ writeFileSync40(metaPath, JSON.stringify(meta, null, 2) + `
90881
91241
  `, { mode: 384 });
90882
91242
  return { stageId, yamlPath, metaPath };
90883
91243
  }
90884
91244
  function listPendingScheduleEntries(agent, opts = {}) {
90885
91245
  const dir = pendingDir(agent, opts);
90886
- if (!existsSync85(dir))
91246
+ if (!existsSync86(dir))
90887
91247
  return [];
90888
91248
  const out = [];
90889
91249
  for (const name of readdirSync31(dir).sort()) {
90890
91250
  if (!name.endsWith(".meta.json"))
90891
91251
  continue;
90892
91252
  const stageId = name.slice(0, -".meta.json".length);
90893
- const metaPath = join83(dir, name);
90894
- const yamlPath = join83(dir, `${stageId}.yaml`);
90895
- if (!existsSync85(yamlPath))
91253
+ const metaPath = join84(dir, name);
91254
+ const yamlPath = join84(dir, `${stageId}.yaml`);
91255
+ if (!existsSync86(yamlPath))
90896
91256
  continue;
90897
91257
  try {
90898
- const meta = JSON.parse(readFileSync72(metaPath, "utf-8"));
91258
+ const meta = JSON.parse(readFileSync73(metaPath, "utf-8"));
90899
91259
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
90900
91260
  continue;
90901
91261
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -90910,12 +91270,12 @@ function commitPendingScheduleEntry(opts) {
90910
91270
  return { committed: false, reason: "not_found" };
90911
91271
  const slug = match.meta.entry.name ?? match.stageId;
90912
91272
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
90913
- const finalPath = join83(paths.scheduleDir, `${slug}.yaml`);
90914
- if (existsSync85(finalPath)) {
91273
+ const finalPath = join84(paths.scheduleDir, `${slug}.yaml`);
91274
+ if (existsSync86(finalPath)) {
90915
91275
  return { committed: false, reason: "slug_collision" };
90916
91276
  }
90917
91277
  renameSync20(match.yamlPath, finalPath);
90918
- unlinkSync18(match.metaPath);
91278
+ unlinkSync19(match.metaPath);
90919
91279
  return { committed: true, path: finalPath, slug };
90920
91280
  }
90921
91281
  function denyPendingScheduleEntry(opts) {
@@ -90924,16 +91284,16 @@ function denyPendingScheduleEntry(opts) {
90924
91284
  if (!match)
90925
91285
  return { denied: false, reason: "not_found" };
90926
91286
  try {
90927
- unlinkSync18(match.yamlPath);
91287
+ unlinkSync19(match.yamlPath);
90928
91288
  } catch {}
90929
91289
  try {
90930
- unlinkSync18(match.metaPath);
91290
+ unlinkSync19(match.metaPath);
90931
91291
  } catch {}
90932
91292
  return { denied: true };
90933
91293
  }
90934
91294
 
90935
91295
  // src/cli/agent-config-write.ts
90936
- import { existsSync as existsSync86, readFileSync as readFileSync73 } from "node:fs";
91296
+ import { existsSync as existsSync87, readFileSync as readFileSync74 } from "node:fs";
90937
91297
  import { execFileSync as execFileSync26 } from "node:child_process";
90938
91298
 
90939
91299
  // src/scheduler/schedule-report.ts
@@ -91345,8 +91705,8 @@ function scheduleRemove(opts) {
91345
91705
  }
91346
91706
  let priorContent = null;
91347
91707
  try {
91348
- if (existsSync86(match.path))
91349
- priorContent = readFileSync73(match.path, "utf-8");
91708
+ if (existsSync87(match.path))
91709
+ priorContent = readFileSync74(match.path, "utf-8");
91350
91710
  } catch {}
91351
91711
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
91352
91712
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -91549,7 +91909,7 @@ function registerAgentConfigWriteCommands(program3) {
91549
91909
  }
91550
91910
  let blob;
91551
91911
  if (opts.jsonl) {
91552
- blob = existsSync86(opts.jsonl) ? readFileSync73(opts.jsonl, "utf-8") : "";
91912
+ blob = existsSync87(opts.jsonl) ? readFileSync74(opts.jsonl, "utf-8") : "";
91553
91913
  } else {
91554
91914
  try {
91555
91915
  blob = execFileSync26("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
@@ -91581,11 +91941,11 @@ function registerAgentConfigWriteCommands(program3) {
91581
91941
 
91582
91942
  // src/cli/agent-config-skill-write.ts
91583
91943
  var import_yaml22 = __toESM(require_dist(), 1);
91584
- import { existsSync as existsSync87 } from "node:fs";
91944
+ import { existsSync as existsSync88 } from "node:fs";
91585
91945
  init_reconcile_default_skills();
91586
91946
  init_agent_config();
91587
91947
  var import_yaml23 = __toESM(require_dist(), 1);
91588
- import { join as join84 } from "node:path";
91948
+ import { join as join85 } from "node:path";
91589
91949
  var MAX_SKILLS_PER_AGENT = 20;
91590
91950
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
91591
91951
  function exitCodeFor2(code) {
@@ -91660,8 +92020,8 @@ function skillInstall(opts) {
91660
92020
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
91661
92021
  }
91662
92022
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
91663
- const skillPath = join84(poolDir, skillName);
91664
- if (!existsSync87(skillPath)) {
92023
+ const skillPath = join85(poolDir, skillName);
92024
+ if (!existsSync88(skillPath)) {
91665
92025
  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.`);
91666
92026
  }
91667
92027
  const yamlText = import_yaml22.stringify({ skills: [skillName] });
@@ -91825,21 +92185,21 @@ function registerAgentConfigSkillWriteCommands(program3) {
91825
92185
  // src/cli/skill.ts
91826
92186
  import {
91827
92187
  closeSync as closeSync16,
91828
- existsSync as existsSync88,
92188
+ existsSync as existsSync89,
91829
92189
  lstatSync as lstatSync9,
91830
- mkdirSync as mkdirSync49,
92190
+ mkdirSync as mkdirSync50,
91831
92191
  mkdtempSync as mkdtempSync5,
91832
92192
  openSync as openSync16,
91833
- readFileSync as readFileSync74,
92193
+ readFileSync as readFileSync75,
91834
92194
  readdirSync as readdirSync32,
91835
92195
  realpathSync as realpathSync7,
91836
92196
  renameSync as renameSync21,
91837
92197
  rmSync as rmSync18,
91838
- statSync as statSync36,
91839
- writeFileSync as writeFileSync40
92198
+ statSync as statSync37,
92199
+ writeFileSync as writeFileSync41
91840
92200
  } from "node:fs";
91841
- import { tmpdir as tmpdir5, homedir as homedir49 } from "node:os";
91842
- import { dirname as dirname29, join as join85, relative as relative2, resolve as resolve52 } from "node:path";
92201
+ import { tmpdir as tmpdir5, homedir as homedir50 } from "node:os";
92202
+ import { dirname as dirname30, join as join86, relative as relative2, resolve as resolve52 } from "node:path";
91843
92203
  import { spawnSync as spawnSync15 } from "node:child_process";
91844
92204
 
91845
92205
  // src/cli/skill-common.ts
@@ -92073,10 +92433,10 @@ function scanForClaudeP2(content) {
92073
92433
  function resolveSkillsPoolDir2(override) {
92074
92434
  const raw = override ?? "~/.switchroom/skills";
92075
92435
  if (raw.startsWith("~/")) {
92076
- return join85(homedir49(), raw.slice(2));
92436
+ return join86(homedir50(), raw.slice(2));
92077
92437
  }
92078
92438
  if (raw === "~")
92079
- return homedir49();
92439
+ return homedir50();
92080
92440
  return resolve52(raw);
92081
92441
  }
92082
92442
  function readStdinSync() {
@@ -92105,14 +92465,14 @@ function isTarballPath(p) {
92105
92465
  }
92106
92466
  function loadFromDir(dir) {
92107
92467
  const abs = realpathSync7(dir);
92108
- if (!statSync36(abs).isDirectory()) {
92468
+ if (!statSync37(abs).isDirectory()) {
92109
92469
  fail3(`--from path is not a directory: ${dir}`);
92110
92470
  }
92111
92471
  const files = {};
92112
92472
  const walk2 = (sub) => {
92113
92473
  const entries = readdirSync32(sub, { withFileTypes: true });
92114
92474
  for (const ent of entries) {
92115
- const full = join85(sub, ent.name);
92475
+ const full = join86(sub, ent.name);
92116
92476
  const rel = relative2(abs, full);
92117
92477
  if (ent.isSymbolicLink()) {
92118
92478
  fail3(`refusing to read symlink inside --from dir: ${rel}`);
@@ -92122,7 +92482,7 @@ function loadFromDir(dir) {
92122
92482
  continue;
92123
92483
  }
92124
92484
  if (ent.isFile()) {
92125
- const buf = readFileSync74(full);
92485
+ const buf = readFileSync75(full);
92126
92486
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
92127
92487
  }
92128
92488
  }
@@ -92147,7 +92507,7 @@ function loadFromTarball(tarPath) {
92147
92507
  fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
92148
92508
  }
92149
92509
  }
92150
- const staging = mkdtempSync5(join85(tmpdir5(), "skill-apply-extract-"));
92510
+ const staging = mkdtempSync5(join86(tmpdir5(), "skill-apply-extract-"));
92151
92511
  try {
92152
92512
  const flags = isGz ? ["-xzf"] : ["-xf"];
92153
92513
  const r = spawnSync15("tar", [
@@ -92171,7 +92531,7 @@ function loadFromTarball(tarPath) {
92171
92531
  }
92172
92532
  }
92173
92533
  function loadSingleFile(filePath) {
92174
- const content = readFileSync74(filePath, "utf-8");
92534
+ const content = readFileSync75(filePath, "utf-8");
92175
92535
  return { "SKILL.md": content };
92176
92536
  }
92177
92537
  function loadFromStdin() {
@@ -92233,10 +92593,10 @@ function validatePayload(name, files) {
92233
92593
  errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
92234
92594
  }
92235
92595
  } else if (PY_SCRIPT_RE2.test(path8)) {
92236
- const tmp = mkdtempSync5(join85(tmpdir5(), "skill-apply-py-"));
92237
- const tmpPy = join85(tmp, "check.py");
92596
+ const tmp = mkdtempSync5(join86(tmpdir5(), "skill-apply-py-"));
92597
+ const tmpPy = join86(tmp, "check.py");
92238
92598
  try {
92239
- writeFileSync40(tmpPy, content);
92599
+ writeFileSync41(tmpPy, content);
92240
92600
  const r = spawnSync15("python3", ["-m", "py_compile", tmpPy], {
92241
92601
  encoding: "utf-8"
92242
92602
  });
@@ -92254,15 +92614,15 @@ function validatePayload(name, files) {
92254
92614
  function diffSummary(currentDir, files) {
92255
92615
  const lines = [];
92256
92616
  const currentFiles = {};
92257
- if (existsSync88(currentDir)) {
92617
+ if (existsSync89(currentDir)) {
92258
92618
  const walk2 = (sub) => {
92259
92619
  for (const ent of readdirSync32(sub, { withFileTypes: true })) {
92260
- const full = join85(sub, ent.name);
92620
+ const full = join86(sub, ent.name);
92261
92621
  const rel = relative2(currentDir, full);
92262
92622
  if (ent.isDirectory()) {
92263
92623
  walk2(full);
92264
92624
  } else if (ent.isFile()) {
92265
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync74(full, "utf-8");
92625
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync75(full, "utf-8");
92266
92626
  }
92267
92627
  }
92268
92628
  };
@@ -92290,10 +92650,10 @@ function diffSummary(currentDir, files) {
92290
92650
  `);
92291
92651
  }
92292
92652
  function writePayload(poolDir, name, files) {
92293
- if (!existsSync88(poolDir)) {
92294
- mkdirSync49(poolDir, { recursive: true, mode: 493 });
92653
+ if (!existsSync89(poolDir)) {
92654
+ mkdirSync50(poolDir, { recursive: true, mode: 493 });
92295
92655
  }
92296
- const target = join85(poolDir, name);
92656
+ const target = join86(poolDir, name);
92297
92657
  let targetIsSymlink = false;
92298
92658
  try {
92299
92659
  const st = lstatSync9(target);
@@ -92304,15 +92664,15 @@ function writePayload(poolDir, name, files) {
92304
92664
  if (targetIsSymlink) {
92305
92665
  fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
92306
92666
  }
92307
- const staging = mkdtempSync5(join85(poolDir, `.skill-apply-stage-${name}-`));
92667
+ const staging = mkdtempSync5(join86(poolDir, `.skill-apply-stage-${name}-`));
92308
92668
  let oldRename = null;
92309
92669
  try {
92310
92670
  for (const [path8, content] of Object.entries(files)) {
92311
- const full = join85(staging, path8);
92312
- mkdirSync49(dirname29(full), { recursive: true, mode: 493 });
92671
+ const full = join86(staging, path8);
92672
+ mkdirSync50(dirname30(full), { recursive: true, mode: 493 });
92313
92673
  const fd = openSync16(full, "wx");
92314
92674
  try {
92315
- writeFileSync40(fd, content);
92675
+ writeFileSync41(fd, content);
92316
92676
  } finally {
92317
92677
  closeSync16(fd);
92318
92678
  }
@@ -92339,9 +92699,9 @@ function writePayload(poolDir, name, files) {
92339
92699
  try {
92340
92700
  rmSync18(staging, { recursive: true, force: true });
92341
92701
  } catch {}
92342
- if (oldRename && existsSync88(oldRename)) {
92702
+ if (oldRename && existsSync89(oldRename)) {
92343
92703
  try {
92344
- if (existsSync88(target)) {
92704
+ if (existsSync89(target)) {
92345
92705
  rmSync18(target, { recursive: true, force: true });
92346
92706
  }
92347
92707
  renameSync21(oldRename, target);
@@ -92362,10 +92722,10 @@ function registerSkillCommand(program3) {
92362
92722
  files = loadFromStdin();
92363
92723
  } else {
92364
92724
  const fromPath = resolve52(opts.from);
92365
- if (!existsSync88(fromPath)) {
92725
+ if (!existsSync89(fromPath)) {
92366
92726
  fail3(`--from path does not exist: ${opts.from}`);
92367
92727
  }
92368
- const st = statSync36(fromPath);
92728
+ const st = statSync37(fromPath);
92369
92729
  if (st.isDirectory()) {
92370
92730
  files = loadFromDir(fromPath);
92371
92731
  } else if (isTarballPath(fromPath)) {
@@ -92386,7 +92746,7 @@ function registerSkillCommand(program3) {
92386
92746
  }
92387
92747
  const config = loadConfig();
92388
92748
  const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
92389
- const currentDir = join85(poolDir, name);
92749
+ const currentDir = join86(poolDir, name);
92390
92750
  console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
92391
92751
  console.log(source_default.bold("Diff vs current pool content:"));
92392
92752
  console.log(diffSummary(currentDir, files));
@@ -92418,21 +92778,21 @@ function sumBytes(files) {
92418
92778
  init_esm();
92419
92779
  import {
92420
92780
  closeSync as closeSync17,
92421
- existsSync as existsSync89,
92781
+ existsSync as existsSync90,
92422
92782
  lstatSync as lstatSync10,
92423
- mkdirSync as mkdirSync50,
92783
+ mkdirSync as mkdirSync51,
92424
92784
  mkdtempSync as mkdtempSync6,
92425
92785
  openSync as openSync17,
92426
- readFileSync as readFileSync75,
92786
+ readFileSync as readFileSync76,
92427
92787
  readdirSync as readdirSync33,
92428
92788
  renameSync as renameSync22,
92429
92789
  rmSync as rmSync19,
92430
- statSync as statSync37,
92790
+ statSync as statSync38,
92431
92791
  utimesSync,
92432
- writeFileSync as writeFileSync41
92792
+ writeFileSync as writeFileSync42
92433
92793
  } from "node:fs";
92434
- import { dirname as dirname30, join as join86, relative as relative3, resolve as resolve53 } from "node:path";
92435
- import { homedir as homedir50, tmpdir as tmpdir6 } from "node:os";
92794
+ import { dirname as dirname31, join as join87, relative as relative3, resolve as resolve53 } from "node:path";
92795
+ import { homedir as homedir51, tmpdir as tmpdir6 } from "node:os";
92436
92796
  import { spawnSync as spawnSync16 } from "node:child_process";
92437
92797
  init_helpers();
92438
92798
  init_agent_config();
@@ -92443,15 +92803,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
92443
92803
  var PERSONAL_SKILLS_SUBPATH = "personal-skills";
92444
92804
  function resolveConfigSkillsDir(agent) {
92445
92805
  const override = process.env.SWITCHROOM_CONFIG_DIR;
92446
- const candidate = override ? resolve53(override) : join86(homedir50(), ".switchroom-config");
92447
- if (!existsSync89(candidate))
92806
+ const candidate = override ? resolve53(override) : join87(homedir51(), ".switchroom-config");
92807
+ if (!existsSync90(candidate))
92448
92808
  return null;
92449
- return join86(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
92809
+ return join87(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
92450
92810
  }
92451
92811
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
92452
92812
  function sweepMirrorPriors(configSkillsRoot) {
92453
92813
  try {
92454
- if (!existsSync89(configSkillsRoot))
92814
+ if (!existsSync90(configSkillsRoot))
92455
92815
  return;
92456
92816
  const now = Date.now();
92457
92817
  for (const ent of readdirSync33(configSkillsRoot)) {
@@ -92464,7 +92824,7 @@ function sweepMirrorPriors(configSkillsRoot) {
92464
92824
  if (now - ts < MIRROR_PRIOR_TTL_MS)
92465
92825
  continue;
92466
92826
  try {
92467
- rmSync19(join86(configSkillsRoot, ent), { recursive: true, force: true });
92827
+ rmSync19(join87(configSkillsRoot, ent), { recursive: true, force: true });
92468
92828
  } catch {}
92469
92829
  }
92470
92830
  } catch {}
@@ -92473,7 +92833,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
92473
92833
  const configSkillsRoot = resolveConfigSkillsDir(agent);
92474
92834
  if (!configSkillsRoot)
92475
92835
  return;
92476
- const dest = join86(configSkillsRoot, name);
92836
+ const dest = join87(configSkillsRoot, name);
92477
92837
  try {
92478
92838
  if (liveSkillDir !== null) {
92479
92839
  try {
@@ -92487,32 +92847,32 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
92487
92847
  }
92488
92848
  if (liveSkillDir === null) {
92489
92849
  sweepMirrorPriors(configSkillsRoot);
92490
- if (existsSync89(dest)) {
92491
- const trash = join86(configSkillsRoot, `.${name}-trash-${Date.now()}`);
92850
+ if (existsSync90(dest)) {
92851
+ const trash = join87(configSkillsRoot, `.${name}-trash-${Date.now()}`);
92492
92852
  renameSync22(dest, trash);
92493
92853
  }
92494
92854
  return;
92495
92855
  }
92496
- mkdirSync50(configSkillsRoot, { recursive: true, mode: 493 });
92856
+ mkdirSync51(configSkillsRoot, { recursive: true, mode: 493 });
92497
92857
  sweepMirrorPriors(configSkillsRoot);
92498
- const staging = mkdtempSync6(join86(configSkillsRoot, `.${name}-staging-`));
92858
+ const staging = mkdtempSync6(join87(configSkillsRoot, `.${name}-staging-`));
92499
92859
  const walk2 = (src, dst) => {
92500
- mkdirSync50(dst, { recursive: true, mode: 493 });
92860
+ mkdirSync51(dst, { recursive: true, mode: 493 });
92501
92861
  for (const ent of readdirSync33(src, { withFileTypes: true })) {
92502
- const s = join86(src, ent.name);
92503
- const d = join86(dst, ent.name);
92862
+ const s = join87(src, ent.name);
92863
+ const d = join87(dst, ent.name);
92504
92864
  if (ent.isSymbolicLink())
92505
92865
  continue;
92506
92866
  if (ent.isDirectory())
92507
92867
  walk2(s, d);
92508
92868
  else if (ent.isFile()) {
92509
- writeFileSync41(d, readFileSync75(s));
92869
+ writeFileSync42(d, readFileSync76(s));
92510
92870
  }
92511
92871
  }
92512
92872
  };
92513
92873
  walk2(liveSkillDir, staging);
92514
- if (existsSync89(dest)) {
92515
- const prior = join86(configSkillsRoot, `.${name}-prior-${Date.now()}`);
92874
+ if (existsSync90(dest)) {
92875
+ const prior = join87(configSkillsRoot, `.${name}-prior-${Date.now()}`);
92516
92876
  renameSync22(dest, prior);
92517
92877
  }
92518
92878
  renameSync22(staging, dest);
@@ -92539,17 +92899,17 @@ function resolveAgent(opts) {
92539
92899
  function resolveAgentsRoot(opts) {
92540
92900
  if (opts.root)
92541
92901
  return resolve53(opts.root);
92542
- return join86(homedir50(), ".switchroom", "agents");
92902
+ return join87(homedir51(), ".switchroom", "agents");
92543
92903
  }
92544
92904
  function personalSkillDir(agentsRoot, agent, name) {
92545
- return join86(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
92905
+ return join87(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
92546
92906
  }
92547
92907
  function trashDir(agentsRoot, agent) {
92548
- return join86(agentsRoot, agent, ".claude", TRASH_DIRNAME);
92908
+ return join87(agentsRoot, agent, ".claude", TRASH_DIRNAME);
92549
92909
  }
92550
92910
  function countPersonalSkills(agentsRoot, agent) {
92551
- const skillsDir = join86(agentsRoot, agent, ".claude", "skills");
92552
- if (!existsSync89(skillsDir))
92911
+ const skillsDir = join87(agentsRoot, agent, ".claude", "skills");
92912
+ if (!existsSync90(skillsDir))
92553
92913
  return 0;
92554
92914
  let n = 0;
92555
92915
  for (const ent of readdirSync33(skillsDir, { withFileTypes: true })) {
@@ -92580,13 +92940,13 @@ function readStdinSync2() {
92580
92940
  }
92581
92941
  function loadFromDir2(dir) {
92582
92942
  const abs = resolve53(dir);
92583
- if (!statSync37(abs).isDirectory()) {
92943
+ if (!statSync38(abs).isDirectory()) {
92584
92944
  fail4(`--from path is not a directory: ${dir}`);
92585
92945
  }
92586
92946
  const files = {};
92587
92947
  const walk2 = (sub) => {
92588
92948
  for (const ent of readdirSync33(sub, { withFileTypes: true })) {
92589
- const full = join86(sub, ent.name);
92949
+ const full = join87(sub, ent.name);
92590
92950
  if (ent.isSymbolicLink()) {
92591
92951
  fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
92592
92952
  }
@@ -92596,7 +92956,7 @@ function loadFromDir2(dir) {
92596
92956
  }
92597
92957
  if (ent.isFile()) {
92598
92958
  const rel = relative3(abs, full).replace(/\\/g, "/");
92599
- files[rel] = readFileSync75(full, "utf-8");
92959
+ files[rel] = readFileSync76(full, "utf-8");
92600
92960
  }
92601
92961
  }
92602
92962
  };
@@ -92639,10 +92999,10 @@ function behavioralValidate(files) {
92639
92999
  errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
92640
93000
  }
92641
93001
  } else if (PY_SCRIPT_RE.test(path8)) {
92642
- const tmp = mkdtempSync6(join86(tmpdir6(), "skill-personal-py-"));
92643
- const tmpPy = join86(tmp, "check.py");
93002
+ const tmp = mkdtempSync6(join87(tmpdir6(), "skill-personal-py-"));
93003
+ const tmpPy = join87(tmp, "check.py");
92644
93004
  try {
92645
- writeFileSync41(tmpPy, content);
93005
+ writeFileSync42(tmpPy, content);
92646
93006
  const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
92647
93007
  encoding: "utf-8"
92648
93008
  });
@@ -92658,15 +93018,15 @@ function behavioralValidate(files) {
92658
93018
  }
92659
93019
  function sweepTrash(agentsRoot, agent) {
92660
93020
  const trash = trashDir(agentsRoot, agent);
92661
- if (!existsSync89(trash))
93021
+ if (!existsSync90(trash))
92662
93022
  return;
92663
93023
  const now = Date.now();
92664
93024
  for (const ent of readdirSync33(trash, { withFileTypes: true })) {
92665
93025
  if (!ent.isDirectory())
92666
93026
  continue;
92667
- const entPath = join86(trash, ent.name);
93027
+ const entPath = join87(trash, ent.name);
92668
93028
  try {
92669
- const st = statSync37(entPath);
93029
+ const st = statSync38(entPath);
92670
93030
  if (now - st.mtimeMs > TRASH_TTL_MS) {
92671
93031
  rmSync19(entPath, { recursive: true, force: true });
92672
93032
  }
@@ -92684,16 +93044,16 @@ function writePersonalSkill(targetDir, files) {
92684
93044
  if (targetIsSymlink) {
92685
93045
  fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
92686
93046
  }
92687
- mkdirSync50(dirname30(targetDir), { recursive: true, mode: 493 });
92688
- const staging = mkdtempSync6(join86(dirname30(targetDir), `.skill-personal-stage-`));
93047
+ mkdirSync51(dirname31(targetDir), { recursive: true, mode: 493 });
93048
+ const staging = mkdtempSync6(join87(dirname31(targetDir), `.skill-personal-stage-`));
92689
93049
  let oldRename = null;
92690
93050
  try {
92691
93051
  for (const [path8, content] of Object.entries(files)) {
92692
- const full = join86(staging, path8);
92693
- mkdirSync50(dirname30(full), { recursive: true, mode: 493 });
93052
+ const full = join87(staging, path8);
93053
+ mkdirSync51(dirname31(full), { recursive: true, mode: 493 });
92694
93054
  const fd = openSync17(full, "wx");
92695
93055
  try {
92696
- writeFileSync41(fd, content);
93056
+ writeFileSync42(fd, content);
92697
93057
  } finally {
92698
93058
  closeSync17(fd);
92699
93059
  }
@@ -92720,9 +93080,9 @@ function writePersonalSkill(targetDir, files) {
92720
93080
  try {
92721
93081
  rmSync19(staging, { recursive: true, force: true });
92722
93082
  } catch {}
92723
- if (oldRename && existsSync89(oldRename)) {
93083
+ if (oldRename && existsSync90(oldRename)) {
92724
93084
  try {
92725
- if (existsSync89(targetDir)) {
93085
+ if (existsSync90(targetDir)) {
92726
93086
  rmSync19(targetDir, { recursive: true, force: true });
92727
93087
  }
92728
93088
  renameSync22(oldRename, targetDir);
@@ -92788,15 +93148,15 @@ function loadFiles(opts) {
92788
93148
  return loadFromStdin2();
92789
93149
  }
92790
93150
  const p = resolve53(opts.from);
92791
- if (!existsSync89(p)) {
93151
+ if (!existsSync90(p)) {
92792
93152
  fail4(`--from path does not exist: ${opts.from}`);
92793
93153
  }
92794
- const st = statSync37(p);
93154
+ const st = statSync38(p);
92795
93155
  if (st.isDirectory()) {
92796
93156
  return loadFromDir2(p);
92797
93157
  }
92798
93158
  if (p.endsWith(".md")) {
92799
- return { "SKILL.md": readFileSync75(p, "utf-8") };
93159
+ return { "SKILL.md": readFileSync76(p, "utf-8") };
92800
93160
  }
92801
93161
  fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
92802
93162
  }
@@ -92836,10 +93196,10 @@ function editPersonalAction(name, opts) {
92836
93196
  }
92837
93197
  var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
92838
93198
  function defaultSharedRoot() {
92839
- return join86(homedir50(), ".switchroom", "skills");
93199
+ return join87(homedir51(), ".switchroom", "skills");
92840
93200
  }
92841
93201
  function defaultBundledRoot() {
92842
- return join86(homedir50(), ".switchroom", "skills", "_bundled");
93202
+ return join87(homedir51(), ".switchroom", "skills", "_bundled");
92843
93203
  }
92844
93204
  function resolveCloneSource(source, opts) {
92845
93205
  const m = CLONE_SOURCE_RE.exec(source);
@@ -92849,8 +93209,8 @@ function resolveCloneSource(source, opts) {
92849
93209
  const tier = m[1];
92850
93210
  const slug = m[2];
92851
93211
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
92852
- const dir = join86(root, slug);
92853
- if (!existsSync89(dir)) {
93212
+ const dir = join87(root, slug);
93213
+ if (!existsSync90(dir)) {
92854
93214
  fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
92855
93215
  }
92856
93216
  const st = lstatSync10(dir);
@@ -92865,7 +93225,7 @@ function readSourceFiles(dir) {
92865
93225
  const skipped = [];
92866
93226
  const walk2 = (sub) => {
92867
93227
  for (const ent of readdirSync33(sub, { withFileTypes: true })) {
92868
- const full = join86(sub, ent.name);
93228
+ const full = join87(sub, ent.name);
92869
93229
  if (ent.isSymbolicLink()) {
92870
93230
  continue;
92871
93231
  }
@@ -92885,7 +93245,7 @@ function readSourceFiles(dir) {
92885
93245
  fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
92886
93246
  }
92887
93247
  } catch {}
92888
- files[rel] = readFileSync75(full, "utf-8");
93248
+ files[rel] = readFileSync76(full, "utf-8");
92889
93249
  }
92890
93250
  }
92891
93251
  };
@@ -92974,9 +93334,9 @@ function removePersonalAction(name, opts) {
92974
93334
  throw err2;
92975
93335
  }
92976
93336
  const trashRoot2 = trashDir(agentsRoot, agent);
92977
- mkdirSync50(trashRoot2, { recursive: true, mode: 493 });
93337
+ mkdirSync51(trashRoot2, { recursive: true, mode: 493 });
92978
93338
  const ts = Date.now();
92979
- const trashTarget = join86(trashRoot2, `${name}-${ts}`);
93339
+ const trashTarget = join87(trashRoot2, `${name}-${ts}`);
92980
93340
  renameSync22(target, trashTarget);
92981
93341
  const now = new Date(ts);
92982
93342
  utimesSync(trashTarget, now, now);
@@ -92995,16 +93355,16 @@ function listPersonalAction(opts) {
92995
93355
  const agent = resolveAgent(opts);
92996
93356
  const agentsRoot = resolveAgentsRoot(opts);
92997
93357
  sweepTrash(agentsRoot, agent);
92998
- const skillsDir = join86(agentsRoot, agent, ".claude", "skills");
93358
+ const skillsDir = join87(agentsRoot, agent, ".claude", "skills");
92999
93359
  const personal = [];
93000
- if (existsSync89(skillsDir)) {
93360
+ if (existsSync90(skillsDir)) {
93001
93361
  for (const ent of readdirSync33(skillsDir, { withFileTypes: true })) {
93002
93362
  if (!ent.isDirectory())
93003
93363
  continue;
93004
93364
  if (!ent.name.startsWith(PERSONAL_PREFIX))
93005
93365
  continue;
93006
93366
  const skillName = ent.name.slice(PERSONAL_PREFIX.length);
93007
- const skillPath = join86(skillsDir, ent.name);
93367
+ const skillPath = join87(skillsDir, ent.name);
93008
93368
  let fileCount = 0;
93009
93369
  let totalBytes = 0;
93010
93370
  const walk2 = (sub) => {
@@ -93012,10 +93372,10 @@ function listPersonalAction(opts) {
93012
93372
  if (e.isFile()) {
93013
93373
  fileCount += 1;
93014
93374
  try {
93015
- totalBytes += statSync37(join86(sub, e.name)).size;
93375
+ totalBytes += statSync38(join87(sub, e.name)).size;
93016
93376
  } catch {}
93017
93377
  } else if (e.isDirectory()) {
93018
- walk2(join86(sub, e.name));
93378
+ walk2(join87(sub, e.name));
93019
93379
  }
93020
93380
  }
93021
93381
  };
@@ -93053,12 +93413,12 @@ function registerSkillPersonalCommands(program3) {
93053
93413
 
93054
93414
  // src/cli/self-improve-propose-skill.ts
93055
93415
  import { createConnection as createConnection4 } from "node:net";
93056
- import { homedir as homedir51 } from "node:os";
93057
- import { join as join87 } from "node:path";
93058
- import { readFileSync as readFileSync76 } from "node:fs";
93416
+ import { homedir as homedir52 } from "node:os";
93417
+ import { join as join88 } from "node:path";
93418
+ import { readFileSync as readFileSync77 } from "node:fs";
93059
93419
  var IPC_CONNECT_TIMEOUT_MS = 5000;
93060
93420
  function gatewaySocketPath() {
93061
- return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join87(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join87(homedir51(), ".claude", "channels", "telegram", "gateway.sock"));
93421
+ return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join88(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join88(homedir52(), ".claude", "channels", "telegram", "gateway.sock"));
93062
93422
  }
93063
93423
  function fail5(msg, code = 1) {
93064
93424
  console.error(msg);
@@ -93093,7 +93453,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
93093
93453
  fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
93094
93454
  let draft;
93095
93455
  try {
93096
- draft = JSON.parse(readFileSync76(opts.draft, "utf-8"));
93456
+ draft = JSON.parse(readFileSync77(opts.draft, "utf-8"));
93097
93457
  } catch (e) {
93098
93458
  fail5(`failed to read/parse --draft: ${e.message}`);
93099
93459
  }
@@ -93130,28 +93490,28 @@ function registerSelfImproveProposeSkillCommand(program3) {
93130
93490
  init_esm();
93131
93491
  init_helpers();
93132
93492
  var import_yaml25 = __toESM(require_dist(), 1);
93133
- import { existsSync as existsSync90, readdirSync as readdirSync34, readFileSync as readFileSync77, statSync as statSync38 } from "node:fs";
93134
- import { homedir as homedir52 } from "node:os";
93135
- import { join as join88, resolve as resolve54 } from "node:path";
93493
+ import { existsSync as existsSync91, readdirSync as readdirSync34, readFileSync as readFileSync78, statSync as statSync39 } from "node:fs";
93494
+ import { homedir as homedir53 } from "node:os";
93495
+ import { join as join89, resolve as resolve54 } from "node:path";
93136
93496
  var PERSONAL_PREFIX2 = "personal-";
93137
93497
  var BUNDLED_SUBDIR = "_bundled";
93138
93498
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
93139
93499
  function defaultAgentsRoot() {
93140
- return resolve54(homedir52(), ".switchroom/agents");
93500
+ return resolve54(homedir53(), ".switchroom/agents");
93141
93501
  }
93142
93502
  function defaultSharedRoot2() {
93143
- return resolve54(homedir52(), ".switchroom/skills");
93503
+ return resolve54(homedir53(), ".switchroom/skills");
93144
93504
  }
93145
93505
  function defaultBundledRoot2() {
93146
- return resolve54(homedir52(), ".switchroom/skills/_bundled");
93506
+ return resolve54(homedir53(), ".switchroom/skills/_bundled");
93147
93507
  }
93148
93508
  function readSkillFrontmatter(skillDir) {
93149
- const mdPath = join88(skillDir, "SKILL.md");
93150
- if (!existsSync90(mdPath))
93509
+ const mdPath = join89(skillDir, "SKILL.md");
93510
+ if (!existsSync91(mdPath))
93151
93511
  return null;
93152
93512
  let content;
93153
93513
  try {
93154
- content = readFileSync77(mdPath, "utf-8");
93514
+ content = readFileSync78(mdPath, "utf-8");
93155
93515
  } catch {
93156
93516
  return null;
93157
93517
  }
@@ -93179,9 +93539,9 @@ function readSkillFrontmatter(skillDir) {
93179
93539
  return { fm: parsed };
93180
93540
  }
93181
93541
  function statSkillMd(skillDir) {
93182
- const mdPath = join88(skillDir, "SKILL.md");
93542
+ const mdPath = join89(skillDir, "SKILL.md");
93183
93543
  try {
93184
- const st = statSync38(mdPath);
93544
+ const st = statSync39(mdPath);
93185
93545
  return { size: st.size, mtime: st.mtime.toISOString() };
93186
93546
  } catch {
93187
93547
  return null;
@@ -93190,8 +93550,8 @@ function statSkillMd(skillDir) {
93190
93550
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
93191
93551
  if (!AGENT_NAME_RE3.test(agent))
93192
93552
  return [];
93193
- const skillsDir = join88(agentsRoot, agent, ".claude/skills");
93194
- if (!existsSync90(skillsDir))
93553
+ const skillsDir = join89(agentsRoot, agent, ".claude/skills");
93554
+ if (!existsSync91(skillsDir))
93195
93555
  return [];
93196
93556
  const out = [];
93197
93557
  let entries;
@@ -93203,9 +93563,9 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
93203
93563
  for (const ent of entries) {
93204
93564
  if (!ent.startsWith(PERSONAL_PREFIX2))
93205
93565
  continue;
93206
- const dirPath = join88(skillsDir, ent);
93566
+ const dirPath = join89(skillsDir, ent);
93207
93567
  try {
93208
- if (!statSync38(dirPath).isDirectory())
93568
+ if (!statSync39(dirPath).isDirectory())
93209
93569
  continue;
93210
93570
  } catch {
93211
93571
  continue;
@@ -93229,7 +93589,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
93229
93589
  return out;
93230
93590
  }
93231
93591
  function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
93232
- if (!existsSync90(sharedRoot))
93592
+ if (!existsSync91(sharedRoot))
93233
93593
  return [];
93234
93594
  const out = [];
93235
93595
  let entries;
@@ -93243,9 +93603,9 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
93243
93603
  continue;
93244
93604
  if (ent.startsWith("."))
93245
93605
  continue;
93246
- const dirPath = join88(sharedRoot, ent);
93606
+ const dirPath = join89(sharedRoot, ent);
93247
93607
  try {
93248
- if (!statSync38(dirPath).isDirectory())
93608
+ if (!statSync39(dirPath).isDirectory())
93249
93609
  continue;
93250
93610
  } catch {
93251
93611
  continue;
@@ -93267,7 +93627,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
93267
93627
  return out;
93268
93628
  }
93269
93629
  function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
93270
- if (!existsSync90(bundledRoot))
93630
+ if (!existsSync91(bundledRoot))
93271
93631
  return [];
93272
93632
  const out = [];
93273
93633
  let entries;
@@ -93279,9 +93639,9 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
93279
93639
  for (const ent of entries) {
93280
93640
  if (ent.startsWith("."))
93281
93641
  continue;
93282
- const dirPath = join88(bundledRoot, ent);
93642
+ const dirPath = join89(bundledRoot, ent);
93283
93643
  try {
93284
- if (!statSync38(dirPath).isDirectory())
93644
+ if (!statSync39(dirPath).isDirectory())
93285
93645
  continue;
93286
93646
  } catch {
93287
93647
  continue;
@@ -93425,18 +93785,18 @@ init_source();
93425
93785
  init_helpers();
93426
93786
  init_operator_uid();
93427
93787
  import {
93428
- existsSync as existsSync92,
93429
- mkdirSync as mkdirSync51,
93788
+ existsSync as existsSync93,
93789
+ mkdirSync as mkdirSync52,
93430
93790
  readdirSync as readdirSync35,
93431
- readFileSync as readFileSync79,
93432
- writeFileSync as writeFileSync42,
93433
- statSync as statSync39,
93791
+ readFileSync as readFileSync80,
93792
+ writeFileSync as writeFileSync43,
93793
+ statSync as statSync40,
93434
93794
  lstatSync as lstatSync11,
93435
93795
  realpathSync as realpathSync8,
93436
93796
  copyFileSync as copyFileSync13
93437
93797
  } from "node:fs";
93438
- import { homedir as homedir53 } from "node:os";
93439
- import { join as join89 } from "node:path";
93798
+ import { homedir as homedir54 } from "node:os";
93799
+ import { join as join90 } from "node:path";
93440
93800
  import { spawnSync as spawnSync20 } from "node:child_process";
93441
93801
 
93442
93802
  // src/cli/deploy-version-guard.ts
@@ -93658,7 +94018,7 @@ networks:
93658
94018
  # operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
93659
94019
  `;
93660
94020
  }
93661
- function resolveHostdHostHome(env2 = process.env, home2 = homedir53()) {
94021
+ function resolveHostdHostHome(env2 = process.env, home2 = homedir54()) {
93662
94022
  const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
93663
94023
  const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
93664
94024
  if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
@@ -93669,7 +94029,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir53()) {
93669
94029
  return resolved;
93670
94030
  }
93671
94031
  function resolveHostdSkillsTarget(hostHome) {
93672
- const skillsPath = join89(hostHome, ".switchroom", "skills");
94032
+ const skillsPath = join90(hostHome, ".switchroom", "skills");
93673
94033
  let st;
93674
94034
  try {
93675
94035
  st = lstatSync11(skillsPath);
@@ -93686,21 +94046,21 @@ function resolveHostdSkillsTarget(hostHome) {
93686
94046
  console.warn(`switchroom hostd install: ~/.switchroom/skills is a symlink whose target ` + `does not resolve (dangling) \u2014 skipping the skills bind mount. Bundled ` + `skills will be unavailable to rollout/update until the symlink is fixed.`);
93687
94047
  return;
93688
94048
  }
93689
- if (!existsSync92(target)) {
94049
+ if (!existsSync93(target)) {
93690
94050
  console.warn(`switchroom hostd install: ~/.switchroom/skills resolves to "${target}", ` + `which does not exist \u2014 skipping the skills bind mount. Bundled skills ` + `will be unavailable to rollout/update until the symlink target exists.`);
93691
94051
  return;
93692
94052
  }
93693
94053
  return target;
93694
94054
  }
93695
94055
  function hostdDir() {
93696
- return join89(homedir53(), ".switchroom", "hostd");
94056
+ return join90(homedir54(), ".switchroom", "hostd");
93697
94057
  }
93698
94058
  function hostdComposePath() {
93699
- return join89(hostdDir(), "docker-compose.yml");
94059
+ return join90(hostdDir(), "docker-compose.yml");
93700
94060
  }
93701
94061
  function backupExistingCompose() {
93702
94062
  const p = hostdComposePath();
93703
- if (!existsSync92(p))
94063
+ if (!existsSync93(p))
93704
94064
  return null;
93705
94065
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
93706
94066
  const bak = `${p}.bak-${ts}`;
@@ -93733,7 +94093,7 @@ async function doInstall(opts, program3) {
93733
94093
  }
93734
94094
  const dir = hostdDir();
93735
94095
  const composePath = hostdComposePath();
93736
- mkdirSync51(dir, { recursive: true });
94096
+ mkdirSync52(dir, { recursive: true });
93737
94097
  const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
93738
94098
  const guard = checkDowngrade({
93739
94099
  container: "switchroom-hostd",
@@ -93766,7 +94126,7 @@ async function doInstall(opts, program3) {
93766
94126
  const bak = backupExistingCompose();
93767
94127
  if (bak)
93768
94128
  console.log(source_default.dim(` Backed up existing compose to ${bak}`));
93769
- writeFileSync42(composePath, yaml, "utf8");
94129
+ writeFileSync43(composePath, yaml, "utf8");
93770
94130
  console.log(source_default.green(` \u2713 Wrote ${composePath}`));
93771
94131
  const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
93772
94132
  console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
@@ -93798,7 +94158,7 @@ function doStatus() {
93798
94158
  const composeYml = hostdComposePath();
93799
94159
  console.log(source_default.bold("switchroom-hostd"));
93800
94160
  console.log("");
93801
- if (!existsSync92(composeYml)) {
94161
+ if (!existsSync93(composeYml)) {
93802
94162
  console.log(source_default.yellow(" compose: not installed"));
93803
94163
  console.log(source_default.dim(" run `switchroom hostd install` to set up."));
93804
94164
  return;
@@ -93819,15 +94179,15 @@ function doStatus() {
93819
94179
  } else {
93820
94180
  console.log(source_default.green(` container: ${ps.stdout.trim()}`));
93821
94181
  }
93822
- if (existsSync92(dir)) {
94182
+ if (existsSync93(dir)) {
93823
94183
  const entries = [];
93824
94184
  try {
93825
94185
  for (const name of readdirSync35(dir)) {
93826
94186
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
93827
94187
  continue;
93828
- const sockPath = join89(dir, name, "sock");
93829
- if (existsSync92(sockPath)) {
93830
- const st = statSync39(sockPath);
94188
+ const sockPath = join90(dir, name, "sock");
94189
+ if (existsSync93(sockPath)) {
94190
+ const st = statSync40(sockPath);
93831
94191
  if ((st.mode & 61440) === 49152) {
93832
94192
  entries.push(`${name} \u2192 ${sockPath}`);
93833
94193
  }
@@ -93845,7 +94205,7 @@ function doStatus() {
93845
94205
  }
93846
94206
  function doUninstall() {
93847
94207
  const composeYml = hostdComposePath();
93848
- if (!existsSync92(composeYml)) {
94208
+ if (!existsSync93(composeYml)) {
93849
94209
  console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
93850
94210
  return;
93851
94211
  }
@@ -93869,12 +94229,12 @@ function registerHostdCommand(program3) {
93869
94229
  hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
93870
94230
  hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
93871
94231
  const logPath = opts.path ?? defaultAuditLogPath2();
93872
- if (!existsSync92(logPath)) {
94232
+ if (!existsSync93(logPath)) {
93873
94233
  console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
93874
94234
  The log is created when hostd handles its first privileged-verb request.`));
93875
94235
  return;
93876
94236
  }
93877
- const raw = readFileSync79(logPath, "utf-8");
94237
+ const raw = readFileSync80(logPath, "utf-8");
93878
94238
  const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
93879
94239
  const filters = {
93880
94240
  agent: opts.agent,
@@ -93917,9 +94277,9 @@ The log is created when hostd handles its first privileged-verb request.`));
93917
94277
  init_source();
93918
94278
  init_helpers();
93919
94279
  init_operator_uid();
93920
- import { existsSync as existsSync93, mkdirSync as mkdirSync52, writeFileSync as writeFileSync43, copyFileSync as copyFileSync14 } from "node:fs";
93921
- import { homedir as homedir54 } from "node:os";
93922
- import { join as join90 } from "node:path";
94280
+ import { existsSync as existsSync94, mkdirSync as mkdirSync53, writeFileSync as writeFileSync44, copyFileSync as copyFileSync14 } from "node:fs";
94281
+ import { homedir as homedir55 } from "node:os";
94282
+ import { join as join91 } from "node:path";
93923
94283
  import { spawnSync as spawnSync21 } from "node:child_process";
93924
94284
  function resolveWebImageTag(explicitTag, release) {
93925
94285
  if (explicitTag)
@@ -94004,14 +94364,14 @@ services:
94004
94364
  `;
94005
94365
  }
94006
94366
  function webdDir() {
94007
- return join90(homedir54(), ".switchroom", "web");
94367
+ return join91(homedir55(), ".switchroom", "web");
94008
94368
  }
94009
94369
  function webdComposePath() {
94010
- return join90(webdDir(), "docker-compose.yml");
94370
+ return join91(webdDir(), "docker-compose.yml");
94011
94371
  }
94012
94372
  function backupExistingCompose2() {
94013
94373
  const p = webdComposePath();
94014
- if (!existsSync93(p))
94374
+ if (!existsSync94(p))
94015
94375
  return null;
94016
94376
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
94017
94377
  const bak = `${p}.bak-${ts}`;
@@ -94036,7 +94396,7 @@ async function doInstall2(opts, program3) {
94036
94396
  }
94037
94397
  const dir = webdDir();
94038
94398
  const composePath = webdComposePath();
94039
- mkdirSync52(dir, { recursive: true });
94399
+ mkdirSync53(dir, { recursive: true });
94040
94400
  const cfg = getConfig(program3);
94041
94401
  const imageTag = resolveWebImageTag(opts.tag, cfg.release);
94042
94402
  const guard = checkDowngrade({
@@ -94049,7 +94409,7 @@ async function doInstall2(opts, program3) {
94049
94409
  return;
94050
94410
  }
94051
94411
  const yaml = renderWebComposeFile({
94052
- hostHome: homedir54(),
94412
+ hostHome: homedir55(),
94053
94413
  imageTag,
94054
94414
  operatorUid
94055
94415
  });
@@ -94062,7 +94422,7 @@ async function doInstall2(opts, program3) {
94062
94422
  const bak = backupExistingCompose2();
94063
94423
  if (bak)
94064
94424
  console.log(source_default.dim(` Backed up existing compose to ${bak}`));
94065
- writeFileSync43(composePath, yaml, "utf8");
94425
+ writeFileSync44(composePath, yaml, "utf8");
94066
94426
  console.log(source_default.green(` \u2713 Wrote ${composePath}`));
94067
94427
  console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host`));
94068
94428
  console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${imageTag}\u2026`));
@@ -94095,7 +94455,7 @@ function doStatus2() {
94095
94455
  const composeYml = webdComposePath();
94096
94456
  console.log(source_default.bold("switchroom-web"));
94097
94457
  console.log("");
94098
- if (!existsSync93(composeYml)) {
94458
+ if (!existsSync94(composeYml)) {
94099
94459
  console.log(source_default.yellow(" compose: not installed"));
94100
94460
  console.log(source_default.dim(" run `switchroom webd install` to set up."));
94101
94461
  return;
@@ -94119,7 +94479,7 @@ function doStatus2() {
94119
94479
  }
94120
94480
  function doUninstall2() {
94121
94481
  const composeYml = webdComposePath();
94122
- if (!existsSync93(composeYml)) {
94482
+ if (!existsSync94(composeYml)) {
94123
94483
  console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
94124
94484
  return;
94125
94485
  }
@@ -94148,10 +94508,10 @@ function registerWebdCommand(program3) {
94148
94508
 
94149
94509
  // src/cli/host-repair.ts
94150
94510
  init_source();
94151
- import { homedir as homedir55 } from "node:os";
94152
- import { join as join91 } from "node:path";
94511
+ import { homedir as homedir56 } from "node:os";
94512
+ import { join as join92 } from "node:path";
94153
94513
  var ARTIFACT_ALLOWLIST = {
94154
- dockerComposePluginDir: (home2) => join91(home2, ".docker", "cli-plugins", "docker-compose"),
94514
+ dockerComposePluginDir: (home2) => join92(home2, ".docker", "cli-plugins", "docker-compose"),
94155
94515
  stateSentinel: "/state"
94156
94516
  };
94157
94517
  function isStateBogusAutoDir(probe2) {
@@ -94200,7 +94560,7 @@ function isStateBogusAutoDir(probe2) {
94200
94560
  return true;
94201
94561
  }
94202
94562
  function planMountRepairs(probe2, opts = {}) {
94203
- const home2 = opts.hostHome ?? homedir55();
94563
+ const home2 = opts.hostHome ?? homedir56();
94204
94564
  const items = [];
94205
94565
  const dockerComposePath = ARTIFACT_ALLOWLIST.dockerComposePluginDir(home2);
94206
94566
  const dockerComposeSt = probe2.lstat(dockerComposePath);