switchroom 0.18.15 → 0.18.18

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 (76) hide show
  1. package/dist/agent-scheduler/index.js +16 -0
  2. package/dist/auth-broker/index.js +445 -10
  3. package/dist/cli/notion-write-pretool.mjs +16 -0
  4. package/dist/cli/switchroom.js +654 -479
  5. package/dist/host-control/main.js +20 -1
  6. package/dist/vault/approvals/kernel-server.js +16 -0
  7. package/dist/vault/broker/server.js +16 -0
  8. package/package.json +1 -1
  9. package/profiles/_base/start.sh.hbs +81 -139
  10. package/telegram-plugin/bridge/bridge.ts +7 -1
  11. package/telegram-plugin/dist/bridge/bridge.js +26 -1
  12. package/telegram-plugin/dist/gateway/gateway.js +1758 -661
  13. package/telegram-plugin/dist/server.js +26 -1
  14. package/telegram-plugin/draft-stream.ts +78 -3
  15. package/telegram-plugin/fleet-fallback-resume.ts +26 -3
  16. package/telegram-plugin/gateway/approval-hold.ts +49 -0
  17. package/telegram-plugin/gateway/bridge-dead-watchdog.ts +64 -22
  18. package/telegram-plugin/gateway/effort-command.ts +9 -7
  19. package/telegram-plugin/gateway/gateway.ts +627 -291
  20. package/telegram-plugin/gateway/linear-activity.ts +20 -4
  21. package/telegram-plugin/gateway/litellm-local-notice-wiring.ts +200 -0
  22. package/telegram-plugin/gateway/model-command.ts +96 -18
  23. package/telegram-plugin/gateway/pending-session-command.ts +10 -8
  24. package/telegram-plugin/gateway/premium-recovery-wiring.ts +122 -0
  25. package/telegram-plugin/gateway/session-model-file.ts +141 -172
  26. package/telegram-plugin/gateway/tier-downgrade-wiring.ts +121 -0
  27. package/telegram-plugin/gateway/unhandled-rejection-policy.ts +14 -1
  28. package/telegram-plugin/litellm-local-notice.ts +189 -0
  29. package/telegram-plugin/llm-error-present.ts +436 -0
  30. package/telegram-plugin/operator-events.ts +7 -1
  31. package/telegram-plugin/permission-title.ts +172 -10
  32. package/telegram-plugin/premium-recovery.ts +101 -0
  33. package/telegram-plugin/quota-watch.ts +16 -4
  34. package/telegram-plugin/raw-error-scrub.ts +73 -0
  35. package/telegram-plugin/retry-api-call.ts +8 -2
  36. package/telegram-plugin/runtime-metrics.ts +16 -0
  37. package/telegram-plugin/send-gate-degraded.test.ts +161 -8
  38. package/telegram-plugin/send-gate-observability.test.ts +140 -0
  39. package/telegram-plugin/send-gate-observability.ts +65 -20
  40. package/telegram-plugin/send-gate.test.ts +143 -1
  41. package/telegram-plugin/send-gate.ts +246 -23
  42. package/telegram-plugin/session-tail.ts +16 -0
  43. package/telegram-plugin/shared/local-time.ts +69 -0
  44. package/telegram-plugin/stream-controller.ts +143 -20
  45. package/telegram-plugin/stream-reply-handler.ts +12 -2
  46. package/telegram-plugin/tests/approval-hold-harness.ts +6 -6
  47. package/telegram-plugin/tests/approval-hold-outcome.test.ts +10 -2
  48. package/telegram-plugin/tests/bot-api.harness.ts +7 -2
  49. package/telegram-plugin/tests/bridge-dead-watchdog.test.ts +61 -0
  50. package/telegram-plugin/tests/draft-stream.test.ts +110 -1
  51. package/telegram-plugin/tests/effort-command.test.ts +4 -4
  52. package/telegram-plugin/tests/fleet-fallback-resume.test.ts +39 -0
  53. package/telegram-plugin/tests/flood-windows-persistence.test.ts +5 -4
  54. package/telegram-plugin/tests/gateway-pending-command-wiring.test.ts +33 -19
  55. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +47 -127
  56. package/telegram-plugin/tests/linear-create-issue.test.ts +30 -2
  57. package/telegram-plugin/tests/litellm-local-notice.test.ts +417 -0
  58. package/telegram-plugin/tests/llm-error-present.test.ts +380 -0
  59. package/telegram-plugin/tests/model-command.test.ts +84 -1
  60. package/telegram-plugin/tests/permission-title.test.ts +167 -4
  61. package/telegram-plugin/tests/premium-recovery-wiring.test.ts +150 -0
  62. package/telegram-plugin/tests/premium-recovery.test.ts +165 -0
  63. package/telegram-plugin/tests/quota-watch.test.ts +21 -0
  64. package/telegram-plugin/tests/reaction-gate-routing.test.ts +8 -3
  65. package/telegram-plugin/tests/retry-api-call.test.ts +21 -0
  66. package/telegram-plugin/tests/session-model-file.test.ts +7 -155
  67. package/telegram-plugin/tests/stream-controller-send-gate.test.ts +521 -0
  68. package/telegram-plugin/tests/stream-reply-handler.test.ts +44 -0
  69. package/telegram-plugin/tests/tier-downgrade-wiring.test.ts +165 -0
  70. package/telegram-plugin/tests/tier-downgrade.test.ts +141 -0
  71. package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +27 -1
  72. package/telegram-plugin/tests/worker-activity-feed.test.ts +212 -2
  73. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +492 -0
  74. package/telegram-plugin/tier-downgrade.ts +198 -0
  75. package/telegram-plugin/tool-activity-summary.ts +99 -0
  76. package/telegram-plugin/worker-activity-feed.ts +543 -368
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.18.15", COMMIT_SHA = "2fa611a1";
2123
+ var VERSION = "0.18.18", COMMIT_SHA = "a256b3b1";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -13818,6 +13818,19 @@ var init_schema = __esm(() => {
13818
13818
  approval_timeout_minutes: exports_external.number().int().nonnegative().optional().describe("Operator approval-card lifetime (minutes) for the tool-use 'Allow " + "once' card and the vault grant decision wait. After this long with " + "no operator tap, the card auto-denies (a TIMEOUT, not a denial \u2014 the " + "agent is told not to retry). Default 60. hostd-gated verbs " + "(mcp__hostd__*) keep their own longer window; the hostd " + "config-propose card is not governed by this key."),
13819
13819
  sub_agent_tick_interval_ms: exports_external.number().int().nonnegative().optional().describe("Heartbeat tick interval (ms) for sub-agent rendering. Forces a " + "re-render of the elapsed-time counter while sub-agents are running, " + "even during silent stretches between tool calls. Default 10000 (10 s). " + "Set to 0 to disable the elapsed-ticker path."),
13820
13820
  edit_budget_threshold: exports_external.number().int().nonnegative().optional().describe("Telegram API edit budget per minute before the progress-card driver " + "falls back to a slower coalesce window. When a chat accumulates more " + "than this many card edits in the trailing 60 s, the driver switches " + "to a wider coalesce interval until the rate drops back. Default 18. " + "Increase if your gateway frequently bumps the Telegram edit-rate ceiling " + "with many parallel sub-agents; decrease for a more conservative buffer."),
13821
+ send_gate: exports_external.object({
13822
+ enabled: exports_external.boolean().optional().describe("Master switch for the deterministic outbound send gate " + "(telegram-plugin/send-gate.ts) \u2014 the token-bucket scheduler every " + "Bot API call transits so per-surface throttles can't add up past a " + "flood ceiling. ON by default. Precedence: the operator break-glass " + "env var SWITCHROOM_TELEGRAM_SEND_GATE (0/false/off/no) ALWAYS wins " + "when explicitly set; this key only decides when that env var is " + "unset. Omit to keep the gate on."),
13823
+ global_per_sec: exports_external.number().positive().optional().describe("Global bucket sustained rate (Bot API calls/sec across ALL chats). " + "Default 25 (headroom under Telegram's ~30/s). Must be > 0 \u2014 a zero " + "or negative rate would wedge all outbound sends. Omit to keep 25."),
13824
+ global_burst: exports_external.number().int().positive().describe("Global bucket burst capacity. Default 4; worst-case 1s window " + "admits global_burst + global_per_sec = 29 < 30, a real margin under " + "the ceiling. Must be an integer >= 1 (a 0 capacity never admits a " + "token and wedges sends). Omit to keep 4.").optional(),
13825
+ per_chat_per_sec: exports_external.number().positive().optional().describe("Per-chat sustained rate (calls/sec to a single chat). Default 1. " + "Must be > 0 (zero/negative wedges that chat). Omit to keep 1."),
13826
+ per_chat_burst: exports_external.number().int().positive().optional().describe("Per-chat burst capacity. Default 3. Must be an integer >= 1 " + "(0 wedges the chat's bucket). Omit to keep 3."),
13827
+ per_group_per_min: exports_external.number().positive().optional().describe("Per-group sustained rate (calls/min to a single group/supergroup). " + "Default 18 (headroom under Telegram's ~20/min group ceiling). Must " + "be > 0. Omit to keep 18."),
13828
+ per_group_burst: exports_external.number().int().positive().optional().describe("Per-group burst capacity. Default 2. Must be an integer >= 1 " + "(0 wedges the group's bucket). Omit to keep 2."),
13829
+ edit_floor_ms: exports_external.number().int().nonnegative().optional().describe("Minimum ms between successive edits of the SAME message_id " + "(last-write-wins coalescing enforces this floor). Default 1500 " + "(Telegram's ~1 edit/sec/message practical ceiling). 0 disables the " + "floor. Must be an integer >= 0. Omit to keep 1500.")
13830
+ }).optional().describe("Tunable rate limits for the deterministic outbound send gate " + "(telegram-plugin/send-gate.ts). Every key is optional and defaults to " + "the send gate's built-in value, so omitting the whole block reproduces " + "today's exact behaviour \u2014 this is pure operator tuning, no default is " + "changed. Cascades from defaults.channels.telegram.send_gate."),
13831
+ worker_feed: exports_external.object({
13832
+ max_rows: exports_external.number().int().positive().optional().describe("Max live-worker rows rendered in the COMBINED worker-activity feed " + "(2+ background workers in one chat/thread coalesce into ONE message; " + "telegram-plugin/worker-activity-feed.ts) before a compact " + "'+M more working\u2026' spill line \u2014 keeps the coalesced body compact and " + "legible (and under the rich-message wire ceiling). Default 8. A " + "single-worker chat renders the full \uD83D\uDEE0 Worker card and ignores this. " + "Must be an integer >= 1. Omit to keep 8.")
13833
+ }).optional().describe("Tuning for the coalesced worker-activity feed " + "(telegram-plugin/worker-activity-feed.ts). Cascades from " + "defaults.channels.telegram.worker_feed."),
13821
13834
  stickers: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Sticker aliases for the `send_sticker` MCP tool (#576). Maps a " + "short alias name (e.g. 'happy', 'thinking') to a Telegram file_id. " + "Operator-curated \u2014 capture file_ids from inbound stickers the user " + "sends and add them here. The agent calls send_sticker(chat_id, " + "alias='happy') and the gateway resolves to the file_id at send " + "time. Aliases enable persona-flavored expressiveness without " + "exposing raw file_ids in the agent prompt. Personal-assistant / " + "health-coach personas benefit; coding agents typically don't " + "configure any."),
13822
13835
  voice_in: exports_external.object({
13823
13836
  enabled: exports_external.boolean().optional().describe("Master switch for voice-message transcription."),
@@ -13843,6 +13856,9 @@ var init_schema = __esm(() => {
13843
13856
  window_ms: exports_external.number().int().nonnegative().optional().describe("Sliding-window (ms) for merging consecutive inbound messages from " + "the same sender+topic into ONE Claude turn. Each new message resets " + "the timer; the turn starts once the sender pauses for this long. " + "Catches forwarded bursts, pasted text the Telegram client split " + "into several messages, and mixed text+media forwards. Default 500. " + "Set 0 to disable (every message becomes its own turn). Raise for " + "users who think in multiple short messages; the trade-off is the " + "single-message turn start is delayed by this much (the \uD83D\uDC40 ack still " + "fires immediately, so perceived latency is unchanged)."),
13844
13857
  max_attachments: exports_external.number().int().positive().optional().describe("Maximum number of media attachments carried into ONE coalesced " + "Claude turn. Default 10 \u2014 a full Telegram album (media_group caps " + "at 10) or a text+multi-image forwarded burst arrives as a single " + "turn; the agent sees numbered attachment fields (image_path, " + "image_path_2, \u2026). Set 1 to restore the historical " + "single-attachment-per-turn behaviour. Excess attachments beyond " + "the cap spill into the next turn. Each attachment is downloaded, " + "so a high cap on a slow link delays turn start.")
13845
13858
  }).optional().describe("Inbound coalescing \u2014 how the gateway groups rapid consecutive messages " + "into a single turn so a forwarded album or split paste doesn't fan out " + "into N separate turns. Cascades from defaults.channels.telegram.coalesce."),
13859
+ litellm_notice: exports_external.object({
13860
+ window_ms: exports_external.number().int().positive().optional().describe("Per-agent cooldown window (ms) for the litellm-local 429 notice. " + "When the agent trips the LiteLLM proxy's OWN tpm_limit/rpm_limit " + "cap (a `litellm-local` classified 429 \u2014 see docs/auth.md \u00a7 " + "LiteLLM-proxy-local 429s), the gateway posts ONE calm notice " + "naming the fleet token limiter, then counts further hits silently " + "for this long; the first notice after the window expires says " + "how many were absorbed. Default 900000 (15 min). Invalid values " + "fall back to the default.")
13861
+ }).optional().describe("Debounce tuning for the litellm-local throttle notice \u2014 the calm " + "'fleet token limiter engaged' message posted when the LiteLLM " + "proxy's own rate cap trips (never an Anthropic account limit). " + "Cascades from defaults.channels.telegram.litellm_notice."),
13846
13862
  interrupt: exports_external.object({
13847
13863
  safe_boundary: exports_external.boolean().optional().describe("When true (the default), a `!`-prefix interrupt that arrives while " + "the agent is mid-tool-call is DEFERRED: the SIGINT and the " + "replacement turn wait until the in-flight tool call finishes (a " + "clean boundary) instead of C-c'ing the agent mid-write/mid-bash. If " + "no tool is in flight the interrupt still fires immediately. Bounded " + "by max_wait_ms so a long tool never strands the user. Set false to " + "fire synchronously the moment `!` is received (historical " + "behaviour). Rapid repeated `!` while one is pending coalesce into a " + "single deferred interrupt carrying the latest body."),
13848
13864
  max_wait_ms: exports_external.number().int().positive().optional().describe("Upper bound (ms) the gateway waits for a safe boundary before firing " + "a deferred `!` interrupt anyway. Only consulted when safe_boundary is " + "true. Default 8000. Keep it short \u2014 the user explicitly asked to " + "interrupt, so a long in-flight tool shouldn't ghost them; the cap " + "trades a tiny risk of a mid-tool C-c for a guaranteed response.")
@@ -17113,18 +17129,18 @@ function alignAgentTreeOwnershipIfRoot(name, agentDir) {
17113
17129
  }
17114
17130
  return uid;
17115
17131
  }
17116
- function findAgentUnreadablePaths(candidates, agentUid) {
17132
+ function findAgentUnreadablePaths(candidates, agentUid, fs = { lstatSync, statSync: statSync3 }) {
17117
17133
  const bad = [];
17118
17134
  for (const p of candidates) {
17119
17135
  let ls;
17120
17136
  try {
17121
- ls = lstatSync(p);
17137
+ ls = fs.lstatSync(p);
17122
17138
  } catch {
17123
17139
  continue;
17124
17140
  }
17125
17141
  if (ls.isSymbolicLink())
17126
17142
  continue;
17127
- const st = statSync3(p);
17143
+ const st = fs.statSync(p);
17128
17144
  if (st.uid === agentUid)
17129
17145
  continue;
17130
17146
  const otherReadable = st.isDirectory() ? (st.mode & 5) === 5 : (st.mode & 4) === 4;
@@ -26683,6 +26699,37 @@ function channelsToEnv(agent) {
26683
26699
  if (tg.edit_budget_threshold !== undefined) {
26684
26700
  out.SWITCHROOM_TG_EDIT_BUDGET_THRESHOLD = String(tg.edit_budget_threshold);
26685
26701
  }
26702
+ const sg = tg.send_gate;
26703
+ if (sg) {
26704
+ if (sg.enabled !== undefined) {
26705
+ out.SWITCHROOM_TG_SEND_GATE_ENABLED = sg.enabled ? "1" : "0";
26706
+ }
26707
+ if (sg.global_per_sec !== undefined) {
26708
+ out.SWITCHROOM_TG_SEND_GATE_GLOBAL_PER_SEC = String(sg.global_per_sec);
26709
+ }
26710
+ if (sg.global_burst !== undefined) {
26711
+ out.SWITCHROOM_TG_SEND_GATE_GLOBAL_BURST = String(sg.global_burst);
26712
+ }
26713
+ if (sg.per_chat_per_sec !== undefined) {
26714
+ out.SWITCHROOM_TG_SEND_GATE_PER_CHAT_PER_SEC = String(sg.per_chat_per_sec);
26715
+ }
26716
+ if (sg.per_chat_burst !== undefined) {
26717
+ out.SWITCHROOM_TG_SEND_GATE_PER_CHAT_BURST = String(sg.per_chat_burst);
26718
+ }
26719
+ if (sg.per_group_per_min !== undefined) {
26720
+ out.SWITCHROOM_TG_SEND_GATE_PER_GROUP_PER_MIN = String(sg.per_group_per_min);
26721
+ }
26722
+ if (sg.per_group_burst !== undefined) {
26723
+ out.SWITCHROOM_TG_SEND_GATE_PER_GROUP_BURST = String(sg.per_group_burst);
26724
+ }
26725
+ if (sg.edit_floor_ms !== undefined) {
26726
+ out.SWITCHROOM_TG_SEND_GATE_EDIT_FLOOR_MS = String(sg.edit_floor_ms);
26727
+ }
26728
+ }
26729
+ const wf = tg?.worker_feed;
26730
+ if (wf?.max_rows !== undefined) {
26731
+ out.SWITCHROOM_TG_WORKER_FEED_MAX_ROWS = String(wf.max_rows);
26732
+ }
26686
26733
  if (tg.clear_status_on_completion !== undefined) {
26687
26734
  out.SWITCHROOM_TG_CLEAR_STATUS_ON_COMPLETION = tg.clear_status_on_completion ? "1" : "0";
26688
26735
  }
@@ -28995,6 +29042,9 @@ function buildAccessJson2(agentConfig, telegramConfig, resolvedTopicId, userId)
28995
29042
  if (typeof tg?.coalesce?.max_attachments === "number") {
28996
29043
  access.coalesceMaxAttachments = tg.coalesce.max_attachments;
28997
29044
  }
29045
+ if (typeof tg?.litellm_notice?.window_ms === "number") {
29046
+ access.litellmNoticeWindowMs = tg.litellm_notice.window_ms;
29047
+ }
28998
29048
  if (typeof tg?.interrupt?.safe_boundary === "boolean") {
28999
29049
  access.interruptSafeBoundary = tg.interrupt.safe_boundary;
29000
29050
  }
@@ -29763,6 +29813,7 @@ function generateCompose(opts) {
29763
29813
  const hostControlEnabled = config.host_control?.enabled !== false;
29764
29814
  const hostHomeForChecks = opts.homeDir ?? process.env.HOME ?? "";
29765
29815
  const probeHome = opts.probeHomeDir ?? hostHomeForChecks;
29816
+ const precreateHostDirs = opts.precreateHostDirs ?? (opts.homeDir !== undefined || opts.probeHomeDir !== undefined);
29766
29817
  const switchroomConfigPath = resolveConfigMountSource(opts.switchroomConfigPath, homePrefix);
29767
29818
  const bundledSkillsPoolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
29768
29819
  const voiceEngine = opts.voiceEngine ?? loadHostCapabilities()?.voice.engine ?? "cloud";
@@ -29967,7 +30018,7 @@ function generateCompose(opts) {
29967
30018
  telemetryDisabled,
29968
30019
  posthogKeyOverride,
29969
30020
  posthogHostOverride
29970
- }, bundledSkillsPoolDir, hostControlEnabled, opts.operatorUid, voiceEngine);
30021
+ }, bundledSkillsPoolDir, hostControlEnabled, opts.operatorUid, voiceEngine, precreateHostDirs);
29971
30022
  }
29972
30023
  lines.push(`volumes:`);
29973
30024
  for (const a of describeAgents(config, opts.litellmConfirmedAgents)) {
@@ -30001,7 +30052,7 @@ function generateCompose(opts) {
30001
30052
  return lines.join(`
30002
30053
  `);
30003
30054
  }
30004
- function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, probeHome, switchroomConfigPath, containerNamePrefix, posthog, bundledSkillsPoolDir, hostControlEnabled, operatorUid, voiceEngine) {
30055
+ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, probeHome, switchroomConfigPath, containerNamePrefix, posthog, bundledSkillsPoolDir, hostControlEnabled, operatorUid, voiceEngine, precreateHostDirs) {
30005
30056
  lines.push(` agent-${a.name}:`);
30006
30057
  emitImageOrBuild(lines, "agent", imageTag, buildMode, buildContext);
30007
30058
  lines.push(` container_name: ${containerNamePrefix}-${a.name}`);
@@ -30164,22 +30215,30 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
30164
30215
  if (conditionalMountPresent(`${probeHome}/.switchroom/credentials/${a.name}`, hostHomeForChecks, probeHome)) {
30165
30216
  lines.push(` - ${homePrefix}/.switchroom/credentials/${a.name}:${homePrefix}/.switchroom/credentials:ro`);
30166
30217
  }
30167
- try {
30168
- mkdirSync12(`${probeHome}/.switchroom/audit/${a.name}`, { recursive: true });
30169
- } catch {}
30170
- try {
30171
- const blockedDir = `${probeHome}/.switchroom/blocked-approvals`;
30172
- mkdirSync12(blockedDir, { recursive: true });
30173
- chmodSync3(blockedDir, 1023);
30174
- } catch {}
30175
- try {
30176
- mkdirSync12(`${probeHome}/.switchroom/agents/${a.name}/schedule.d`, { recursive: true });
30177
- } catch {}
30178
- lines.push(` - ${homePrefix}/.switchroom/audit/${a.name}:${homePrefix}/.switchroom/audit/${a.name}:rw`);
30179
- if (conditionalMountPresent(`${probeHome}/.switchroom-config`, hostHomeForChecks, probeHome)) {
30218
+ if (precreateHostDirs) {
30180
30219
  try {
30181
- mkdirSync12(`${probeHome}/.switchroom-config/agents/${a.name}/personal-skills`, { recursive: true });
30220
+ mkdirSync12(`${probeHome}/.switchroom/audit/${a.name}`, { recursive: true });
30182
30221
  } catch {}
30222
+ }
30223
+ if (precreateHostDirs) {
30224
+ try {
30225
+ const blockedDir = `${probeHome}/.switchroom/blocked-approvals`;
30226
+ mkdirSync12(blockedDir, { recursive: true });
30227
+ chmodSync3(blockedDir, 1023);
30228
+ } catch {}
30229
+ }
30230
+ if (precreateHostDirs) {
30231
+ try {
30232
+ mkdirSync12(`${probeHome}/.switchroom/agents/${a.name}/schedule.d`, { recursive: true });
30233
+ } catch {}
30234
+ }
30235
+ lines.push(` - ${homePrefix}/.switchroom/audit/${a.name}:${homePrefix}/.switchroom/audit/${a.name}:rw`);
30236
+ if (conditionalMountPresent(`${probeHome}/.switchroom-config`, hostHomeForChecks, probeHome)) {
30237
+ if (precreateHostDirs) {
30238
+ try {
30239
+ mkdirSync12(`${probeHome}/.switchroom-config/agents/${a.name}/personal-skills`, { recursive: true });
30240
+ } catch {}
30241
+ }
30183
30242
  lines.push(` - ${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:rw`);
30184
30243
  }
30185
30244
  if (existsSync18(`${probeHome}/.switchroom/bin/webkite`)) {
@@ -43707,6 +43766,74 @@ function runCredentialsMigrationChecks(config, deps = {}) {
43707
43766
  }
43708
43767
  var init_doctor_credentials_migration = () => {};
43709
43768
 
43769
+ // src/cli/doctor-agent-dotfile-ownership.ts
43770
+ import { existsSync as existsSync62, lstatSync as lstatSync8, statSync as statSync36 } from "node:fs";
43771
+ import { join as join61 } from "node:path";
43772
+ function agentDotfileCandidates(agentDir) {
43773
+ return [
43774
+ join61(agentDir, ".claude", "settings.json"),
43775
+ join61(agentDir, ".claude.json"),
43776
+ join61(agentDir, ".claude", ".claude.json"),
43777
+ join61(agentDir, ".mcp.json"),
43778
+ join61(agentDir, ".claude-cron", ".mcp.json")
43779
+ ];
43780
+ }
43781
+ function runAgentDotfileOwnershipChecks(config, deps = {}) {
43782
+ const results = [];
43783
+ const agents = Object.keys(config.agents ?? {});
43784
+ if (agents.length === 0)
43785
+ return results;
43786
+ const exists = deps.existsSync ?? existsSync62;
43787
+ const scanFs = deps.scanFs ?? { lstatSync: lstatSync8, statSync: statSync36 };
43788
+ let agentsDir = deps.agentsDir;
43789
+ if (agentsDir === undefined) {
43790
+ try {
43791
+ agentsDir = resolveAgentsDir(config);
43792
+ } catch {
43793
+ agentsDir = undefined;
43794
+ }
43795
+ }
43796
+ if (agentsDir === undefined) {
43797
+ return [
43798
+ {
43799
+ name: "agent dotfile ownership",
43800
+ status: "warn",
43801
+ detail: "could not resolve the agents directory (no switchroom block) \u2014 skipped dotfile-ownership check"
43802
+ }
43803
+ ];
43804
+ }
43805
+ for (const name of agents) {
43806
+ const agentDir = join61(agentsDir, name);
43807
+ if (!exists(agentDir)) {
43808
+ continue;
43809
+ }
43810
+ const isRoot = config.agents[name]?.root === true;
43811
+ const runtimeUid = isRoot ? 0 : allocateAgentUid(name);
43812
+ const candidates = agentDotfileCandidates(agentDir);
43813
+ const unreadable = findAgentUnreadablePaths(candidates, runtimeUid, scanFs);
43814
+ if (unreadable.length === 0) {
43815
+ results.push({
43816
+ name: `dotfile ownership: ${name}`,
43817
+ status: "ok",
43818
+ detail: isRoot ? `runs as uid 0 (root-tier) \u2014 dotfiles readable` : `dotfiles readable by agent uid ${runtimeUid}`
43819
+ });
43820
+ continue;
43821
+ }
43822
+ results.push({
43823
+ name: `dotfile ownership: ${name}`,
43824
+ status: "fail",
43825
+ detail: `${unreadable.length} dotfile(s) not readable by the agent's runtime ` + `uid ${runtimeUid}: ${unreadable.join(", ")}. Claude Code loads no ` + `permission allowlist from an unreadable settings.json \u2192 the agent ` + `prompts the operator to approve every tool call (card storm).`,
43826
+ fix: `sudo chown -h -R ${runtimeUid}:${runtimeUid} ${agentDir} # then \`switchroom agent restart ${name}\``
43827
+ });
43828
+ }
43829
+ return results;
43830
+ }
43831
+ var init_doctor_agent_dotfile_ownership = __esm(() => {
43832
+ init_loader();
43833
+ init_agent_uid();
43834
+ init_agent_owned_tree();
43835
+ });
43836
+
43710
43837
  // src/cli/doctor-inlined-secrets.ts
43711
43838
  import { readFileSync as fsReadFileSync } from "node:fs";
43712
43839
  function isSecretShapedKey(key) {
@@ -43804,7 +43931,7 @@ var init_doctor_inlined_secrets = __esm(() => {
43804
43931
  // src/cli/doctor-approval-attribution.ts
43805
43932
  import { execFileSync as execFileSync19 } from "node:child_process";
43806
43933
  import { readFileSync as fsReadFileSync2 } from "node:fs";
43807
- import { join as join61 } from "node:path";
43934
+ import { join as join62 } from "node:path";
43808
43935
  function parseApproverSet(canonical) {
43809
43936
  try {
43810
43937
  const arr = JSON.parse(canonical);
@@ -43924,7 +44051,7 @@ function loadLiveAllowFromAccessFiles(agentNames, agentsDir, readFile2 = (p) =>
43924
44051
  let readAny = false;
43925
44052
  for (const name of agentNames) {
43926
44053
  try {
43927
- const raw = readFile2(join61(agentsDir, name, "telegram", "access.json"));
44054
+ const raw = readFile2(join62(agentsDir, name, "telegram", "access.json"));
43928
44055
  const parsed = JSON.parse(raw);
43929
44056
  readAny = true;
43930
44057
  if (Array.isArray(parsed.allowFrom)) {
@@ -43957,13 +44084,13 @@ var init_doctor_approval_attribution = __esm(() => {
43957
44084
  // src/cli/doctor-audit-integrity.ts
43958
44085
  import { readFileSync as fsReadFileSync3 } from "node:fs";
43959
44086
  import { homedir as homedir37 } from "node:os";
43960
- import { join as join62 } from "node:path";
44087
+ import { join as join63 } from "node:path";
43961
44088
  function rootWrittenLogs(home2) {
43962
44089
  return [
43963
- { label: "vault-broker", path: join62(home2, ".switchroom", "vault-audit.log") },
44090
+ { label: "vault-broker", path: join63(home2, ".switchroom", "vault-audit.log") },
43964
44091
  {
43965
44092
  label: "hostd",
43966
- path: join62(home2, ".switchroom", "host-control-audit.log")
44093
+ path: join63(home2, ".switchroom", "host-control-audit.log")
43967
44094
  }
43968
44095
  ];
43969
44096
  }
@@ -43972,7 +44099,7 @@ function runAuditIntegrityChecks(deps = {}) {
43972
44099
  const read = deps.readFileSync ?? ((p) => fsReadFileSync3(p, "utf8"));
43973
44100
  const readFailOpen = deps.readFailOpenState ?? readFailOpenState;
43974
44101
  const results = [];
43975
- const vaultAuditLog = join62(home2, ".switchroom", "vault-audit.log");
44102
+ const vaultAuditLog = join63(home2, ".switchroom", "vault-audit.log");
43976
44103
  const failOpen = readFailOpen(failOpenStatePath(vaultAuditLog));
43977
44104
  if (failOpen.failOpenCount > 0) {
43978
44105
  const when = failOpen.lastFailureTs ? ` (last at ${failOpen.lastFailureTs})` : ``;
@@ -44045,16 +44172,16 @@ var init_doctor_audit_integrity = __esm(() => {
44045
44172
  });
44046
44173
 
44047
44174
  // src/cli/doctor-agent-smoke.ts
44048
- import { existsSync as existsSync62 } from "node:fs";
44175
+ import { existsSync as existsSync63 } from "node:fs";
44049
44176
  import { homedir as homedir38 } from "node:os";
44050
- import { join as join63 } from "node:path";
44177
+ import { join as join64 } from "node:path";
44051
44178
  import { randomUUID as randomUUID5 } from "node:crypto";
44052
44179
  async function runAgentSmokeChecks(config, deps = {}) {
44053
44180
  if (deps.fast)
44054
44181
  return [];
44055
44182
  const home2 = deps.homeDir ?? homedir38();
44056
- const sock = deps.operatorSockPath ?? join63(home2, ".switchroom", "hostd", "operator", "sock");
44057
- if (!deps.hostdRequestImpl && !existsSync62(sock)) {
44183
+ const sock = deps.operatorSockPath ?? join64(home2, ".switchroom", "hostd", "operator", "sock");
44184
+ if (!deps.hostdRequestImpl && !existsSync63(sock)) {
44058
44185
  return [
44059
44186
  {
44060
44187
  name: "agent liveness",
@@ -44132,9 +44259,9 @@ var init_doctor_agent_smoke = __esm(() => {
44132
44259
 
44133
44260
  // src/cli/doctor-vault-broker-durability.ts
44134
44261
  import { execFileSync as execFileSync20 } from "node:child_process";
44135
- import { existsSync as existsSync63, statSync as statSync36 } from "node:fs";
44262
+ import { existsSync as existsSync64, statSync as statSync37 } from "node:fs";
44136
44263
  import { homedir as homedir39 } from "node:os";
44137
- import { join as join64 } from "node:path";
44264
+ import { join as join65 } from "node:path";
44138
44265
  function probeBindMountInode(hostPath, brokerContainerPath, opts) {
44139
44266
  const statHost = opts?.statHost ?? defaultStatHost;
44140
44267
  const statBroker = opts?.statBroker ?? defaultStatBroker;
@@ -44156,10 +44283,10 @@ function probeBindMountInode(hostPath, brokerContainerPath, opts) {
44156
44283
  };
44157
44284
  }
44158
44285
  function defaultStatHost(p) {
44159
- if (!existsSync63(p))
44286
+ if (!existsSync64(p))
44160
44287
  return null;
44161
44288
  try {
44162
- const s = statSync36(p, { bigint: true });
44289
+ const s = statSync37(p, { bigint: true });
44163
44290
  return { ino: s.ino, size: Number(s.size) };
44164
44291
  } catch {
44165
44292
  return null;
@@ -44283,16 +44410,16 @@ function runVaultBrokerDurabilityChecks(_config, opts) {
44283
44410
  probeBrokerUnlocked(opts?.statusProbe),
44284
44411
  probeAutoUnlockBlob(home2),
44285
44412
  probeMachineIdMount(),
44286
- formatBindMountResult("vault-broker: vault.enc bind mount", join64(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join64(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
44287
- formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join64(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join64(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
44288
- formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join64(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join64(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
44413
+ formatBindMountResult("vault-broker: vault.enc bind mount", join65(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join65(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
44414
+ formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join65(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join65(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
44415
+ formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join65(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join65(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
44289
44416
  probeKernelDbDurability(home2, {
44290
44417
  statBroker: opts?.kernelStatBroker
44291
44418
  })
44292
44419
  ];
44293
44420
  }
44294
44421
  function probeKernelDbDurability(home2, opts) {
44295
- const hostDir = join64(home2, ".switchroom", "approvals");
44422
+ const hostDir = join65(home2, ".switchroom", "approvals");
44296
44423
  const containerDir = "/state/approvals";
44297
44424
  const name = "approval-kernel: approvals bind mount (allow_always durability)";
44298
44425
  const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
@@ -44360,8 +44487,8 @@ function defaultKernelStatBroker(p) {
44360
44487
  return { kind: "ok-with-stat", ino: inoStr, size };
44361
44488
  }
44362
44489
  function probeAutoUnlockBlob(home2) {
44363
- const blobPath = join64(home2, ".switchroom", "vault-auto-unlock");
44364
- if (!existsSync63(blobPath)) {
44490
+ const blobPath = join65(home2, ".switchroom", "vault-auto-unlock");
44491
+ if (!existsSync64(blobPath)) {
44365
44492
  return {
44366
44493
  name: "vault-broker: auto-unlock blob",
44367
44494
  status: "warn",
@@ -44369,7 +44496,7 @@ function probeAutoUnlockBlob(home2) {
44369
44496
  fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
44370
44497
  };
44371
44498
  }
44372
- const sz = statSync36(blobPath).size;
44499
+ const sz = statSync37(blobPath).size;
44373
44500
  if (sz === 0) {
44374
44501
  return {
44375
44502
  name: "vault-broker: auto-unlock blob",
@@ -44385,7 +44512,7 @@ function probeAutoUnlockBlob(home2) {
44385
44512
  };
44386
44513
  }
44387
44514
  function probeMachineIdMount() {
44388
- const hostExists = existsSync63("/etc/machine-id");
44515
+ const hostExists = existsSync64("/etc/machine-id");
44389
44516
  if (!hostExists) {
44390
44517
  return {
44391
44518
  name: "vault-broker: machine-id passthrough",
@@ -44536,25 +44663,25 @@ import { Socket as Socket3 } from "node:net";
44536
44663
  import {
44537
44664
  accessSync as accessSync2,
44538
44665
  constants as fsConstants5,
44539
- existsSync as existsSync64,
44540
- lstatSync as lstatSync8,
44666
+ existsSync as existsSync65,
44667
+ lstatSync as lstatSync9,
44541
44668
  mkdirSync as mkdirSync33,
44542
44669
  readFileSync as readFileSync58,
44543
44670
  readdirSync as readdirSync22,
44544
- statSync as statSync37
44671
+ statSync as statSync38
44545
44672
  } from "node:fs";
44546
- import { dirname as dirname19, join as join65, resolve as resolve38 } from "node:path";
44673
+ import { dirname as dirname19, join as join66, resolve as resolve38 } from "node:path";
44547
44674
  import { createPublicKey, createPrivateKey } from "node:crypto";
44548
44675
  function findInNvm(bin) {
44549
- const nvmRoot = join65(process.env.HOME ?? "", ".nvm", "versions", "node");
44550
- if (!existsSync64(nvmRoot))
44676
+ const nvmRoot = join66(process.env.HOME ?? "", ".nvm", "versions", "node");
44677
+ if (!existsSync65(nvmRoot))
44551
44678
  return null;
44552
44679
  try {
44553
44680
  const versions = readdirSync22(nvmRoot).sort().reverse();
44554
44681
  for (const v of versions) {
44555
- const candidate = join65(nvmRoot, v, "bin", bin);
44682
+ const candidate = join66(nvmRoot, v, "bin", bin);
44556
44683
  try {
44557
- const s = statSync37(candidate);
44684
+ const s = statSync38(candidate);
44558
44685
  if (s.isFile() || s.isSymbolicLink()) {
44559
44686
  return candidate;
44560
44687
  }
@@ -44579,7 +44706,7 @@ function whichOnPath(bin) {
44579
44706
  for (const dir of pathEnv.split(":")) {
44580
44707
  if (!dir)
44581
44708
  continue;
44582
- const candidate = join65(dir, bin);
44709
+ const candidate = join66(dir, bin);
44583
44710
  if (isX(candidate))
44584
44711
  return candidate;
44585
44712
  }
@@ -44745,21 +44872,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
44745
44872
  if (envBrowsersPath && envBrowsersPath.length > 0) {
44746
44873
  cacheLocations.push(envBrowsersPath);
44747
44874
  }
44748
- cacheLocations.push(join65(homeDir, ".cache", "ms-playwright"));
44875
+ cacheLocations.push(join66(homeDir, ".cache", "ms-playwright"));
44749
44876
  for (const cacheDir of cacheLocations) {
44750
- if (!existsSync64(cacheDir))
44877
+ if (!existsSync65(cacheDir))
44751
44878
  continue;
44752
44879
  try {
44753
44880
  const entries = readdirSync22(cacheDir).filter((e) => e.startsWith("chromium"));
44754
44881
  for (const entry of entries) {
44755
44882
  const candidates2 = [
44756
- join65(cacheDir, entry, "chrome-linux64", "chrome"),
44757
- join65(cacheDir, entry, "chrome-linux", "chrome"),
44758
- join65(cacheDir, entry, "chrome-linux64", "headless_shell"),
44759
- join65(cacheDir, entry, "chrome-linux", "headless_shell")
44883
+ join66(cacheDir, entry, "chrome-linux64", "chrome"),
44884
+ join66(cacheDir, entry, "chrome-linux", "chrome"),
44885
+ join66(cacheDir, entry, "chrome-linux64", "headless_shell"),
44886
+ join66(cacheDir, entry, "chrome-linux", "headless_shell")
44760
44887
  ];
44761
44888
  for (const path4 of candidates2) {
44762
- if (existsSync64(path4))
44889
+ if (existsSync65(path4))
44763
44890
  return path4;
44764
44891
  }
44765
44892
  }
@@ -44878,7 +45005,7 @@ function checkUserDeclaredMcps(name, agentConfig, config, renderedMcpServers) {
44878
45005
  }
44879
45006
  function defaultPathKind(p) {
44880
45007
  try {
44881
- const s = lstatSync8(p);
45008
+ const s = lstatSync9(p);
44882
45009
  return s.isDirectory() ? "dir" : "file-or-symlink";
44883
45010
  } catch {
44884
45011
  return "absent";
@@ -44888,7 +45015,7 @@ function checkDeployMounts(opts) {
44888
45015
  const home2 = opts?.home ?? process.env.HOME ?? "/root";
44889
45016
  const { pathKind } = opts?.deps ?? DEFAULT_DEPLOY_MOUNTS_DEPS;
44890
45017
  const results = [];
44891
- const dockerComposePlugin = join65(home2, ".docker", "cli-plugins", "docker-compose");
45018
+ const dockerComposePlugin = join66(home2, ".docker", "cli-plugins", "docker-compose");
44892
45019
  const pluginKind = pathKind(dockerComposePlugin);
44893
45020
  if (pluginKind === "dir") {
44894
45021
  results.push({
@@ -44926,8 +45053,8 @@ function checkDeployMounts(opts) {
44926
45053
  function checkLegacyState() {
44927
45054
  const results = [];
44928
45055
  const h = process.env.HOME ?? "/root";
44929
- const clerkDir = join65(h, LEGACY_STATE_DIR);
44930
- const clerkPresent = existsSync64(clerkDir);
45056
+ const clerkDir = join66(h, LEGACY_STATE_DIR);
45057
+ const clerkPresent = existsSync65(clerkDir);
44931
45058
  results.push({
44932
45059
  name: "legacy ~/.clerk state",
44933
45060
  status: clerkPresent ? "warn" : "ok",
@@ -44936,10 +45063,10 @@ function checkLegacyState() {
44936
45063
  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."
44937
45064
  } : {}
44938
45065
  });
44939
- const legacySock = join65(h, ".switchroom", "vault-broker.sock");
45066
+ const legacySock = join66(h, ".switchroom", "vault-broker.sock");
44940
45067
  let sockStat = null;
44941
45068
  try {
44942
- sockStat = lstatSync8(legacySock);
45069
+ sockStat = lstatSync9(legacySock);
44943
45070
  } catch {}
44944
45071
  if (sockStat) {
44945
45072
  results.push({
@@ -45057,7 +45184,7 @@ function checkVault(config) {
45057
45184
  detail: "Approval auth: passphrase (two-factor)"
45058
45185
  };
45059
45186
  const pairsResult = checkVaultBrokerSocketPairs(config);
45060
- if (!existsSync64(vaultPath)) {
45187
+ if (!existsSync65(vaultPath)) {
45061
45188
  return [
45062
45189
  postureResult,
45063
45190
  {
@@ -45443,7 +45570,7 @@ function tryReadHostFile(path4) {
45443
45570
  }
45444
45571
  }
45445
45572
  function parseEnvFile(path4) {
45446
- if (!existsSync64(path4))
45573
+ if (!existsSync65(path4))
45447
45574
  return {};
45448
45575
  let content;
45449
45576
  try {
@@ -45502,7 +45629,7 @@ async function checkTelegram(config) {
45502
45629
  const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
45503
45630
  if (plugin !== "switchroom")
45504
45631
  continue;
45505
- const envPath = join65(agentsDir, name, "telegram", ".env");
45632
+ const envPath = join66(agentsDir, name, "telegram", ".env");
45506
45633
  const read = tryReadHostFile(envPath);
45507
45634
  if (read.kind === "eacces") {
45508
45635
  results.push({
@@ -45554,7 +45681,7 @@ async function checkTelegram(config) {
45554
45681
  }
45555
45682
  function checkStartShStale(agentName, startShPath) {
45556
45683
  const label = `${agentName}: start.sh scheduler block`;
45557
- if (!existsSync64(startShPath)) {
45684
+ if (!existsSync65(startShPath)) {
45558
45685
  return {
45559
45686
  name: label,
45560
45687
  status: "warn",
@@ -45585,10 +45712,10 @@ function checkStartShStale(agentName, startShPath) {
45585
45712
  }
45586
45713
  function checkLeakedHomeSwitchroom(agentName, agentDir) {
45587
45714
  const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
45588
- const path4 = join65(agentDir, "home", ".switchroom");
45715
+ const path4 = join66(agentDir, "home", ".switchroom");
45589
45716
  let stats;
45590
45717
  try {
45591
- stats = lstatSync8(path4);
45718
+ stats = lstatSync9(path4);
45592
45719
  } catch (err) {
45593
45720
  if (err.code === "ENOENT") {
45594
45721
  return {
@@ -45622,8 +45749,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
45622
45749
  }
45623
45750
  function checkRepoHygiene(repoRoot) {
45624
45751
  const results = [];
45625
- const exportDir = join65(repoRoot, "clerk-export");
45626
- if (existsSync64(exportDir)) {
45752
+ const exportDir = join66(repoRoot, "clerk-export");
45753
+ if (existsSync65(exportDir)) {
45627
45754
  results.push({
45628
45755
  name: "repo hygiene: clerk-export/ on disk (#1072)",
45629
45756
  status: "warn",
@@ -45631,8 +45758,8 @@ function checkRepoHygiene(repoRoot) {
45631
45758
  fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
45632
45759
  });
45633
45760
  }
45634
- const knownTarball = join65(repoRoot, "clerk-export-with-secrets.tar.gz");
45635
- if (existsSync64(knownTarball)) {
45761
+ const knownTarball = join66(repoRoot, "clerk-export-with-secrets.tar.gz");
45762
+ if (existsSync65(knownTarball)) {
45636
45763
  results.push({
45637
45764
  name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
45638
45765
  status: "warn",
@@ -45649,7 +45776,7 @@ function checkRepoHygiene(repoRoot) {
45649
45776
  results.push({
45650
45777
  name: `repo hygiene: ${name} on disk (#1072)`,
45651
45778
  status: "warn",
45652
- detail: `${join65(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
45779
+ detail: `${join66(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
45653
45780
  fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
45654
45781
  });
45655
45782
  }
@@ -45672,10 +45799,10 @@ function checkRepoHygiene(repoRoot) {
45672
45799
  }
45673
45800
  function isSwitchroomCheckout(dir) {
45674
45801
  try {
45675
- if (!existsSync64(join65(dir, ".git")))
45802
+ if (!existsSync65(join66(dir, ".git")))
45676
45803
  return false;
45677
- const pkgPath = join65(dir, "package.json");
45678
- if (!existsSync64(pkgPath))
45804
+ const pkgPath = join66(dir, "package.json");
45805
+ if (!existsSync65(pkgPath))
45679
45806
  return false;
45680
45807
  const pkg = JSON.parse(readFileSync58(pkgPath, "utf-8"));
45681
45808
  return pkg.name === "switchroom";
@@ -45690,7 +45817,7 @@ function checkAgents(config, configPath) {
45690
45817
  const authStatuses = getAllAuthStatuses(config);
45691
45818
  for (const [name, agentConfig] of Object.entries(config.agents)) {
45692
45819
  const agentDir = resolve38(agentsDir, name);
45693
- if (!existsSync64(agentDir)) {
45820
+ if (!existsSync65(agentDir)) {
45694
45821
  results.push({
45695
45822
  name: `${name}: scaffold`,
45696
45823
  status: "fail",
@@ -45711,7 +45838,7 @@ function checkAgents(config, configPath) {
45711
45838
  fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
45712
45839
  });
45713
45840
  }
45714
- results.push(checkStartShStale(name, join65(agentDir, "start.sh")));
45841
+ results.push(checkStartShStale(name, join66(agentDir, "start.sh")));
45715
45842
  results.push(checkLeakedHomeSwitchroom(name, agentDir));
45716
45843
  const status = statuses[name];
45717
45844
  const active = status?.active ?? "unknown";
@@ -45788,8 +45915,8 @@ function checkAgents(config, configPath) {
45788
45915
  }
45789
45916
  }
45790
45917
  if (agentConfig.channels?.telegram?.plugin === "switchroom") {
45791
- const mcpJsonPath = join65(agentDir, ".mcp.json");
45792
- if (!existsSync64(mcpJsonPath)) {
45918
+ const mcpJsonPath = join66(agentDir, ".mcp.json");
45919
+ if (!existsSync65(mcpJsonPath)) {
45793
45920
  results.push({
45794
45921
  name: `${name}: .mcp.json`,
45795
45922
  status: "fail",
@@ -45875,7 +46002,7 @@ function mffEnvPath(config) {
45875
46002
  return agent ? resolve38(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve38(home2, ".switchroom/credentials/my-family-finance/.env");
45876
46003
  }
45877
46004
  function mffEnvState(envPath) {
45878
- if (!existsSync64(envPath))
46005
+ if (!existsSync65(envPath))
45879
46006
  return "absent";
45880
46007
  try {
45881
46008
  accessSync2(envPath, fsConstants5.R_OK);
@@ -45893,7 +46020,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
45893
46020
  fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
45894
46021
  };
45895
46022
  }
45896
- if (!existsSync64(vaultPath)) {
46023
+ if (!existsSync65(vaultPath)) {
45897
46024
  return {
45898
46025
  name: "mff: vault key present",
45899
46026
  status: "fail",
@@ -45946,7 +46073,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
45946
46073
  }
45947
46074
  }
45948
46075
  function checkMffVaultKeyFormat(passphrase, vaultPath) {
45949
- if (!passphrase || !existsSync64(vaultPath)) {
46076
+ if (!passphrase || !existsSync65(vaultPath)) {
45950
46077
  return {
45951
46078
  name: "mff: vault key format",
45952
46079
  status: "warn",
@@ -46090,8 +46217,8 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
46090
46217
  };
46091
46218
  }
46092
46219
  const credDir = dirname19(envPath);
46093
- const authScript = join65(credDir, "claude-auth.py");
46094
- if (!existsSync64(authScript)) {
46220
+ const authScript = join66(credDir, "claude-auth.py");
46221
+ if (!existsSync65(authScript)) {
46095
46222
  return {
46096
46223
  name: "mff: auth flow",
46097
46224
  status: "warn",
@@ -46397,6 +46524,10 @@ function registerDoctorCommand(program3) {
46397
46524
  { title: "Memory (Hindsight)", results: await checkHindsight(config) },
46398
46525
  { title: "Telegram", results: await checkTelegram(config) },
46399
46526
  { title: "Agents", results: checkAgents(config, configPath) },
46527
+ {
46528
+ title: "Agent dotfile ownership (#3157)",
46529
+ results: runAgentDotfileOwnershipChecks(config)
46530
+ },
46400
46531
  { title: "Credentials", results: runCredentialsMigrationChecks(config) },
46401
46532
  { title: "Audit integrity (WS10-F4)", results: runAuditIntegrityChecks() },
46402
46533
  {
@@ -46519,6 +46650,7 @@ var init_doctor = __esm(() => {
46519
46650
  init_doctor_notion();
46520
46651
  init_doctor_credentials_migration();
46521
46652
  init_doctor_secret_access();
46653
+ init_doctor_agent_dotfile_ownership();
46522
46654
  init_doctor_inlined_secrets();
46523
46655
  init_doctor_approval_attribution();
46524
46656
  init_doctor_audit_integrity();
@@ -47110,7 +47242,7 @@ var init_fleet_defaults = __esm(() => {
47110
47242
 
47111
47243
  // src/agents/connection-health.ts
47112
47244
  import { mkdirSync as mkdirSync44, writeFileSync as writeFileSync27 } from "node:fs";
47113
- import { join as join80 } from "node:path";
47245
+ import { join as join81 } from "node:path";
47114
47246
  async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
47115
47247
  const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
47116
47248
  if (reqs.length === 0)
@@ -47167,8 +47299,8 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
47167
47299
  return issues;
47168
47300
  }
47169
47301
  function writeConnectionHealthFile(agentDir, health, deps) {
47170
- const dir = join80(agentDir, ".claude");
47171
- const path7 = join80(dir, CONNECTION_HEALTH_FILENAME);
47302
+ const dir = join81(agentDir, ".claude");
47303
+ const path7 = join81(dir, CONNECTION_HEALTH_FILENAME);
47172
47304
  (deps?.mkdir ?? ((p, o) => mkdirSync44(p, o)))(dir, { recursive: true });
47173
47305
  (deps?.writeFile ?? ((p, d) => writeFileSync27(p, d)))(path7, JSON.stringify(health, null, 2) + `
47174
47306
  `);
@@ -47191,10 +47323,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
47191
47323
  var init_connection_health = () => {};
47192
47324
 
47193
47325
  // src/cli/update-prompt-hook.ts
47194
- import { existsSync as existsSync81, readFileSync as readFileSync70, writeFileSync as writeFileSync28, chmodSync as chmodSync11, mkdirSync as mkdirSync45 } from "node:fs";
47195
- import { join as join81 } from "node:path";
47326
+ import { existsSync as existsSync82, readFileSync as readFileSync70, writeFileSync as writeFileSync28, chmodSync as chmodSync11, mkdirSync as mkdirSync45 } from "node:fs";
47327
+ import { join as join82 } from "node:path";
47196
47328
  function containerHookCommand() {
47197
- return join81(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
47329
+ return join82(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
47198
47330
  }
47199
47331
  function updatePromptHookScript() {
47200
47332
  return `#!/bin/bash
@@ -47260,12 +47392,12 @@ exit 0
47260
47392
  `;
47261
47393
  }
47262
47394
  function installUpdatePromptHook(agentDir) {
47263
- const hooksDir = join81(agentDir, ".claude", "hooks");
47395
+ const hooksDir = join82(agentDir, ".claude", "hooks");
47264
47396
  mkdirSync45(hooksDir, { recursive: true });
47265
- const scriptPath = join81(hooksDir, HOOK_FILENAME);
47397
+ const scriptPath = join82(hooksDir, HOOK_FILENAME);
47266
47398
  const desired = updatePromptHookScript();
47267
47399
  let installed = false;
47268
- const existing = existsSync81(scriptPath) ? readFileSync70(scriptPath, "utf-8") : "";
47400
+ const existing = existsSync82(scriptPath) ? readFileSync70(scriptPath, "utf-8") : "";
47269
47401
  if (existing !== desired) {
47270
47402
  writeFileSync28(scriptPath, desired, { mode: 493 });
47271
47403
  chmodSync11(scriptPath, 493);
@@ -47275,8 +47407,8 @@ function installUpdatePromptHook(agentDir) {
47275
47407
  chmodSync11(scriptPath, 493);
47276
47408
  } catch {}
47277
47409
  }
47278
- const settingsPath = join81(agentDir, ".claude", "settings.json");
47279
- if (!existsSync81(settingsPath)) {
47410
+ const settingsPath = join82(agentDir, ".claude", "settings.json");
47411
+ if (!existsSync82(settingsPath)) {
47280
47412
  return { scriptPath, settingsPath, installed };
47281
47413
  }
47282
47414
  const raw = readFileSync70(settingsPath, "utf-8");
@@ -47704,7 +47836,7 @@ __export(exports_voice_sidecar_token, {
47704
47836
  VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
47705
47837
  });
47706
47838
  import { randomBytes as randomBytes14 } from "node:crypto";
47707
- import { chmodSync as chmodSync12, chownSync as chownSync7, existsSync as existsSync83, mkdirSync as mkdirSync46, readFileSync as readFileSync71, rmSync as rmSync17, writeFileSync as writeFileSync29 } from "node:fs";
47839
+ import { chmodSync as chmodSync12, chownSync as chownSync7, existsSync as existsSync84, mkdirSync as mkdirSync46, readFileSync as readFileSync71, rmSync as rmSync17, writeFileSync as writeFileSync29 } from "node:fs";
47708
47840
  import { dirname as dirname28 } from "node:path";
47709
47841
  async function defaultResolveOrSeedToken(home2, writeErr) {
47710
47842
  const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
@@ -47739,7 +47871,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
47739
47871
  const envPath = composeEnvPath(composePath);
47740
47872
  if (engine !== "local") {
47741
47873
  try {
47742
- if (existsSync83(envPath)) {
47874
+ if (existsSync84(envPath)) {
47743
47875
  const body = readFileSync71(envPath, "utf-8");
47744
47876
  if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
47745
47877
  rmSync17(envPath);
@@ -47762,7 +47894,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
47762
47894
  mkdirSync46(dirname28(envPath), { recursive: true });
47763
47895
  let body = "";
47764
47896
  try {
47765
- if (existsSync83(envPath))
47897
+ if (existsSync84(envPath))
47766
47898
  body = readFileSync71(envPath, "utf-8");
47767
47899
  } catch {}
47768
47900
  const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
@@ -47822,11 +47954,11 @@ __export(exports_apply, {
47822
47954
  DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
47823
47955
  COMPOSE_PROJECT: () => COMPOSE_PROJECT2
47824
47956
  });
47825
- import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync84, mkdirSync as mkdirSync47, readFileSync as readFileSync72, readdirSync as readdirSync29, renameSync as renameSync18, statSync as statSync46, writeFileSync as writeFileSync30 } from "node:fs";
47957
+ import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync85, mkdirSync as mkdirSync47, readFileSync as readFileSync72, readdirSync as readdirSync29, renameSync as renameSync18, statSync as statSync47, writeFileSync as writeFileSync30 } from "node:fs";
47826
47958
  import { mkdir as mkdir2 } from "node:fs/promises";
47827
47959
  import { spawnSync as childSpawnSync } from "node:child_process";
47828
47960
  import readline from "node:readline";
47829
- import { dirname as dirname29, join as join83, resolve as resolve51 } from "node:path";
47961
+ import { dirname as dirname29, join as join84, resolve as resolve51 } from "node:path";
47830
47962
  import { homedir as homedir49 } from "node:os";
47831
47963
  import { execFileSync as execFileSync27 } from "node:child_process";
47832
47964
  function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
@@ -47838,7 +47970,7 @@ async function resolveOperatorVaultPassphrase(home2) {
47838
47970
  return envPass;
47839
47971
  try {
47840
47972
  const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
47841
- const blobPath = join83(home2, ".switchroom", "vault-auto-unlock");
47973
+ const blobPath = join84(home2, ".switchroom", "vault-auto-unlock");
47842
47974
  const pass = readAutoUnlockFile2(blobPath);
47843
47975
  return pass && pass.length > 0 ? pass : null;
47844
47976
  } catch {
@@ -47926,7 +48058,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
47926
48058
  const oauthAccount = config.auth?.active;
47927
48059
  let pendingConfigEdits = false;
47928
48060
  let configText = null;
47929
- if (switchroomConfigPath && existsSync84(switchroomConfigPath)) {
48061
+ if (switchroomConfigPath && existsSync85(switchroomConfigPath)) {
47930
48062
  try {
47931
48063
  configText = readFileSync72(switchroomConfigPath, "utf-8");
47932
48064
  } catch (err) {
@@ -48083,7 +48215,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
48083
48215
  try {
48084
48216
  let cfgMode = 420;
48085
48217
  try {
48086
- cfgMode = statSync46(switchroomConfigPath).mode & 511;
48218
+ cfgMode = statSync47(switchroomConfigPath).mode & 511;
48087
48219
  } catch {}
48088
48220
  writeConfigFileSync(switchroomConfigPath, configText, cfgMode);
48089
48221
  writeOut(source_default.gray(` ~ litellm: granted read-ACL on per-agent keys (updated ${switchroomConfigPath})
@@ -48151,10 +48283,10 @@ function resolveVaultBindMountDir(homeDir, ctx) {
48151
48283
  if (isCustomPath && ctx.customVaultPath) {
48152
48284
  return dirname29(ctx.customVaultPath);
48153
48285
  }
48154
- return join83(homeDir, ".switchroom", "vault");
48286
+ return join84(homeDir, ".switchroom", "vault");
48155
48287
  }
48156
48288
  function inspectVaultBindMountDir(vaultDir) {
48157
- if (!existsSync84(vaultDir))
48289
+ if (!existsSync85(vaultDir))
48158
48290
  return { kind: "missing" };
48159
48291
  const entries = readdirSync29(vaultDir);
48160
48292
  const unknown = [];
@@ -48182,43 +48314,43 @@ function hasVaultRefs(value) {
48182
48314
  async function ensureHostMountSources(config) {
48183
48315
  const home2 = resolveHostHomeForCompose();
48184
48316
  const dirs = [
48185
- join83(home2, ".switchroom", "approvals"),
48186
- join83(home2, ".switchroom", "scheduler"),
48187
- join83(home2, ".switchroom", "logs"),
48188
- join83(home2, ".switchroom", "compose"),
48189
- join83(home2, ".switchroom", "broker-operator")
48317
+ join84(home2, ".switchroom", "approvals"),
48318
+ join84(home2, ".switchroom", "scheduler"),
48319
+ join84(home2, ".switchroom", "logs"),
48320
+ join84(home2, ".switchroom", "compose"),
48321
+ join84(home2, ".switchroom", "broker-operator")
48190
48322
  ];
48191
48323
  for (const name of Object.keys(config.agents)) {
48192
- dirs.push(join83(home2, ".switchroom", "agents", name));
48193
- dirs.push(join83(home2, ".switchroom", "logs", name));
48194
- dirs.push(join83(home2, ".claude", "projects", name));
48195
- dirs.push(join83(home2, ".switchroom", "audit", name));
48196
- if (existsSync84(join83(home2, ".switchroom-config"))) {
48197
- dirs.push(join83(home2, ".switchroom-config", "agents", name, "personal-skills"));
48324
+ dirs.push(join84(home2, ".switchroom", "agents", name));
48325
+ dirs.push(join84(home2, ".switchroom", "logs", name));
48326
+ dirs.push(join84(home2, ".claude", "projects", name));
48327
+ dirs.push(join84(home2, ".switchroom", "audit", name));
48328
+ if (existsSync85(join84(home2, ".switchroom-config"))) {
48329
+ dirs.push(join84(home2, ".switchroom-config", "agents", name, "personal-skills"));
48198
48330
  }
48199
48331
  }
48200
48332
  for (const dir of dirs) {
48201
48333
  await mkdir2(dir, { recursive: true });
48202
48334
  }
48203
- const autoUnlockPath = join83(home2, ".switchroom", "vault-auto-unlock");
48204
- if (!existsSync84(autoUnlockPath)) {
48335
+ const autoUnlockPath = join84(home2, ".switchroom", "vault-auto-unlock");
48336
+ if (!existsSync85(autoUnlockPath)) {
48205
48337
  writeFileSync30(autoUnlockPath, "", { mode: 384 });
48206
48338
  }
48207
- const auditLogPath = join83(home2, ".switchroom", "vault-audit.log");
48208
- if (!existsSync84(auditLogPath)) {
48339
+ const auditLogPath = join84(home2, ".switchroom", "vault-audit.log");
48340
+ if (!existsSync85(auditLogPath)) {
48209
48341
  writeFileSync30(auditLogPath, "", { mode: 420 });
48210
48342
  }
48211
- const grantsDbPath = join83(home2, ".switchroom", "vault-grants.db");
48212
- if (!existsSync84(grantsDbPath)) {
48343
+ const grantsDbPath = join84(home2, ".switchroom", "vault-grants.db");
48344
+ if (!existsSync85(grantsDbPath)) {
48213
48345
  writeFileSync30(grantsDbPath, "", { mode: 384 });
48214
48346
  }
48215
- const hostdAuditLogPath = join83(home2, ".switchroom", "host-control-audit.log");
48216
- if (!existsSync84(hostdAuditLogPath)) {
48347
+ const hostdAuditLogPath = join84(home2, ".switchroom", "host-control-audit.log");
48348
+ if (!existsSync85(hostdAuditLogPath)) {
48217
48349
  writeFileSync30(hostdAuditLogPath, "", { mode: 420 });
48218
48350
  }
48219
48351
  for (const name of Object.keys(config.agents)) {
48220
- const tokenPath = join83(home2, ".switchroom", "agents", name, ".vault-token");
48221
- if (!existsSync84(tokenPath)) {
48352
+ const tokenPath = join84(home2, ".switchroom", "agents", name, ".vault-token");
48353
+ if (!existsSync85(tokenPath)) {
48222
48354
  writeFileSync30(tokenPath, "", { mode: 384 });
48223
48355
  }
48224
48356
  try {
@@ -48226,16 +48358,16 @@ async function ensureHostMountSources(config) {
48226
48358
  chownSync8(tokenPath, uid, uid);
48227
48359
  } catch {}
48228
48360
  }
48229
- const fleetDir = join83(home2, ".switchroom", "fleet");
48361
+ const fleetDir = join84(home2, ".switchroom", "fleet");
48230
48362
  await mkdir2(fleetDir, { recursive: true });
48231
- const invariantsPath = join83(fleetDir, "switchroom-invariants.md");
48363
+ const invariantsPath = join84(fleetDir, "switchroom-invariants.md");
48232
48364
  const invariantsCanonical = renderFleetInvariants();
48233
- const invariantsCurrent = existsSync84(invariantsPath) ? readFileSync72(invariantsPath, "utf-8") : null;
48365
+ const invariantsCurrent = existsSync85(invariantsPath) ? readFileSync72(invariantsPath, "utf-8") : null;
48234
48366
  if (invariantsCurrent !== invariantsCanonical) {
48235
48367
  writeFileSync30(invariantsPath, invariantsCanonical, { mode: 420 });
48236
48368
  }
48237
- const fleetClaudePath = join83(fleetDir, "CLAUDE.md");
48238
- if (!existsSync84(fleetClaudePath)) {
48369
+ const fleetClaudePath = join84(fleetDir, "CLAUDE.md");
48370
+ if (!existsSync85(fleetClaudePath)) {
48239
48371
  writeFileSync30(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
48240
48372
  mode: 420
48241
48373
  });
@@ -48265,7 +48397,7 @@ function isInAgentContainer(vaultPresent, composeV2Present, env2 = process.env)
48265
48397
  function runApplyPreflight(config, opts = {}) {
48266
48398
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
48267
48399
  const detect = opts.detectComposeV2 ?? detectComposeV2;
48268
- const vaultMissing = hasVaultRefs(config) && !existsSync84(vaultPath);
48400
+ const vaultMissing = hasVaultRefs(config) && !existsSync85(vaultPath);
48269
48401
  const composeErr = detect();
48270
48402
  if ((vaultMissing || composeErr) && isInAgentContainer(!vaultMissing, composeErr === null)) {
48271
48403
  throw new Error(IN_AGENT_CONTAINER_APPLY_MSG);
@@ -48279,7 +48411,7 @@ function runApplyPreflight(config, opts = {}) {
48279
48411
  detectAndReportLegacyGdriveSlots(vaultPath);
48280
48412
  }
48281
48413
  function detectAndReportLegacyGdriveSlots(vaultPath) {
48282
- if (!existsSync84(vaultPath))
48414
+ if (!existsSync85(vaultPath))
48283
48415
  return;
48284
48416
  const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
48285
48417
  if (!passphrase)
@@ -48320,8 +48452,8 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
48320
48452
  }
48321
48453
  function writeInstallTypeCache(homeDir = homedir49()) {
48322
48454
  const ctx = detectInstallType();
48323
- const dir = join83(homeDir, ".switchroom");
48324
- const out = join83(dir, "install-type.json");
48455
+ const dir = join84(homeDir, ".switchroom");
48456
+ const out = join84(dir, "install-type.json");
48325
48457
  const tmp = `${out}.tmp`;
48326
48458
  mkdirSync47(dir, { recursive: true });
48327
48459
  const payload = {
@@ -48388,17 +48520,17 @@ Applying switchroom config...
48388
48520
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
48389
48521
  `));
48390
48522
  try {
48391
- installUpdatePromptHook(join83(agentsDir, name));
48523
+ installUpdatePromptHook(join84(agentsDir, name));
48392
48524
  } catch (hookErr) {
48393
48525
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
48394
48526
  `));
48395
48527
  }
48396
- await refreshAgentConnectionHealth(config, name, join83(agentsDir, name), {
48528
+ await refreshAgentConnectionHealth(config, name, join84(agentsDir, name), {
48397
48529
  vaultAclReader: connHealthVaultAclReader
48398
48530
  });
48399
48531
  try {
48400
48532
  const uid = allocateAgentUid(name);
48401
- alignAgentUid(name, join83(agentsDir, name), uid, {
48533
+ alignAgentUid(name, join84(agentsDir, name), uid, {
48402
48534
  confirm: !options.nonInteractive,
48403
48535
  writeOut
48404
48536
  });
@@ -48443,7 +48575,7 @@ Applying switchroom config...
48443
48575
  for (const name of agentNames) {
48444
48576
  try {
48445
48577
  const uid = allocateAgentUid(name);
48446
- alignAgentUid(name, join83(agentsDir, name), uid, {
48578
+ alignAgentUid(name, join84(agentsDir, name), uid, {
48447
48579
  confirm: !options.nonInteractive,
48448
48580
  writeOut
48449
48581
  });
@@ -48781,7 +48913,7 @@ function copyExampleConfig2(name) {
48781
48913
  throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
48782
48914
  }
48783
48915
  const dest = resolve51(process.cwd(), "switchroom.yaml");
48784
- if (existsSync84(dest)) {
48916
+ if (existsSync85(dest)) {
48785
48917
  console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
48786
48918
  return;
48787
48919
  }
@@ -48792,7 +48924,7 @@ function copyExampleConfig2(name) {
48792
48924
  return;
48793
48925
  }
48794
48926
  const exampleFile = resolve51(import.meta.dirname, `../../examples/${name}.yaml`);
48795
- if (!existsSync84(exampleFile)) {
48927
+ if (!existsSync85(exampleFile)) {
48796
48928
  throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
48797
48929
  }
48798
48930
  copyFileSync12(exampleFile, dest);
@@ -48803,8 +48935,8 @@ function findUnwritableAgentDirs(config, opts) {
48803
48935
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
48804
48936
  const unwritable = [];
48805
48937
  for (const name of targets) {
48806
- const startSh = join83(agentsDir, name, "start.sh");
48807
- if (!existsSync84(startSh))
48938
+ const startSh = join84(agentsDir, name, "start.sh");
48939
+ if (!existsSync85(startSh))
48808
48940
  continue;
48809
48941
  try {
48810
48942
  accessSync3(startSh, fsConstants6.W_OK);
@@ -49007,7 +49139,7 @@ var init_apply = __esm(() => {
49007
49139
  switchroom: switchroom_default,
49008
49140
  minimal: minimal_default
49009
49141
  };
49010
- DEFAULT_COMPOSE_PATH2 = join83(homedir49(), ".switchroom", "compose", "docker-compose.yml");
49142
+ DEFAULT_COMPOSE_PATH2 = join84(homedir49(), ".switchroom", "compose", "docker-compose.yml");
49011
49143
  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.
49012
49144
  ` + "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.";
49013
49145
  SELF_ELEVATE_PRESERVED_ENV = [
@@ -62456,7 +62588,7 @@ __export(exports_server2, {
62456
62588
  TOOLS: () => TOOLS2
62457
62589
  });
62458
62590
  import { randomBytes as randomBytes16 } from "node:crypto";
62459
- import { existsSync as existsSync93, readFileSync as readFileSync81 } from "node:fs";
62591
+ import { existsSync as existsSync94, readFileSync as readFileSync81 } from "node:fs";
62460
62592
  function selfSocketPath() {
62461
62593
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
62462
62594
  }
@@ -62483,7 +62615,7 @@ async function dispatchTool2(name, args) {
62483
62615
  return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
62484
62616
  }
62485
62617
  const sockPath = selfSocketPath();
62486
- if (!existsSync93(sockPath)) {
62618
+ if (!existsSync94(sockPath)) {
62487
62619
  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.`);
62488
62620
  }
62489
62621
  let req;
@@ -62734,13 +62866,13 @@ function resolveAuditLogPath() {
62734
62866
  if (process.env.HOSTD_AUDIT_LOG_PATH)
62735
62867
  return process.env.HOSTD_AUDIT_LOG_PATH;
62736
62868
  const bindMounted = "/host-home/.switchroom/host-control-audit.log";
62737
- if (existsSync93(bindMounted))
62869
+ if (existsSync94(bindMounted))
62738
62870
  return bindMounted;
62739
62871
  return defaultAuditLogPath2();
62740
62872
  }
62741
62873
  function getLastUpdateApplyStatus() {
62742
62874
  const path8 = resolveAuditLogPath();
62743
- if (!existsSync93(path8)) {
62875
+ if (!existsSync94(path8)) {
62744
62876
  return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
62745
62877
  }
62746
62878
  let raw;
@@ -63422,7 +63554,7 @@ __export(exports_scan, {
63422
63554
  import {
63423
63555
  readFileSync as readFileSync83,
63424
63556
  readdirSync as readdirSync37,
63425
- existsSync as existsSync96,
63557
+ existsSync as existsSync97,
63426
63558
  mkdirSync as mkdirSync54,
63427
63559
  writeFileSync as writeFileSync36
63428
63560
  } from "node:fs";
@@ -63433,7 +63565,7 @@ function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.en
63433
63565
  }
63434
63566
  function listAgents(base) {
63435
63567
  const dir = resolve56(base, "agents");
63436
- if (!existsSync96(dir))
63568
+ if (!existsSync97(dir))
63437
63569
  return [];
63438
63570
  try {
63439
63571
  return readdirSync37(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
@@ -63462,7 +63594,7 @@ function runScan(opts = {}) {
63462
63594
  let gwText = "";
63463
63595
  let sawArtifact = false;
63464
63596
  try {
63465
- if (existsSync96(turnsPath)) {
63597
+ if (existsSync97(turnsPath)) {
63466
63598
  turnsText = readFileSync83(turnsPath, "utf-8");
63467
63599
  sawArtifact = true;
63468
63600
  }
@@ -63470,7 +63602,7 @@ function runScan(opts = {}) {
63470
63602
  log(`fleet-health: WARN skipping ${agent} turns.jsonl unreadable: ${String(e)}`);
63471
63603
  }
63472
63604
  try {
63473
- if (existsSync96(gwPath)) {
63605
+ if (existsSync97(gwPath)) {
63474
63606
  gwText = readFileSync83(gwPath, "utf-8");
63475
63607
  sawArtifact = true;
63476
63608
  }
@@ -63509,7 +63641,7 @@ function runScan(opts = {}) {
63509
63641
  function readLedgerIfPresent(base) {
63510
63642
  const path8 = ledgerPathForBase(base);
63511
63643
  try {
63512
- if (!existsSync96(path8))
63644
+ if (!existsSync97(path8))
63513
63645
  return null;
63514
63646
  return JSON.parse(readFileSync83(path8, "utf-8"));
63515
63647
  } catch {
@@ -69654,6 +69786,36 @@ function printConsumersTable(state) {
69654
69786
  console.log(` ${c.name.padEnd(10)} ${acct} ${status}`);
69655
69787
  }
69656
69788
  }
69789
+ function usageHeadroomCell(t) {
69790
+ const noColor = (s) => s;
69791
+ if (t == null)
69792
+ return { plain: "no data", color: source_default.gray };
69793
+ if (t.headroomPct == null)
69794
+ return { plain: "unknown", color: source_default.gray };
69795
+ const left = Math.round(t.headroomPct);
69796
+ const peak = t.peakUtilizationPct == null ? "?" : `${Math.round(t.peakUtilizationPct)}`;
69797
+ const base = `${left}% left (peak ${peak}%, n=${t.observations})`;
69798
+ if (t.walled)
69799
+ return { plain: `WALLED - ${base}`, color: source_default.red };
69800
+ if (t.refilled)
69801
+ return { plain: `refilled - ${base}`, color: source_default.green };
69802
+ if (left <= 10)
69803
+ return { plain: base, color: source_default.yellow };
69804
+ return { plain: base, color: noColor };
69805
+ }
69806
+ function printUsageTable(state) {
69807
+ console.log(source_default.bold(" ACCOUNT PREMIUM (Fable) HEADROOM STANDARD HEADROOM"));
69808
+ const WIDTH = 44;
69809
+ for (const a of state.accounts) {
69810
+ const marker = a.label === state.active ? source_default.green("*") : " ";
69811
+ const label = a.label.padEnd(32);
69812
+ const premium = usageHeadroomCell(a.usage_ledger?.premium ?? null);
69813
+ const standard = usageHeadroomCell(a.usage_ledger?.standard ?? null);
69814
+ const premiumCol = premium.color(premium.plain.padEnd(WIDTH));
69815
+ const standardCol = standard.color(standard.plain);
69816
+ console.log(` ${marker} ${label} ${premiumCol} ${standardCol}`);
69817
+ }
69818
+ }
69657
69819
  function printAgentDetail(state, agent) {
69658
69820
  console.log();
69659
69821
  console.log(source_default.bold(` ${agent.name}`));
@@ -69832,6 +69994,19 @@ function registerAuthCommand(program3) {
69832
69994
  printAccountsTable(state);
69833
69995
  console.log();
69834
69996
  }));
69997
+ auth.command("usage").description("Per-account premium (Fable) + standard usage headroom, from the durable ledger").option("--json", "Output the raw per-account usage_ledger summaries as JSON").action(withConfigError(async (opts) => {
69998
+ const state = await brokerCall((client) => client.listState());
69999
+ if (opts.json) {
70000
+ console.log(JSON.stringify(state.accounts.map((a) => ({ label: a.label, usage_ledger: a.usage_ledger ?? null })), null, 2));
70001
+ return;
70002
+ }
70003
+ console.log();
70004
+ printUsageTable(state);
70005
+ console.log();
70006
+ console.log(source_default.gray(" Premium = flagship (Fable) 7d_oi tier; standard = 5h/7d. Passive: harvested from probe headers, no extra requests."));
70007
+ console.log(source_default.gray(" 'X% left' is current (refill-aware); 'peak' is the max over retained history (~48h) and may predate the latest reset."));
70008
+ console.log();
70009
+ }));
69835
70010
  function pinAuthActiveInYaml(label, quiet = false) {
69836
70011
  try {
69837
70012
  const yamlPath = getConfigPath(program3);
@@ -83200,9 +83375,9 @@ init_source();
83200
83375
  init_loader();
83201
83376
  init_lifecycle();
83202
83377
  init_compose_env();
83203
- import { cpSync as cpSync2, existsSync as existsSync65, mkdirSync as mkdirSync34, readFileSync as readFileSync59, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync39, chownSync as chownSync5 } from "node:fs";
83378
+ import { cpSync as cpSync2, existsSync as existsSync66, mkdirSync as mkdirSync34, readFileSync as readFileSync59, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync40, chownSync as chownSync5 } from "node:fs";
83204
83379
  import { spawnSync as spawnSync12 } from "node:child_process";
83205
- import { join as join66, dirname as dirname20, resolve as resolve39 } from "node:path";
83380
+ import { join as join67, dirname as dirname20, resolve as resolve39 } from "node:path";
83206
83381
  import { homedir as homedir40 } from "node:os";
83207
83382
 
83208
83383
  // src/cli/release-yaml.ts
@@ -83228,7 +83403,7 @@ init_operator_uid();
83228
83403
  init_atomic();
83229
83404
 
83230
83405
  // src/cli/preflight-mounts.ts
83231
- import { statSync as statSync38 } from "node:fs";
83406
+ import { statSync as statSync39 } from "node:fs";
83232
83407
  function parseHostBindSources(composeText) {
83233
83408
  const out = [];
83234
83409
  const FILE_HINT = /\.(ya?ml|db|log|toml|json|token|id)$|\/\.vault-token$|machine-id$|localtime$|vault-auto-unlock$|\/webkite$/;
@@ -83255,7 +83430,7 @@ function parseHostBindSources(composeText) {
83255
83430
  return out;
83256
83431
  }
83257
83432
  function validateBindSources(composeText, deps = {}) {
83258
- const stat = deps.stat ?? ((p) => statSync38(p));
83433
+ const stat = deps.stat ?? ((p) => statSync39(p));
83259
83434
  const sources = parseHostBindSources(composeText);
83260
83435
  const issues = [];
83261
83436
  const seen = new Set;
@@ -83300,7 +83475,7 @@ function defaultPersistPin(configPath) {
83300
83475
  const after = setReleasePinInConfig(before, pin);
83301
83476
  if (after === before)
83302
83477
  return;
83303
- writeConfigFileSync(path4, after, statSync39(path4).mode & 511);
83478
+ writeConfigFileSync(path4, after, statSync40(path4).mode & 511);
83304
83479
  try {
83305
83480
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
83306
83481
  const uid = resolveOperatorUid();
@@ -83310,13 +83485,13 @@ function defaultPersistPin(configPath) {
83310
83485
  } catch {}
83311
83486
  };
83312
83487
  }
83313
- var DEFAULT_COMPOSE_PATH = join66(homedir40(), ".switchroom", "compose", "docker-compose.yml");
83488
+ var DEFAULT_COMPOSE_PATH = join67(homedir40(), ".switchroom", "compose", "docker-compose.yml");
83314
83489
  function runningFromSwitchroomCheckout(scriptPath) {
83315
83490
  let dir = dirname20(scriptPath);
83316
83491
  for (let i = 0;i < 12; i++) {
83317
- if (existsSync65(join66(dir, ".git"))) {
83492
+ if (existsSync66(join67(dir, ".git"))) {
83318
83493
  try {
83319
- const pkg = JSON.parse(readFileSync59(join66(dir, "package.json"), "utf-8"));
83494
+ const pkg = JSON.parse(readFileSync59(join67(dir, "package.json"), "utf-8"));
83320
83495
  if (pkg.name === "switchroom")
83321
83496
  return true;
83322
83497
  } catch {}
@@ -83401,7 +83576,7 @@ function planUpdate(opts) {
83401
83576
  steps.push({
83402
83577
  name: "pull-images",
83403
83578
  description: "Pull broker / kernel / agent images from GHCR",
83404
- skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync65(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
83579
+ skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync66(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
83405
83580
  run: () => {
83406
83581
  const r = runner("docker", [
83407
83582
  "compose",
@@ -83527,14 +83702,14 @@ function planUpdate(opts) {
83527
83702
  return;
83528
83703
  }
83529
83704
  const source = resolve39(import.meta.dirname, "../../skills");
83530
- const dest = join66(homedir40(), ".switchroom", "skills", "_bundled");
83531
- if (!existsSync65(source)) {
83705
+ const dest = join67(homedir40(), ".switchroom", "skills", "_bundled");
83706
+ if (!existsSync66(source)) {
83532
83707
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
83533
83708
  `);
83534
83709
  return;
83535
83710
  }
83536
83711
  try {
83537
- if (existsSync65(dest)) {
83712
+ if (existsSync66(dest)) {
83538
83713
  rmSync12(dest, { recursive: true, force: true });
83539
83714
  }
83540
83715
  mkdirSync34(dirname20(dest), { recursive: true });
@@ -83648,12 +83823,12 @@ function defaultStatusProbe(composePath) {
83648
83823
  } catch {}
83649
83824
  if (scriptPath) {
83650
83825
  try {
83651
- cliBuiltAt = new Date(statSync39(scriptPath).mtimeMs).toISOString();
83826
+ cliBuiltAt = new Date(statSync40(scriptPath).mtimeMs).toISOString();
83652
83827
  } catch {}
83653
83828
  let dir = dirname20(scriptPath);
83654
83829
  for (let i = 0;i < 8; i++) {
83655
- const pkgPath = join66(dir, "package.json");
83656
- if (existsSync65(pkgPath)) {
83830
+ const pkgPath = join67(dir, "package.json");
83831
+ if (existsSync66(pkgPath)) {
83657
83832
  try {
83658
83833
  const pkg = JSON.parse(readFileSync59(pkgPath, "utf-8"));
83659
83834
  if (typeof pkg.version === "string")
@@ -83676,7 +83851,7 @@ function defaultStatusProbe(composePath) {
83676
83851
  warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
83677
83852
  }
83678
83853
  const services = [];
83679
- if (!existsSync65(composePath)) {
83854
+ if (!existsSync66(composePath)) {
83680
83855
  warnings.push(`compose file not found at ${composePath}; service status unknown`);
83681
83856
  return { cliVersion, cliBuiltAt, services, warnings };
83682
83857
  }
@@ -83872,7 +84047,7 @@ function registerUpdateCommand(program3) {
83872
84047
  // src/cli/rollout.ts
83873
84048
  init_helpers();
83874
84049
  import { spawnSync as spawnSync14 } from "node:child_process";
83875
- import { readFileSync as readFileSync60, chownSync as chownSync6, statSync as statSync40 } from "node:fs";
84050
+ import { readFileSync as readFileSync60, chownSync as chownSync6, statSync as statSync41 } from "node:fs";
83876
84051
  import { homedir as homedir41 } from "node:os";
83877
84052
  init_operator_uid();
83878
84053
  init_atomic();
@@ -84276,7 +84451,7 @@ function registerRolloutCommand(program3) {
84276
84451
  const after = setReleasePinInConfig(before, pin);
84277
84452
  if (after === before)
84278
84453
  return;
84279
- writeConfigFileSync(configPath, after, statSync40(configPath).mode & 511);
84454
+ writeConfigFileSync(configPath, after, statSync41(configPath).mode & 511);
84280
84455
  try {
84281
84456
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
84282
84457
  const uid = resolveOperatorUid();
@@ -84350,8 +84525,8 @@ init_helpers();
84350
84525
  init_lifecycle();
84351
84526
  init_resolve_version();
84352
84527
  import { execSync as execSync3 } from "node:child_process";
84353
- import { existsSync as existsSync66, readFileSync as readFileSync61 } from "node:fs";
84354
- import { dirname as dirname21, join as join67 } from "node:path";
84528
+ import { existsSync as existsSync67, readFileSync as readFileSync61 } from "node:fs";
84529
+ import { dirname as dirname21, join as join68 } from "node:path";
84355
84530
  function getClaudeCodeVersion() {
84356
84531
  try {
84357
84532
  const out = execSync3("claude --version 2>/dev/null", {
@@ -84401,11 +84576,11 @@ function formatUptime3(timestamp) {
84401
84576
  function locateSwitchroomInstallDir() {
84402
84577
  let dir = import.meta.dirname;
84403
84578
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
84404
- const pkgPath = join67(dir, "package.json");
84405
- if (existsSync66(pkgPath)) {
84579
+ const pkgPath = join68(dir, "package.json");
84580
+ if (existsSync67(pkgPath)) {
84406
84581
  try {
84407
84582
  const pkg = JSON.parse(readFileSync61(pkgPath, "utf-8"));
84408
- if (pkg.name === "switchroom" && existsSync66(join67(dir, ".git"))) {
84583
+ if (pkg.name === "switchroom" && existsSync67(join68(dir, ".git"))) {
84409
84584
  return dir;
84410
84585
  }
84411
84586
  } catch {}
@@ -84583,18 +84758,18 @@ import { resolve as resolve41 } from "node:path";
84583
84758
 
84584
84759
  // src/agents/session-retention.ts
84585
84760
  import {
84586
- existsSync as existsSync67,
84761
+ existsSync as existsSync68,
84587
84762
  readdirSync as readdirSync23,
84588
- statSync as statSync41,
84763
+ statSync as statSync42,
84589
84764
  unlinkSync as unlinkSync14
84590
84765
  } from "node:fs";
84591
- import { join as join68 } from "node:path";
84766
+ import { join as join69 } from "node:path";
84592
84767
  var DEFAULT_SESSION_RETENTION_MAX_COUNT = 20;
84593
84768
  var DEFAULT_SESSION_RETENTION_MAX_AGE_DAYS = 30;
84594
84769
  var MIN_KEEP = 2;
84595
84770
  function collectSessionJsonl(claudeConfigDir) {
84596
- const projects = join68(claudeConfigDir, "projects");
84597
- if (!existsSync67(projects))
84771
+ const projects = join69(claudeConfigDir, "projects");
84772
+ if (!existsSync68(projects))
84598
84773
  return [];
84599
84774
  const found = [];
84600
84775
  const walk2 = (dir) => {
@@ -84605,10 +84780,10 @@ function collectSessionJsonl(claudeConfigDir) {
84605
84780
  return;
84606
84781
  }
84607
84782
  for (const name of entries) {
84608
- const full = join68(dir, name);
84783
+ const full = join69(dir, name);
84609
84784
  let st;
84610
84785
  try {
84611
- st = statSync41(full);
84786
+ st = statSync42(full);
84612
84787
  } catch {
84613
84788
  continue;
84614
84789
  }
@@ -84725,18 +84900,18 @@ function registerHandoffCommand(program3) {
84725
84900
  // src/issues/store.ts
84726
84901
  import {
84727
84902
  closeSync as closeSync12,
84728
- existsSync as existsSync68,
84903
+ existsSync as existsSync69,
84729
84904
  mkdirSync as mkdirSync35,
84730
84905
  openSync as openSync12,
84731
84906
  readdirSync as readdirSync24,
84732
84907
  readFileSync as readFileSync62,
84733
84908
  renameSync as renameSync15,
84734
- statSync as statSync42,
84909
+ statSync as statSync43,
84735
84910
  unlinkSync as unlinkSync15,
84736
84911
  writeFileSync as writeFileSync19,
84737
84912
  writeSync as writeSync9
84738
84913
  } from "node:fs";
84739
- import { join as join69 } from "node:path";
84914
+ import { join as join70 } from "node:path";
84740
84915
  import { randomBytes as randomBytes12 } from "node:crypto";
84741
84916
  import { execSync as execSync4 } from "node:child_process";
84742
84917
 
@@ -85167,8 +85342,8 @@ function redactedMarker(ruleId) {
85167
85342
  var ISSUES_FILE = "issues.jsonl";
85168
85343
  var ISSUES_LOCK = "issues.lock";
85169
85344
  function readAll(stateDir) {
85170
- const path4 = join69(stateDir, ISSUES_FILE);
85171
- if (!existsSync68(path4))
85345
+ const path4 = join70(stateDir, ISSUES_FILE);
85346
+ if (!existsSync69(path4))
85172
85347
  return [];
85173
85348
  let raw;
85174
85349
  try {
@@ -85245,7 +85420,7 @@ function record(stateDir, input, nowFn = Date.now) {
85245
85420
  });
85246
85421
  }
85247
85422
  function resolve42(stateDir, fingerprint, nowFn = Date.now) {
85248
- if (!existsSync68(join69(stateDir, ISSUES_FILE)))
85423
+ if (!existsSync69(join70(stateDir, ISSUES_FILE)))
85249
85424
  return 0;
85250
85425
  return withLock(stateDir, () => {
85251
85426
  const all = readAll(stateDir);
@@ -85263,7 +85438,7 @@ function resolve42(stateDir, fingerprint, nowFn = Date.now) {
85263
85438
  });
85264
85439
  }
85265
85440
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
85266
- if (!existsSync68(join69(stateDir, ISSUES_FILE)))
85441
+ if (!existsSync69(join70(stateDir, ISSUES_FILE)))
85267
85442
  return 0;
85268
85443
  return withLock(stateDir, () => {
85269
85444
  const all = readAll(stateDir);
@@ -85281,7 +85456,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
85281
85456
  });
85282
85457
  }
85283
85458
  function prune(stateDir, opts = {}) {
85284
- if (!existsSync68(join69(stateDir, ISSUES_FILE)))
85459
+ if (!existsSync69(join70(stateDir, ISSUES_FILE)))
85285
85460
  return 0;
85286
85461
  return withLock(stateDir, () => {
85287
85462
  const all = readAll(stateDir);
@@ -85314,7 +85489,7 @@ function ensureDir(stateDir) {
85314
85489
  mkdirSync35(stateDir, { recursive: true });
85315
85490
  }
85316
85491
  function writeAll(stateDir, events) {
85317
- const path4 = join69(stateDir, ISSUES_FILE);
85492
+ const path4 = join70(stateDir, ISSUES_FILE);
85318
85493
  sweepOrphanTmpFiles(stateDir);
85319
85494
  const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
85320
85495
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -85336,9 +85511,9 @@ function sweepOrphanTmpFiles(stateDir) {
85336
85511
  for (const entry of entries) {
85337
85512
  if (!entry.startsWith(TMP_PREFIX))
85338
85513
  continue;
85339
- const tmpPath = join69(stateDir, entry);
85514
+ const tmpPath = join70(stateDir, entry);
85340
85515
  try {
85341
- const stat = statSync42(tmpPath);
85516
+ const stat = statSync43(tmpPath);
85342
85517
  if (stat.mtimeMs < cutoff) {
85343
85518
  unlinkSync15(tmpPath);
85344
85519
  }
@@ -85348,7 +85523,7 @@ function sweepOrphanTmpFiles(stateDir) {
85348
85523
  var LOCK_RETRY_MS = 25;
85349
85524
  var LOCK_TIMEOUT_MS = 1e4;
85350
85525
  function withLock(stateDir, fn) {
85351
- const lockPath = join69(stateDir, ISSUES_LOCK);
85526
+ const lockPath = join70(stateDir, ISSUES_LOCK);
85352
85527
  const startedAt = Date.now();
85353
85528
  let fd = null;
85354
85529
  while (fd === null) {
@@ -85631,20 +85806,20 @@ function relTime(deltaMs) {
85631
85806
 
85632
85807
  // src/cli/deps.ts
85633
85808
  init_source();
85634
- import { existsSync as existsSync71 } from "node:fs";
85809
+ import { existsSync as existsSync72 } from "node:fs";
85635
85810
  import { homedir as homedir44 } from "node:os";
85636
- import { join as join72, resolve as resolve43 } from "node:path";
85811
+ import { join as join73, resolve as resolve43 } from "node:path";
85637
85812
 
85638
85813
  // src/deps/python.ts
85639
85814
  import { createHash as createHash13 } from "node:crypto";
85640
85815
  import {
85641
- existsSync as existsSync69,
85816
+ existsSync as existsSync70,
85642
85817
  mkdirSync as mkdirSync36,
85643
85818
  readFileSync as readFileSync63,
85644
85819
  rmSync as rmSync13,
85645
85820
  writeFileSync as writeFileSync20
85646
85821
  } from "node:fs";
85647
- import { dirname as dirname22, join as join70 } from "node:path";
85822
+ import { dirname as dirname22, join as join71 } from "node:path";
85648
85823
  import { homedir as homedir42 } from "node:os";
85649
85824
  import { execFileSync as execFileSync21 } from "node:child_process";
85650
85825
 
@@ -85657,7 +85832,7 @@ class PythonEnvError extends Error {
85657
85832
  }
85658
85833
  }
85659
85834
  function defaultPythonCacheRoot() {
85660
- return join70(homedir42(), ".switchroom", "deps", "python");
85835
+ return join71(homedir42(), ".switchroom", "deps", "python");
85661
85836
  }
85662
85837
  function hashFile(path4) {
85663
85838
  return createHash13("sha256").update(readFileSync63(path4)).digest("hex");
@@ -85666,16 +85841,16 @@ function ensurePythonEnv(opts) {
85666
85841
  const { skillName, requirementsPath, force = false } = opts;
85667
85842
  const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
85668
85843
  const hostPython = opts.pythonBin ?? "python3";
85669
- if (!existsSync69(requirementsPath)) {
85844
+ if (!existsSync70(requirementsPath)) {
85670
85845
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
85671
85846
  }
85672
- const venvDir = join70(cacheRoot, skillName);
85673
- const stampPath = join70(venvDir, ".requirements.sha256");
85674
- const binDir = join70(venvDir, "bin");
85675
- const pythonBin = join70(binDir, "python");
85676
- const pipBin = join70(binDir, "pip");
85847
+ const venvDir = join71(cacheRoot, skillName);
85848
+ const stampPath = join71(venvDir, ".requirements.sha256");
85849
+ const binDir = join71(venvDir, "bin");
85850
+ const pythonBin = join71(binDir, "python");
85851
+ const pipBin = join71(binDir, "pip");
85677
85852
  const targetHash = hashFile(requirementsPath);
85678
- if (!force && existsSync69(stampPath) && existsSync69(pythonBin)) {
85853
+ if (!force && existsSync70(stampPath) && existsSync70(pythonBin)) {
85679
85854
  const existingHash = readFileSync63(stampPath, "utf8").trim();
85680
85855
  if (existingHash === targetHash) {
85681
85856
  return {
@@ -85688,7 +85863,7 @@ function ensurePythonEnv(opts) {
85688
85863
  };
85689
85864
  }
85690
85865
  }
85691
- if (existsSync69(venvDir)) {
85866
+ if (existsSync70(venvDir)) {
85692
85867
  rmSync13(venvDir, { recursive: true, force: true });
85693
85868
  }
85694
85869
  mkdirSync36(dirname22(venvDir), { recursive: true });
@@ -85726,13 +85901,13 @@ function ensurePythonEnv(opts) {
85726
85901
  import { createHash as createHash14 } from "node:crypto";
85727
85902
  import {
85728
85903
  copyFileSync as copyFileSync10,
85729
- existsSync as existsSync70,
85904
+ existsSync as existsSync71,
85730
85905
  mkdirSync as mkdirSync37,
85731
85906
  readFileSync as readFileSync64,
85732
85907
  rmSync as rmSync14,
85733
85908
  writeFileSync as writeFileSync21
85734
85909
  } from "node:fs";
85735
- import { dirname as dirname23, join as join71 } from "node:path";
85910
+ import { dirname as dirname23, join as join72 } from "node:path";
85736
85911
  import { homedir as homedir43 } from "node:os";
85737
85912
  import { execFileSync as execFileSync22 } from "node:child_process";
85738
85913
 
@@ -85756,7 +85931,7 @@ var LOCKFILES_FOR = {
85756
85931
  npm: ["package-lock.json"]
85757
85932
  };
85758
85933
  function defaultNodeCacheRoot() {
85759
- return join71(homedir43(), ".switchroom", "deps", "node");
85934
+ return join72(homedir43(), ".switchroom", "deps", "node");
85760
85935
  }
85761
85936
  function hashDepInputs(packageJsonPath) {
85762
85937
  const sourceDir = dirname23(packageJsonPath);
@@ -85765,8 +85940,8 @@ function hashDepInputs(packageJsonPath) {
85765
85940
  `);
85766
85941
  hasher.update(readFileSync64(packageJsonPath));
85767
85942
  for (const lockName of ALL_LOCKFILES) {
85768
- const lockPath = join71(sourceDir, lockName);
85769
- if (existsSync70(lockPath)) {
85943
+ const lockPath = join72(sourceDir, lockName);
85944
+ if (existsSync71(lockPath)) {
85770
85945
  hasher.update(`
85771
85946
  `);
85772
85947
  hasher.update(lockName);
@@ -85781,16 +85956,16 @@ function ensureNodeEnv(opts) {
85781
85956
  const { skillName, packageJsonPath, force = false } = opts;
85782
85957
  const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
85783
85958
  const installer = opts.installer ?? "bun";
85784
- if (!existsSync70(packageJsonPath)) {
85959
+ if (!existsSync71(packageJsonPath)) {
85785
85960
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
85786
85961
  }
85787
85962
  const sourceDir = dirname23(packageJsonPath);
85788
- const envDir = join71(cacheRoot, skillName);
85789
- const stampPath = join71(envDir, ".package.sha256");
85790
- const nodeModulesDir = join71(envDir, "node_modules");
85791
- const binDir = join71(nodeModulesDir, ".bin");
85963
+ const envDir = join72(cacheRoot, skillName);
85964
+ const stampPath = join72(envDir, ".package.sha256");
85965
+ const nodeModulesDir = join72(envDir, "node_modules");
85966
+ const binDir = join72(nodeModulesDir, ".bin");
85792
85967
  const targetHash = hashDepInputs(packageJsonPath);
85793
- if (!force && existsSync70(stampPath) && existsSync70(nodeModulesDir)) {
85968
+ if (!force && existsSync71(stampPath) && existsSync71(nodeModulesDir)) {
85794
85969
  const existingHash = readFileSync64(stampPath, "utf8").trim();
85795
85970
  if (existingHash === targetHash) {
85796
85971
  return {
@@ -85802,16 +85977,16 @@ function ensureNodeEnv(opts) {
85802
85977
  };
85803
85978
  }
85804
85979
  }
85805
- if (existsSync70(envDir)) {
85980
+ if (existsSync71(envDir)) {
85806
85981
  rmSync14(envDir, { recursive: true, force: true });
85807
85982
  }
85808
85983
  mkdirSync37(envDir, { recursive: true });
85809
- copyFileSync10(packageJsonPath, join71(envDir, "package.json"));
85984
+ copyFileSync10(packageJsonPath, join72(envDir, "package.json"));
85810
85985
  let copiedLockfile = false;
85811
85986
  for (const lockName of LOCKFILES_FOR[installer]) {
85812
- const lockPath = join71(sourceDir, lockName);
85813
- if (existsSync70(lockPath)) {
85814
- copyFileSync10(lockPath, join71(envDir, lockName));
85987
+ const lockPath = join72(sourceDir, lockName);
85988
+ if (existsSync71(lockPath)) {
85989
+ copyFileSync10(lockPath, join72(envDir, lockName));
85815
85990
  copiedLockfile = true;
85816
85991
  }
85817
85992
  }
@@ -85846,22 +86021,22 @@ function registerDepsCommand(program3) {
85846
86021
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
85847
86022
  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) => {
85848
86023
  const skillsRoot = builtinSkillsRoot();
85849
- if (!existsSync71(skillsRoot)) {
86024
+ if (!existsSync72(skillsRoot)) {
85850
86025
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
85851
86026
  process.exit(1);
85852
86027
  }
85853
- const skillDir = join72(skillsRoot, skill);
85854
- if (!existsSync71(skillDir)) {
86028
+ const skillDir = join73(skillsRoot, skill);
86029
+ if (!existsSync72(skillDir)) {
85855
86030
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
85856
86031
  process.exit(1);
85857
86032
  }
85858
- const requirementsPath = join72(skillDir, "requirements.txt");
85859
- const packageJsonPath = join72(skillDir, "package.json");
85860
- const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync71(requirementsPath));
85861
- const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync71(packageJsonPath));
86033
+ const requirementsPath = join73(skillDir, "requirements.txt");
86034
+ const packageJsonPath = join73(skillDir, "package.json");
86035
+ const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync72(requirementsPath));
86036
+ const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync72(packageJsonPath));
85862
86037
  let did = 0;
85863
86038
  if (wantPython) {
85864
- if (!existsSync71(requirementsPath)) {
86039
+ if (!existsSync72(requirementsPath)) {
85865
86040
  console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
85866
86041
  process.exit(1);
85867
86042
  }
@@ -85885,7 +86060,7 @@ function registerDepsCommand(program3) {
85885
86060
  }
85886
86061
  }
85887
86062
  if (wantNode) {
85888
- if (!existsSync71(packageJsonPath)) {
86063
+ if (!existsSync72(packageJsonPath)) {
85889
86064
  console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
85890
86065
  process.exit(1);
85891
86066
  }
@@ -85918,7 +86093,7 @@ function registerDepsCommand(program3) {
85918
86093
  // src/cli/workspace.ts
85919
86094
  init_helpers();
85920
86095
  init_loader();
85921
- import { existsSync as existsSync72 } from "node:fs";
86096
+ import { existsSync as existsSync73 } from "node:fs";
85922
86097
  import { resolve as resolve44, sep as sep3 } from "node:path";
85923
86098
  import { spawnSync as spawnSync15 } from "node:child_process";
85924
86099
 
@@ -86695,7 +86870,7 @@ function registerWorkspaceCommand(program3) {
86695
86870
  if (!dir)
86696
86871
  return;
86697
86872
  const gitDir = resolve44(dir, ".git");
86698
- if (!existsSync72(gitDir)) {
86873
+ if (!existsSync73(gitDir)) {
86699
86874
  process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
86700
86875
  `);
86701
86876
  return;
@@ -86749,7 +86924,7 @@ function registerWorkspaceCommand(program3) {
86749
86924
  if (!dir)
86750
86925
  return;
86751
86926
  const gitDir = resolve44(dir, ".git");
86752
- if (!existsSync72(gitDir)) {
86927
+ if (!existsSync73(gitDir)) {
86753
86928
  process.stdout.write(`Workspace is not a git repository.
86754
86929
  `);
86755
86930
  return;
@@ -86774,7 +86949,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
86774
86949
  const agentsDir = resolveAgentsDir(config);
86775
86950
  const agentDir = resolve44(agentsDir, agentName);
86776
86951
  const dir = resolveAgentWorkspaceDir(agentDir);
86777
- if (!existsSync72(dir)) {
86952
+ if (!existsSync73(dir)) {
86778
86953
  process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
86779
86954
  `);
86780
86955
  return;
@@ -86810,8 +86985,8 @@ function safeParseInt(value, fallback) {
86810
86985
  init_helpers();
86811
86986
  init_loader();
86812
86987
  init_merge();
86813
- import { copyFileSync as copyFileSync11, existsSync as existsSync73, readFileSync as readFileSync65, writeFileSync as writeFileSync22 } from "node:fs";
86814
- import { join as join73, resolve as resolve45 } from "node:path";
86988
+ import { copyFileSync as copyFileSync11, existsSync as existsSync74, readFileSync as readFileSync65, writeFileSync as writeFileSync22 } from "node:fs";
86989
+ import { join as join74, resolve as resolve45 } from "node:path";
86815
86990
  init_scaffold();
86816
86991
  init_profiles();
86817
86992
  init_schema();
@@ -86828,7 +87003,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
86828
87003
  const agentsDir = resolveAgentsDir(config);
86829
87004
  const agentDir = resolve45(agentsDir, agentName);
86830
87005
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
86831
- if (!existsSync73(workspaceDir)) {
87006
+ if (!existsSync74(workspaceDir)) {
86832
87007
  console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
86833
87008
  process.exit(1);
86834
87009
  }
@@ -86837,7 +87012,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
86837
87012
  profileName,
86838
87013
  profilePath,
86839
87014
  workspaceDir,
86840
- soulPath: join73(workspaceDir, "SOUL.md"),
87015
+ soulPath: join74(workspaceDir, "SOUL.md"),
86841
87016
  soul: merged.soul
86842
87017
  };
86843
87018
  }
@@ -86854,7 +87029,7 @@ function registerSoulCommand(program3) {
86854
87029
  const t = resolveSoulTargetOrExit(program3, agentName);
86855
87030
  if (!t)
86856
87031
  return;
86857
- if (!existsSync73(t.soulPath)) {
87032
+ if (!existsSync74(t.soulPath)) {
86858
87033
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
86859
87034
  process.exit(1);
86860
87035
  }
@@ -86869,7 +87044,7 @@ function registerSoulCommand(program3) {
86869
87044
  console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
86870
87045
  process.exit(1);
86871
87046
  }
86872
- const exists = existsSync73(t.soulPath);
87047
+ const exists = existsSync74(t.soulPath);
86873
87048
  if (exists && !opts.yes) {
86874
87049
  if (!isInteractive()) {
86875
87050
  console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
@@ -86884,7 +87059,7 @@ function registerSoulCommand(program3) {
86884
87059
  let backupPath;
86885
87060
  if (exists) {
86886
87061
  backupPath = `${t.soulPath}.bak`;
86887
- if (existsSync73(backupPath)) {
87062
+ if (existsSync74(backupPath)) {
86888
87063
  backupPath = `${t.soulPath}.bak.${Date.now()}`;
86889
87064
  }
86890
87065
  copyFileSync11(t.soulPath, backupPath);
@@ -86903,8 +87078,8 @@ function registerSoulCommand(program3) {
86903
87078
  // src/cli/debug.ts
86904
87079
  init_helpers();
86905
87080
  init_loader();
86906
- import { existsSync as existsSync74, readFileSync as readFileSync66, readdirSync as readdirSync25, statSync as statSync43 } from "node:fs";
86907
- import { resolve as resolve46, join as join74 } from "node:path";
87081
+ import { existsSync as existsSync75, readFileSync as readFileSync66, readdirSync as readdirSync25, statSync as statSync44 } from "node:fs";
87082
+ import { resolve as resolve46, join as join75 } from "node:path";
86908
87083
  import { createHash as createHash15 } from "node:crypto";
86909
87084
  init_merge();
86910
87085
  init_hindsight2();
@@ -86915,8 +87090,8 @@ function estimateTokens(bytes) {
86915
87090
  return Math.round(bytes / 3.7);
86916
87091
  }
86917
87092
  function readMcpServerNames(agentDir) {
86918
- const mcpPath = join74(agentDir, ".mcp.json");
86919
- if (!existsSync74(mcpPath))
87093
+ const mcpPath = join75(agentDir, ".mcp.json");
87094
+ if (!existsSync75(mcpPath))
86920
87095
  return [];
86921
87096
  try {
86922
87097
  const parsed = JSON.parse(readFileSync66(mcpPath, "utf-8"));
@@ -86929,8 +87104,8 @@ function sha256(content) {
86929
87104
  return createHash15("sha256").update(content).digest("hex").slice(0, 16);
86930
87105
  }
86931
87106
  function findLatestTranscriptJsonl(claudeConfigDir) {
86932
- const projectsDir = join74(claudeConfigDir, "projects");
86933
- if (!existsSync74(projectsDir))
87107
+ const projectsDir = join75(claudeConfigDir, "projects");
87108
+ if (!existsSync75(projectsDir))
86934
87109
  return;
86935
87110
  try {
86936
87111
  const entries = readdirSync25(projectsDir, { withFileTypes: true });
@@ -86938,11 +87113,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
86938
87113
  for (const entry of entries) {
86939
87114
  if (!entry.isDirectory())
86940
87115
  continue;
86941
- const projectPath = join74(projectsDir, entry.name);
86942
- const transcriptPath = join74(projectPath, "transcript.jsonl");
86943
- if (!existsSync74(transcriptPath))
87116
+ const projectPath = join75(projectsDir, entry.name);
87117
+ const transcriptPath = join75(projectPath, "transcript.jsonl");
87118
+ if (!existsSync75(transcriptPath))
86944
87119
  continue;
86945
- const stat3 = statSync43(transcriptPath);
87120
+ const stat3 = statSync44(transcriptPath);
86946
87121
  if (!latest || stat3.mtimeMs > latest.mtime) {
86947
87122
  latest = { path: transcriptPath, mtime: stat3.mtimeMs };
86948
87123
  }
@@ -87003,16 +87178,16 @@ function registerDebugCommand(program3) {
87003
87178
  }
87004
87179
  const agentsDir = resolveAgentsDir(config);
87005
87180
  const agentDir = resolve46(agentsDir, agentName);
87006
- if (!existsSync74(agentDir)) {
87181
+ if (!existsSync75(agentDir)) {
87007
87182
  console.error(`Agent directory not found: ${agentDir}`);
87008
87183
  process.exit(1);
87009
87184
  }
87010
87185
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
87011
- const claudeConfigDir = join74(agentDir, ".claude");
87012
- const claudeMdPath = join74(agentDir, "CLAUDE.md");
87013
- const soulMdPath = join74(agentDir, "SOUL.md");
87014
- const workspaceSoulMdPath = join74(workspaceDir, "SOUL.md");
87015
- const handoffPath = join74(agentDir, ".handoff.md");
87186
+ const claudeConfigDir = join75(agentDir, ".claude");
87187
+ const claudeMdPath = join75(agentDir, "CLAUDE.md");
87188
+ const soulMdPath = join75(agentDir, "SOUL.md");
87189
+ const workspaceSoulMdPath = join75(workspaceDir, "SOUL.md");
87190
+ const handoffPath = join75(agentDir, ".handoff.md");
87016
87191
  const lastN = parseInt(opts.last, 10);
87017
87192
  if (isNaN(lastN) || lastN < 1) {
87018
87193
  console.error("--last must be a positive integer");
@@ -87058,7 +87233,7 @@ function registerDebugCommand(program3) {
87058
87233
  }
87059
87234
  console.log(`=== Append System Prompt (per-session) ===
87060
87235
  `);
87061
- const handoffContent = existsSync74(handoffPath) ? readFileSync66(handoffPath, "utf-8") : "";
87236
+ const handoffContent = existsSync75(handoffPath) ? readFileSync66(handoffPath, "utf-8") : "";
87062
87237
  if (handoffContent.trim().length > 0) {
87063
87238
  console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
87064
87239
  console.log(handoffContent);
@@ -87069,7 +87244,7 @@ function registerDebugCommand(program3) {
87069
87244
  }
87070
87245
  console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
87071
87246
  `);
87072
- const claudeMdContent = existsSync74(claudeMdPath) ? readFileSync66(claudeMdPath, "utf-8") : "";
87247
+ const claudeMdContent = existsSync75(claudeMdPath) ? readFileSync66(claudeMdPath, "utf-8") : "";
87073
87248
  if (claudeMdContent.trim().length > 0) {
87074
87249
  console.log(`(${formatBytes(claudeMdContent.length)})`);
87075
87250
  console.log(claudeMdContent);
@@ -87080,7 +87255,7 @@ function registerDebugCommand(program3) {
87080
87255
  }
87081
87256
  console.log(`=== Persona (SOUL.md) ===
87082
87257
  `);
87083
- const soulMdContent = existsSync74(soulMdPath) ? readFileSync66(soulMdPath, "utf-8") : existsSync74(workspaceSoulMdPath) ? readFileSync66(workspaceSoulMdPath, "utf-8") : "";
87258
+ const soulMdContent = existsSync75(soulMdPath) ? readFileSync66(soulMdPath, "utf-8") : existsSync75(workspaceSoulMdPath) ? readFileSync66(workspaceSoulMdPath, "utf-8") : "";
87084
87259
  if (soulMdContent.trim().length > 0) {
87085
87260
  console.log(`(${formatBytes(soulMdContent.length)})`);
87086
87261
  console.log(soulMdContent);
@@ -87141,11 +87316,11 @@ function registerDebugCommand(program3) {
87141
87316
  const soulMdBytes = soulMdContent.length;
87142
87317
  const perTurnBytes = dynamicResult.concatenated.length;
87143
87318
  const userBytes = userMessage?.text.length ?? 0;
87144
- const fleetDir = join74(agentsDir, "..", "fleet");
87145
- const fleetInvPath = join74(fleetDir, "switchroom-invariants.md");
87146
- const fleetClaudePath = join74(fleetDir, "CLAUDE.md");
87147
- const fleetInvBytes = existsSync74(fleetInvPath) ? readFileSync66(fleetInvPath, "utf-8").length : 0;
87148
- const fleetClaudeBytes = existsSync74(fleetClaudePath) ? readFileSync66(fleetClaudePath, "utf-8").length : 0;
87319
+ const fleetDir = join75(agentsDir, "..", "fleet");
87320
+ const fleetInvPath = join75(fleetDir, "switchroom-invariants.md");
87321
+ const fleetClaudePath = join75(fleetDir, "CLAUDE.md");
87322
+ const fleetInvBytes = existsSync75(fleetInvPath) ? readFileSync66(fleetInvPath, "utf-8").length : 0;
87323
+ const fleetClaudeBytes = existsSync75(fleetClaudePath) ? readFileSync66(fleetClaudePath, "utf-8").length : 0;
87149
87324
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
87150
87325
  const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
87151
87326
  console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
@@ -87178,8 +87353,8 @@ init_source();
87178
87353
 
87179
87354
  // src/worktree/claim.ts
87180
87355
  import { execFileSync as execFileSync23 } from "node:child_process";
87181
- import { closeSync as closeSync13, mkdirSync as mkdirSync39, openSync as openSync13, existsSync as existsSync76, unlinkSync as unlinkSync17 } from "node:fs";
87182
- import { join as join76, resolve as resolve48 } from "node:path";
87356
+ import { closeSync as closeSync13, mkdirSync as mkdirSync39, openSync as openSync13, existsSync as existsSync77, unlinkSync as unlinkSync17 } from "node:fs";
87357
+ import { join as join77, resolve as resolve48 } from "node:path";
87183
87358
  import { homedir as homedir46 } from "node:os";
87184
87359
  import { randomBytes as randomBytes13 } from "node:crypto";
87185
87360
 
@@ -87190,16 +87365,16 @@ import {
87190
87365
  readFileSync as readFileSync67,
87191
87366
  readdirSync as readdirSync26,
87192
87367
  unlinkSync as unlinkSync16,
87193
- existsSync as existsSync75,
87368
+ existsSync as existsSync76,
87194
87369
  renameSync as renameSync16
87195
87370
  } from "node:fs";
87196
- import { join as join75, resolve as resolve47 } from "node:path";
87371
+ import { join as join76, resolve as resolve47 } from "node:path";
87197
87372
  import { homedir as homedir45 } from "node:os";
87198
87373
  function registryDir() {
87199
- return resolve47(process.env.SWITCHROOM_WORKTREE_DIR ?? join75(homedir45(), ".switchroom", "worktrees"));
87374
+ return resolve47(process.env.SWITCHROOM_WORKTREE_DIR ?? join76(homedir45(), ".switchroom", "worktrees"));
87200
87375
  }
87201
87376
  function recordPath(id) {
87202
- return join75(registryDir(), `${id}.json`);
87377
+ return join76(registryDir(), `${id}.json`);
87203
87378
  }
87204
87379
  function ensureDir2() {
87205
87380
  mkdirSync38(registryDir(), { recursive: true });
@@ -87250,7 +87425,7 @@ function acquireRepoLock(repoPath) {
87250
87425
  const lockDir = registryDir();
87251
87426
  mkdirSync39(lockDir, { recursive: true });
87252
87427
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
87253
- const lockPath = join76(lockDir, `.lock-${lockName}`);
87428
+ const lockPath = join77(lockDir, `.lock-${lockName}`);
87254
87429
  const deadline = Date.now() + 5000;
87255
87430
  let fd = null;
87256
87431
  while (fd === null) {
@@ -87277,7 +87452,7 @@ function acquireRepoLock(repoPath) {
87277
87452
  }
87278
87453
  var DEFAULT_CONCURRENCY = 5;
87279
87454
  function worktreesBaseDir() {
87280
- return resolve48(process.env.SWITCHROOM_WORKTREE_BASE ?? join76(homedir46(), ".switchroom", "worktree-checkouts"));
87455
+ return resolve48(process.env.SWITCHROOM_WORKTREE_BASE ?? join77(homedir46(), ".switchroom", "worktree-checkouts"));
87281
87456
  }
87282
87457
  function shortId() {
87283
87458
  return randomBytes13(4).toString("hex");
@@ -87299,12 +87474,12 @@ function resolveRepoPath(repo, codeRepos) {
87299
87474
  }
87300
87475
  function expandHome(p) {
87301
87476
  if (p.startsWith("~/"))
87302
- return join76(homedir46(), p.slice(2));
87477
+ return join77(homedir46(), p.slice(2));
87303
87478
  return p;
87304
87479
  }
87305
87480
  async function claimWorktree(input, codeRepos) {
87306
87481
  const repoPath = resolveRepoPath(input.repo, codeRepos);
87307
- if (!existsSync76(repoPath)) {
87482
+ if (!existsSync77(repoPath)) {
87308
87483
  throw new Error(`Repository path does not exist: ${repoPath}`);
87309
87484
  }
87310
87485
  let concurrencyCap = DEFAULT_CONCURRENCY;
@@ -87327,7 +87502,7 @@ async function claimWorktree(input, codeRepos) {
87327
87502
  branch = `task/${taskSuffix}-${id}`;
87328
87503
  const baseDir = worktreesBaseDir();
87329
87504
  mkdirSync39(baseDir, { recursive: true });
87330
- worktreePath = join76(baseDir, `${id}-${taskSuffix}`);
87505
+ worktreePath = join77(baseDir, `${id}-${taskSuffix}`);
87331
87506
  const ambientOwner = process.env.SWITCHROOM_AGENT_NAME;
87332
87507
  const ownerAgent = input.ownerAgent ?? (ambientOwner != null && ambientOwner !== "" ? ambientOwner : undefined);
87333
87508
  const now = new Date().toISOString();
@@ -87360,7 +87535,7 @@ async function claimWorktree(input, codeRepos) {
87360
87535
 
87361
87536
  // src/worktree/release.ts
87362
87537
  import { execFileSync as execFileSync24 } from "node:child_process";
87363
- import { existsSync as existsSync77 } from "node:fs";
87538
+ import { existsSync as existsSync78 } from "node:fs";
87364
87539
  function releaseWorktree(input) {
87365
87540
  const { id } = input;
87366
87541
  const record2 = readRecord(id);
@@ -87368,7 +87543,7 @@ function releaseWorktree(input) {
87368
87543
  return { released: true };
87369
87544
  }
87370
87545
  let gitSuccess = true;
87371
- if (existsSync77(record2.path)) {
87546
+ if (existsSync78(record2.path)) {
87372
87547
  try {
87373
87548
  execFileSync24("git", ["worktree", "remove", "--force", record2.path], {
87374
87549
  cwd: record2.repo,
@@ -87407,7 +87582,7 @@ function listWorktrees() {
87407
87582
 
87408
87583
  // src/worktree/reaper.ts
87409
87584
  import { execFileSync as execFileSync25 } from "node:child_process";
87410
- import { existsSync as existsSync78 } from "node:fs";
87585
+ import { existsSync as existsSync79 } from "node:fs";
87411
87586
  var STALE_THRESHOLD_MS = 10 * 60 * 1000;
87412
87587
  function reapSkipReasonText(action) {
87413
87588
  switch (action) {
@@ -87468,7 +87643,7 @@ function planReaper(nowMs, deps = {}) {
87468
87643
  const plan = [];
87469
87644
  for (const record2 of listRecords()) {
87470
87645
  const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
87471
- const worktreeExists = existsSync78(record2.path);
87646
+ const worktreeExists = existsSync79(record2.path);
87472
87647
  if (!worktreeExists) {
87473
87648
  plan.push({
87474
87649
  record: record2,
@@ -87549,16 +87724,16 @@ function runReaper(nowMs, deps = {}) {
87549
87724
  // src/worktree/gc.ts
87550
87725
  import { execFileSync as execFileSync26 } from "node:child_process";
87551
87726
  import {
87552
- existsSync as existsSync79,
87727
+ existsSync as existsSync80,
87553
87728
  readFileSync as readFileSync68,
87554
87729
  readdirSync as readdirSync27,
87555
- statSync as statSync44,
87730
+ statSync as statSync45,
87556
87731
  renameSync as renameSync17,
87557
87732
  mkdirSync as mkdirSync40,
87558
87733
  rmSync as rmSync15
87559
87734
  } from "node:fs";
87560
87735
  import { homedir as homedir47 } from "node:os";
87561
- import { join as join77, resolve as resolve49 } from "node:path";
87736
+ import { join as join78, resolve as resolve49 } from "node:path";
87562
87737
  function parseGitdirPointer(dotGitFileContents) {
87563
87738
  const m = /^gitdir:\s*(.+?)\s*$/m.exec(dotGitFileContents);
87564
87739
  return m ? m[1] : null;
@@ -87673,17 +87848,17 @@ function defaultPrSignal(repo, branch, exec) {
87673
87848
  }
87674
87849
  }
87675
87850
  function trashRoot() {
87676
- return resolve49(process.env.SWITCHROOM_WORKTREE_TRASH ?? join77(homedir47(), ".switchroom", "worktree-gc-trash"));
87851
+ return resolve49(process.env.SWITCHROOM_WORKTREE_TRASH ?? join78(homedir47(), ".switchroom", "worktree-gc-trash"));
87677
87852
  }
87678
87853
  function planGc(roots, deps = {}) {
87679
- const exists = deps.existsSync ?? existsSync79;
87854
+ const exists = deps.existsSync ?? existsSync80;
87680
87855
  const readDir = deps.readDir ?? ((p) => readdirSync27(p));
87681
87856
  const readFile4 = deps.readFile ?? ((p) => readFileSync68(p, "utf8"));
87682
- const stat3 = deps.stat ?? ((p) => statSync44(p));
87857
+ const stat3 = deps.stat ?? ((p) => statSync45(p));
87683
87858
  const exec = deps.exec ?? defaultExec;
87684
87859
  const prSignal = deps.prSignal ?? ((repo, branch) => defaultPrSignal(repo, branch, exec));
87685
87860
  const stamp = deps.dateStamp ?? "undated";
87686
- const trash = join77(trashRoot(), stamp);
87861
+ const trash = join78(trashRoot(), stamp);
87687
87862
  let claimed;
87688
87863
  try {
87689
87864
  claimed = new Set(listRecords().map((r) => resolve49(r.path)));
@@ -87719,10 +87894,10 @@ function planGc(roots, deps = {}) {
87719
87894
  continue;
87720
87895
  }
87721
87896
  for (const name of entries) {
87722
- const dir = join77(root, name);
87897
+ const dir = join78(root, name);
87723
87898
  if (isEphemeralPath(dir))
87724
87899
  continue;
87725
- const dotGit = join77(dir, ".git");
87900
+ const dotGit = join78(dir, ".git");
87726
87901
  if (!exists(dotGit))
87727
87902
  continue;
87728
87903
  let st;
@@ -87751,7 +87926,7 @@ function planGc(roots, deps = {}) {
87751
87926
  ownerRepos.add(repoRoot);
87752
87927
  if (exists(ptr))
87753
87928
  continue;
87754
- orphans.push({ dir, owner: repoRoot, dest: join77(trash, name) });
87929
+ orphans.push({ dir, owner: repoRoot, dest: join78(trash, name) });
87755
87930
  }
87756
87931
  }
87757
87932
  const registered = [];
@@ -87855,14 +88030,14 @@ function selectPurgeTargets(entries, olderThanDays) {
87855
88030
  return entries.filter((e) => e.ageDays >= olderThanDays).map((e) => e.path);
87856
88031
  }
87857
88032
  function listTrashEntries(nowMs, deps = {}) {
87858
- const exists = deps.existsSync ?? existsSync79;
88033
+ const exists = deps.existsSync ?? existsSync80;
87859
88034
  const readDir = deps.readDir ?? ((p) => readdirSync27(p));
87860
88035
  const root = trashRoot();
87861
88036
  if (!exists(root))
87862
88037
  return [];
87863
88038
  const out = [];
87864
88039
  for (const stamp of readDir(root)) {
87865
- const stampDir = join77(root, stamp);
88040
+ const stampDir = join78(root, stamp);
87866
88041
  let names;
87867
88042
  try {
87868
88043
  names = readDir(stampDir);
@@ -87870,10 +88045,10 @@ function listTrashEntries(nowMs, deps = {}) {
87870
88045
  continue;
87871
88046
  }
87872
88047
  for (const name of names) {
87873
- const p = join77(stampDir, name);
88048
+ const p = join78(stampDir, name);
87874
88049
  let mtimeMs = nowMs;
87875
88050
  try {
87876
- mtimeMs = statSync44(p).mtimeMs;
88051
+ mtimeMs = statSync45(p).mtimeMs;
87877
88052
  } catch {}
87878
88053
  out.push({ path: p, ageDays: (nowMs - mtimeMs) / 86400000 });
87879
88054
  }
@@ -87894,7 +88069,7 @@ function purgeTrash(paths) {
87894
88069
  return { deleted, errors: errors2 };
87895
88070
  }
87896
88071
  function defaultRoots() {
87897
- return [join77(homedir47(), "code")];
88072
+ return [join78(homedir47(), "code")];
87898
88073
  }
87899
88074
 
87900
88075
  // src/cli/worktree.ts
@@ -88122,7 +88297,7 @@ import {
88122
88297
  rmSync as rmSync16,
88123
88298
  writeFileSync as writeFileSync24
88124
88299
  } from "node:fs";
88125
- import { join as join78 } from "node:path";
88300
+ import { join as join79 } from "node:path";
88126
88301
  function encodeCredentialsFilename(email) {
88127
88302
  const SAFE = new Set([
88128
88303
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -88312,16 +88487,16 @@ function resolveCredentialsDir(env2) {
88312
88487
  if (explicit && explicit.length > 0)
88313
88488
  return explicit;
88314
88489
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
88315
- return join78(stateBase, "google-workspace-mcp", "credentials");
88490
+ return join79(stateBase, "google-workspace-mcp", "credentials");
88316
88491
  }
88317
88492
  function writeSeedFile(dir, email, seed) {
88318
88493
  mkdirSync41(dir, { recursive: true, mode: 448 });
88319
88494
  chmodSync10(dir, 448);
88320
88495
  for (const name of readdirSync28(dir)) {
88321
- rmSync16(join78(dir, name), { force: true, recursive: true });
88496
+ rmSync16(join79(dir, name), { force: true, recursive: true });
88322
88497
  }
88323
88498
  const filename = encodeCredentialsFilename(email);
88324
- const filePath = join78(dir, filename);
88499
+ const filePath = join79(dir, filename);
88325
88500
  writeFileSync24(filePath, JSON.stringify(seed), { mode: 384 });
88326
88501
  chmodSync10(filePath, 384);
88327
88502
  return filePath;
@@ -88481,7 +88656,7 @@ function registerDriveMcpLauncherCommand(program3) {
88481
88656
  init_scaffold_integration();
88482
88657
  import { spawn as spawn5 } from "node:child_process";
88483
88658
  import { writeFileSync as writeFileSync25, mkdirSync as mkdirSync42 } from "node:fs";
88484
- import { dirname as dirname24, join as join79 } from "node:path";
88659
+ import { dirname as dirname24, join as join80 } from "node:path";
88485
88660
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
88486
88661
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
88487
88662
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
@@ -88521,7 +88696,7 @@ function writeRefreshHeartbeat(agentName, data) {
88521
88696
  function heartbeatPath(agentName) {
88522
88697
  const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
88523
88698
  if (override) {
88524
- return join79(override, `m365-launcher-${agentName}.heartbeat.json`);
88699
+ return join80(override, `m365-launcher-${agentName}.heartbeat.json`);
88525
88700
  }
88526
88701
  return "/state/agent/m365-launcher.heartbeat.json";
88527
88702
  }
@@ -88753,7 +88928,7 @@ function registerM365McpLauncherCommand(program3) {
88753
88928
  // src/cli/notion-mcp-launcher.ts
88754
88929
  init_scaffold_integration();
88755
88930
  import { spawn as spawn6 } from "node:child_process";
88756
- import { existsSync as existsSync80, mkdirSync as mkdirSync43, writeFileSync as writeFileSync26 } from "node:fs";
88931
+ import { existsSync as existsSync81, mkdirSync as mkdirSync43, writeFileSync as writeFileSync26 } from "node:fs";
88757
88932
  import { dirname as dirname25 } from "node:path";
88758
88933
  var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
88759
88934
  var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
@@ -88765,7 +88940,7 @@ function buildNotionMcpArgs(opts) {
88765
88940
  function defaultWriteHeartbeat(path7, contents) {
88766
88941
  try {
88767
88942
  const dir = dirname25(path7);
88768
- if (!existsSync80(dir))
88943
+ if (!existsSync81(dir))
88769
88944
  mkdirSync43(dir, { recursive: true });
88770
88945
  writeFileSync26(path7, contents);
88771
88946
  } catch {}
@@ -88877,7 +89052,7 @@ function registerNotionMcpLauncherCommand(program3) {
88877
89052
 
88878
89053
  // src/cli/deliver-file.ts
88879
89054
  init_client2();
88880
- import { readFileSync as readFileSync69, statSync as statSync45 } from "node:fs";
89055
+ import { readFileSync as readFileSync69, statSync as statSync46 } from "node:fs";
88881
89056
  import { basename as basename11 } from "node:path";
88882
89057
 
88883
89058
  // src/delivery/onedrive.ts
@@ -89216,7 +89391,7 @@ async function defaultResolveProvider() {
89216
89391
  }
89217
89392
  async function runDeliverFile(localPath, deps = {}) {
89218
89393
  const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
89219
- const sizeOf = deps.fileSize ?? ((p) => statSync45(p).size);
89394
+ const sizeOf = deps.fileSize ?? ((p) => statSync46(p).size);
89220
89395
  const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync69(p)));
89221
89396
  const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
89222
89397
  let size;
@@ -89538,8 +89713,8 @@ function runRedactStdin() {
89538
89713
  }
89539
89714
 
89540
89715
  // src/cli/status-ask.ts
89541
- import { readFileSync as readFileSync73, existsSync as existsSync85, readdirSync as readdirSync30 } from "node:fs";
89542
- import { join as join84 } from "node:path";
89716
+ import { readFileSync as readFileSync73, existsSync as existsSync86, readdirSync as readdirSync30 } from "node:fs";
89717
+ import { join as join85 } from "node:path";
89543
89718
  import { homedir as homedir50 } from "node:os";
89544
89719
 
89545
89720
  // src/status-ask/report.ts
@@ -89861,7 +90036,7 @@ function runReport(opts) {
89861
90036
  function resolveSources(explicitPath) {
89862
90037
  if (explicitPath != null && explicitPath.trim() !== "") {
89863
90038
  const trimmed = explicitPath.trim();
89864
- if (!existsSync85(trimmed)) {
90039
+ if (!existsSync86(trimmed)) {
89865
90040
  process.stderr.write(`status-ask report: ${trimmed}: file not found
89866
90041
  `);
89867
90042
  process.exit(1);
@@ -89875,9 +90050,9 @@ function resolveSources(explicitPath) {
89875
90050
  const config = loadConfig();
89876
90051
  agentsDir = resolveAgentsDir(config);
89877
90052
  } catch {
89878
- agentsDir = join84(homedir50(), ".switchroom", "agents");
90053
+ agentsDir = join85(homedir50(), ".switchroom", "agents");
89879
90054
  }
89880
- if (!existsSync85(agentsDir))
90055
+ if (!existsSync86(agentsDir))
89881
90056
  return [];
89882
90057
  const sources = [];
89883
90058
  let entries;
@@ -89887,8 +90062,8 @@ function resolveSources(explicitPath) {
89887
90062
  return [];
89888
90063
  }
89889
90064
  for (const name of entries) {
89890
- const path8 = join84(agentsDir, name, "runtime-metrics.jsonl");
89891
- if (existsSync85(path8)) {
90065
+ const path8 = join85(agentsDir, name, "runtime-metrics.jsonl");
90066
+ if (existsSync86(path8)) {
89892
90067
  sources.push({ path: path8, agent: name });
89893
90068
  }
89894
90069
  }
@@ -89918,32 +90093,32 @@ var import_yaml21 = __toESM(require_dist(), 1);
89918
90093
  init_paths();
89919
90094
  import {
89920
90095
  closeSync as closeSync14,
89921
- existsSync as existsSync86,
90096
+ existsSync as existsSync87,
89922
90097
  fsyncSync as fsyncSync8,
89923
90098
  mkdirSync as mkdirSync48,
89924
90099
  openSync as openSync14,
89925
90100
  readdirSync as readdirSync31,
89926
90101
  readFileSync as readFileSync74,
89927
90102
  renameSync as renameSync19,
89928
- statSync as statSync47,
90103
+ statSync as statSync48,
89929
90104
  unlinkSync as unlinkSync18,
89930
90105
  writeSync as writeSync10
89931
90106
  } from "node:fs";
89932
- import { join as join85, resolve as resolve52 } from "node:path";
90107
+ import { join as join86, resolve as resolve52 } from "node:path";
89933
90108
  var STAGING_SUBDIR = ".staging";
89934
90109
  function overlayPathsFor(agent, opts = {}) {
89935
90110
  const base = opts.root ? resolve52(opts.root, agent) : resolve52(resolveDualPath(`~/.switchroom/agents/${agent}`));
89936
- const scheduleDir = join85(base, "schedule.d");
89937
- const scheduleStagingDir = join85(scheduleDir, STAGING_SUBDIR);
89938
- const skillsDir = join85(base, "skills.d");
89939
- const skillsStagingDir = join85(skillsDir, STAGING_SUBDIR);
90111
+ const scheduleDir = join86(base, "schedule.d");
90112
+ const scheduleStagingDir = join86(scheduleDir, STAGING_SUBDIR);
90113
+ const skillsDir = join86(base, "skills.d");
90114
+ const skillsStagingDir = join86(skillsDir, STAGING_SUBDIR);
89940
90115
  return {
89941
90116
  agentRoot: base,
89942
90117
  scheduleDir,
89943
90118
  scheduleStagingDir,
89944
90119
  skillsDir,
89945
90120
  skillsStagingDir,
89946
- lockPath: join85(base, ".lock"),
90121
+ lockPath: join86(base, ".lock"),
89947
90122
  stagingDir: scheduleStagingDir
89948
90123
  };
89949
90124
  }
@@ -89969,7 +90144,7 @@ function withAgentLock(paths, fn) {
89969
90144
  if (e.code !== "EEXIST")
89970
90145
  throw err;
89971
90146
  try {
89972
- const age = Date.now() - statSync47(paths.lockPath).mtimeMs;
90147
+ const age = Date.now() - statSync48(paths.lockPath).mtimeMs;
89973
90148
  if (age > 30000) {
89974
90149
  unlinkSync18(paths.lockPath);
89975
90150
  continue;
@@ -89997,8 +90172,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
89997
90172
  const paths = overlayPathsFor(agent, opts);
89998
90173
  return withAgentLock(paths, () => {
89999
90174
  ensureDirs(paths);
90000
- const stagingPath = join85(paths.scheduleStagingDir, `${slug}.yaml`);
90001
- const finalPath = join85(paths.scheduleDir, `${slug}.yaml`);
90175
+ const stagingPath = join86(paths.scheduleStagingDir, `${slug}.yaml`);
90176
+ const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
90002
90177
  const fd = openSync14(stagingPath, "w", 384);
90003
90178
  try {
90004
90179
  writeSync10(fd, yamlText);
@@ -90014,8 +90189,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90014
90189
  const paths = overlayPathsFor(agent, opts);
90015
90190
  return withAgentLock(paths, () => {
90016
90191
  ensureSkillsDirs(paths);
90017
- const stagingPath = join85(paths.skillsStagingDir, `${slug}.yaml`);
90018
- const finalPath = join85(paths.skillsDir, `${slug}.yaml`);
90192
+ const stagingPath = join86(paths.skillsStagingDir, `${slug}.yaml`);
90193
+ const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
90019
90194
  const fd = openSync14(stagingPath, "w", 384);
90020
90195
  try {
90021
90196
  writeSync10(fd, yamlText);
@@ -90030,8 +90205,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90030
90205
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
90031
90206
  const paths = overlayPathsFor(agent, opts);
90032
90207
  return withAgentLock(paths, () => {
90033
- const finalPath = join85(paths.skillsDir, `${slug}.yaml`);
90034
- if (!existsSync86(finalPath))
90208
+ const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
90209
+ if (!existsSync87(finalPath))
90035
90210
  return false;
90036
90211
  unlinkSync18(finalPath);
90037
90212
  return true;
@@ -90039,13 +90214,13 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
90039
90214
  }
90040
90215
  function listSkillsOverlayEntries(agent, opts = {}) {
90041
90216
  const paths = overlayPathsFor(agent, opts);
90042
- if (!existsSync86(paths.skillsDir))
90217
+ if (!existsSync87(paths.skillsDir))
90043
90218
  return [];
90044
90219
  const out = [];
90045
90220
  for (const name of readdirSync31(paths.skillsDir)) {
90046
90221
  if (!/\.ya?ml$/i.test(name))
90047
90222
  continue;
90048
- const full = join85(paths.skillsDir, name);
90223
+ const full = join86(paths.skillsDir, name);
90049
90224
  try {
90050
90225
  const raw = readFileSync74(full, "utf-8");
90051
90226
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -90057,8 +90232,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
90057
90232
  function deleteOverlayEntry(agent, slug, opts = {}) {
90058
90233
  const paths = overlayPathsFor(agent, opts);
90059
90234
  return withAgentLock(paths, () => {
90060
- const finalPath = join85(paths.scheduleDir, `${slug}.yaml`);
90061
- if (!existsSync86(finalPath))
90235
+ const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
90236
+ if (!existsSync87(finalPath))
90062
90237
  return false;
90063
90238
  unlinkSync18(finalPath);
90064
90239
  return true;
@@ -90066,13 +90241,13 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
90066
90241
  }
90067
90242
  function listOverlayEntries(agent, opts = {}) {
90068
90243
  const paths = overlayPathsFor(agent, opts);
90069
- if (!existsSync86(paths.scheduleDir))
90244
+ if (!existsSync87(paths.scheduleDir))
90070
90245
  return [];
90071
90246
  const out = [];
90072
90247
  for (const name of readdirSync31(paths.scheduleDir)) {
90073
90248
  if (!/\.ya?ml$/i.test(name))
90074
90249
  continue;
90075
- const full = join85(paths.scheduleDir, name);
90250
+ const full = join86(paths.scheduleDir, name);
90076
90251
  try {
90077
90252
  const raw = readFileSync74(full, "utf-8");
90078
90253
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -90294,7 +90469,7 @@ function reconcileAgentCronOnly(agent) {
90294
90469
  // src/cli/agent-config-pending.ts
90295
90470
  import {
90296
90471
  closeSync as closeSync15,
90297
- existsSync as existsSync87,
90472
+ existsSync as existsSync88,
90298
90473
  fsyncSync as fsyncSync9,
90299
90474
  mkdirSync as mkdirSync49,
90300
90475
  openSync as openSync15,
@@ -90305,12 +90480,12 @@ import {
90305
90480
  writeFileSync as writeFileSync31,
90306
90481
  writeSync as writeSync11
90307
90482
  } from "node:fs";
90308
- import { join as join86 } from "node:path";
90483
+ import { join as join87 } from "node:path";
90309
90484
  import { randomBytes as randomBytes15 } from "node:crypto";
90310
90485
  var STAGE_ID_PREFIX = "cap_";
90311
90486
  function pendingDir(agent, opts = {}) {
90312
90487
  const paths = overlayPathsFor(agent, opts);
90313
- return join86(paths.scheduleDir, ".pending");
90488
+ return join87(paths.scheduleDir, ".pending");
90314
90489
  }
90315
90490
  function ensurePendingDir(agent, opts = {}) {
90316
90491
  const dir = pendingDir(agent, opts);
@@ -90323,8 +90498,8 @@ function newStageId() {
90323
90498
  function stagePendingScheduleEntry(opts) {
90324
90499
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
90325
90500
  const stageId = opts.stageId ?? newStageId();
90326
- const yamlPath = join86(dir, `${stageId}.yaml`);
90327
- const metaPath = join86(dir, `${stageId}.meta.json`);
90501
+ const yamlPath = join87(dir, `${stageId}.yaml`);
90502
+ const metaPath = join87(dir, `${stageId}.meta.json`);
90328
90503
  const meta = {
90329
90504
  v: 1,
90330
90505
  stage_id: stageId,
@@ -90351,16 +90526,16 @@ function stagePendingScheduleEntry(opts) {
90351
90526
  }
90352
90527
  function listPendingScheduleEntries(agent, opts = {}) {
90353
90528
  const dir = pendingDir(agent, opts);
90354
- if (!existsSync87(dir))
90529
+ if (!existsSync88(dir))
90355
90530
  return [];
90356
90531
  const out = [];
90357
90532
  for (const name of readdirSync32(dir).sort()) {
90358
90533
  if (!name.endsWith(".meta.json"))
90359
90534
  continue;
90360
90535
  const stageId = name.slice(0, -".meta.json".length);
90361
- const metaPath = join86(dir, name);
90362
- const yamlPath = join86(dir, `${stageId}.yaml`);
90363
- if (!existsSync87(yamlPath))
90536
+ const metaPath = join87(dir, name);
90537
+ const yamlPath = join87(dir, `${stageId}.yaml`);
90538
+ if (!existsSync88(yamlPath))
90364
90539
  continue;
90365
90540
  try {
90366
90541
  const meta = JSON.parse(readFileSync75(metaPath, "utf-8"));
@@ -90378,8 +90553,8 @@ function commitPendingScheduleEntry(opts) {
90378
90553
  return { committed: false, reason: "not_found" };
90379
90554
  const slug = match.meta.entry.name ?? match.stageId;
90380
90555
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
90381
- const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
90382
- if (existsSync87(finalPath)) {
90556
+ const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
90557
+ if (existsSync88(finalPath)) {
90383
90558
  return { committed: false, reason: "slug_collision" };
90384
90559
  }
90385
90560
  renameSync20(match.yamlPath, finalPath);
@@ -90401,7 +90576,7 @@ function denyPendingScheduleEntry(opts) {
90401
90576
  }
90402
90577
 
90403
90578
  // src/cli/agent-config-write.ts
90404
- import { existsSync as existsSync88, readFileSync as readFileSync76 } from "node:fs";
90579
+ import { existsSync as existsSync89, readFileSync as readFileSync76 } from "node:fs";
90405
90580
  import { execFileSync as execFileSync28 } from "node:child_process";
90406
90581
 
90407
90582
  // src/scheduler/schedule-report.ts
@@ -90791,7 +90966,7 @@ function scheduleRemove(opts) {
90791
90966
  }
90792
90967
  let priorContent = null;
90793
90968
  try {
90794
- if (existsSync88(match.path))
90969
+ if (existsSync89(match.path))
90795
90970
  priorContent = readFileSync76(match.path, "utf-8");
90796
90971
  } catch {}
90797
90972
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
@@ -90995,7 +91170,7 @@ function registerAgentConfigWriteCommands(program3) {
90995
91170
  }
90996
91171
  let blob;
90997
91172
  if (opts.jsonl) {
90998
- blob = existsSync88(opts.jsonl) ? readFileSync76(opts.jsonl, "utf-8") : "";
91173
+ blob = existsSync89(opts.jsonl) ? readFileSync76(opts.jsonl, "utf-8") : "";
90999
91174
  } else {
91000
91175
  try {
91001
91176
  blob = execFileSync28("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
@@ -91027,11 +91202,11 @@ function registerAgentConfigWriteCommands(program3) {
91027
91202
 
91028
91203
  // src/cli/agent-config-skill-write.ts
91029
91204
  var import_yaml22 = __toESM(require_dist(), 1);
91030
- import { existsSync as existsSync89 } from "node:fs";
91205
+ import { existsSync as existsSync90 } from "node:fs";
91031
91206
  init_reconcile_default_skills();
91032
91207
  init_agent_config();
91033
91208
  var import_yaml23 = __toESM(require_dist(), 1);
91034
- import { join as join87 } from "node:path";
91209
+ import { join as join88 } from "node:path";
91035
91210
  var MAX_SKILLS_PER_AGENT = 20;
91036
91211
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
91037
91212
  function exitCodeFor2(code) {
@@ -91106,8 +91281,8 @@ function skillInstall(opts) {
91106
91281
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
91107
91282
  }
91108
91283
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
91109
- const skillPath = join87(poolDir, skillName);
91110
- if (!existsSync89(skillPath)) {
91284
+ const skillPath = join88(poolDir, skillName);
91285
+ if (!existsSync90(skillPath)) {
91111
91286
  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.`);
91112
91287
  }
91113
91288
  const yamlText = import_yaml22.stringify({ skills: [skillName] });
@@ -91271,8 +91446,8 @@ function registerAgentConfigSkillWriteCommands(program3) {
91271
91446
  // src/cli/skill.ts
91272
91447
  import {
91273
91448
  closeSync as closeSync16,
91274
- existsSync as existsSync90,
91275
- lstatSync as lstatSync10,
91449
+ existsSync as existsSync91,
91450
+ lstatSync as lstatSync11,
91276
91451
  mkdirSync as mkdirSync50,
91277
91452
  mkdtempSync as mkdtempSync5,
91278
91453
  openSync as openSync16,
@@ -91281,11 +91456,11 @@ import {
91281
91456
  realpathSync as realpathSync7,
91282
91457
  renameSync as renameSync21,
91283
91458
  rmSync as rmSync18,
91284
- statSync as statSync48,
91459
+ statSync as statSync49,
91285
91460
  writeFileSync as writeFileSync32
91286
91461
  } from "node:fs";
91287
91462
  import { tmpdir as tmpdir5, homedir as homedir51 } from "node:os";
91288
- import { dirname as dirname30, join as join88, relative as relative2, resolve as resolve53 } from "node:path";
91463
+ import { dirname as dirname30, join as join89, relative as relative2, resolve as resolve53 } from "node:path";
91289
91464
  import { spawnSync as spawnSync16 } from "node:child_process";
91290
91465
 
91291
91466
  // src/cli/skill-common.ts
@@ -91519,7 +91694,7 @@ function scanForClaudeP2(content) {
91519
91694
  function resolveSkillsPoolDir2(override) {
91520
91695
  const raw = override ?? "~/.switchroom/skills";
91521
91696
  if (raw.startsWith("~/")) {
91522
- return join88(homedir51(), raw.slice(2));
91697
+ return join89(homedir51(), raw.slice(2));
91523
91698
  }
91524
91699
  if (raw === "~")
91525
91700
  return homedir51();
@@ -91551,14 +91726,14 @@ function isTarballPath(p) {
91551
91726
  }
91552
91727
  function loadFromDir(dir) {
91553
91728
  const abs = realpathSync7(dir);
91554
- if (!statSync48(abs).isDirectory()) {
91729
+ if (!statSync49(abs).isDirectory()) {
91555
91730
  fail3(`--from path is not a directory: ${dir}`);
91556
91731
  }
91557
91732
  const files = {};
91558
91733
  const walk2 = (sub) => {
91559
91734
  const entries = readdirSync33(sub, { withFileTypes: true });
91560
91735
  for (const ent of entries) {
91561
- const full = join88(sub, ent.name);
91736
+ const full = join89(sub, ent.name);
91562
91737
  const rel = relative2(abs, full);
91563
91738
  if (ent.isSymbolicLink()) {
91564
91739
  fail3(`refusing to read symlink inside --from dir: ${rel}`);
@@ -91593,7 +91768,7 @@ function loadFromTarball(tarPath) {
91593
91768
  fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
91594
91769
  }
91595
91770
  }
91596
- const staging = mkdtempSync5(join88(tmpdir5(), "skill-apply-extract-"));
91771
+ const staging = mkdtempSync5(join89(tmpdir5(), "skill-apply-extract-"));
91597
91772
  try {
91598
91773
  const flags = isGz ? ["-xzf"] : ["-xf"];
91599
91774
  const r = spawnSync16("tar", [
@@ -91679,8 +91854,8 @@ function validatePayload(name, files) {
91679
91854
  errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
91680
91855
  }
91681
91856
  } else if (PY_SCRIPT_RE2.test(path8)) {
91682
- const tmp = mkdtempSync5(join88(tmpdir5(), "skill-apply-py-"));
91683
- const tmpPy = join88(tmp, "check.py");
91857
+ const tmp = mkdtempSync5(join89(tmpdir5(), "skill-apply-py-"));
91858
+ const tmpPy = join89(tmp, "check.py");
91684
91859
  try {
91685
91860
  writeFileSync32(tmpPy, content);
91686
91861
  const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
@@ -91700,10 +91875,10 @@ function validatePayload(name, files) {
91700
91875
  function diffSummary(currentDir, files) {
91701
91876
  const lines = [];
91702
91877
  const currentFiles = {};
91703
- if (existsSync90(currentDir)) {
91878
+ if (existsSync91(currentDir)) {
91704
91879
  const walk2 = (sub) => {
91705
91880
  for (const ent of readdirSync33(sub, { withFileTypes: true })) {
91706
- const full = join88(sub, ent.name);
91881
+ const full = join89(sub, ent.name);
91707
91882
  const rel = relative2(currentDir, full);
91708
91883
  if (ent.isDirectory()) {
91709
91884
  walk2(full);
@@ -91736,13 +91911,13 @@ function diffSummary(currentDir, files) {
91736
91911
  `);
91737
91912
  }
91738
91913
  function writePayload(poolDir, name, files) {
91739
- if (!existsSync90(poolDir)) {
91914
+ if (!existsSync91(poolDir)) {
91740
91915
  mkdirSync50(poolDir, { recursive: true, mode: 493 });
91741
91916
  }
91742
- const target = join88(poolDir, name);
91917
+ const target = join89(poolDir, name);
91743
91918
  let targetIsSymlink = false;
91744
91919
  try {
91745
- const st = lstatSync10(target);
91920
+ const st = lstatSync11(target);
91746
91921
  if (st.isSymbolicLink()) {
91747
91922
  targetIsSymlink = true;
91748
91923
  }
@@ -91750,11 +91925,11 @@ function writePayload(poolDir, name, files) {
91750
91925
  if (targetIsSymlink) {
91751
91926
  fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
91752
91927
  }
91753
- const staging = mkdtempSync5(join88(poolDir, `.skill-apply-stage-${name}-`));
91928
+ const staging = mkdtempSync5(join89(poolDir, `.skill-apply-stage-${name}-`));
91754
91929
  let oldRename = null;
91755
91930
  try {
91756
91931
  for (const [path8, content] of Object.entries(files)) {
91757
- const full = join88(staging, path8);
91932
+ const full = join89(staging, path8);
91758
91933
  mkdirSync50(dirname30(full), { recursive: true, mode: 493 });
91759
91934
  const fd = openSync16(full, "wx");
91760
91935
  try {
@@ -91769,7 +91944,7 @@ function writePayload(poolDir, name, files) {
91769
91944
  }
91770
91945
  let targetExists = false;
91771
91946
  try {
91772
- lstatSync10(target);
91947
+ lstatSync11(target);
91773
91948
  targetExists = true;
91774
91949
  } catch {}
91775
91950
  if (targetExists) {
@@ -91785,9 +91960,9 @@ function writePayload(poolDir, name, files) {
91785
91960
  try {
91786
91961
  rmSync18(staging, { recursive: true, force: true });
91787
91962
  } catch {}
91788
- if (oldRename && existsSync90(oldRename)) {
91963
+ if (oldRename && existsSync91(oldRename)) {
91789
91964
  try {
91790
- if (existsSync90(target)) {
91965
+ if (existsSync91(target)) {
91791
91966
  rmSync18(target, { recursive: true, force: true });
91792
91967
  }
91793
91968
  renameSync21(oldRename, target);
@@ -91808,10 +91983,10 @@ function registerSkillCommand(program3) {
91808
91983
  files = loadFromStdin();
91809
91984
  } else {
91810
91985
  const fromPath = resolve53(opts.from);
91811
- if (!existsSync90(fromPath)) {
91986
+ if (!existsSync91(fromPath)) {
91812
91987
  fail3(`--from path does not exist: ${opts.from}`);
91813
91988
  }
91814
- const st = statSync48(fromPath);
91989
+ const st = statSync49(fromPath);
91815
91990
  if (st.isDirectory()) {
91816
91991
  files = loadFromDir(fromPath);
91817
91992
  } else if (isTarballPath(fromPath)) {
@@ -91832,7 +92007,7 @@ function registerSkillCommand(program3) {
91832
92007
  }
91833
92008
  const config = loadConfig();
91834
92009
  const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
91835
- const currentDir = join88(poolDir, name);
92010
+ const currentDir = join89(poolDir, name);
91836
92011
  console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
91837
92012
  console.log(source_default.bold("Diff vs current pool content:"));
91838
92013
  console.log(diffSummary(currentDir, files));
@@ -91864,8 +92039,8 @@ function sumBytes(files) {
91864
92039
  init_esm();
91865
92040
  import {
91866
92041
  closeSync as closeSync17,
91867
- existsSync as existsSync91,
91868
- lstatSync as lstatSync11,
92042
+ existsSync as existsSync92,
92043
+ lstatSync as lstatSync12,
91869
92044
  mkdirSync as mkdirSync51,
91870
92045
  mkdtempSync as mkdtempSync6,
91871
92046
  openSync as openSync17,
@@ -91873,11 +92048,11 @@ import {
91873
92048
  readdirSync as readdirSync34,
91874
92049
  renameSync as renameSync22,
91875
92050
  rmSync as rmSync19,
91876
- statSync as statSync49,
92051
+ statSync as statSync50,
91877
92052
  utimesSync,
91878
92053
  writeFileSync as writeFileSync33
91879
92054
  } from "node:fs";
91880
- import { dirname as dirname31, join as join89, relative as relative3, resolve as resolve54 } from "node:path";
92055
+ import { dirname as dirname31, join as join90, relative as relative3, resolve as resolve54 } from "node:path";
91881
92056
  import { homedir as homedir52, tmpdir as tmpdir6 } from "node:os";
91882
92057
  import { spawnSync as spawnSync17 } from "node:child_process";
91883
92058
  init_helpers();
@@ -91889,15 +92064,15 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
91889
92064
  var PERSONAL_SKILLS_SUBPATH = "personal-skills";
91890
92065
  function resolveConfigSkillsDir(agent) {
91891
92066
  const override = process.env.SWITCHROOM_CONFIG_DIR;
91892
- const candidate = override ? resolve54(override) : join89(homedir52(), ".switchroom-config");
91893
- if (!existsSync91(candidate))
92067
+ const candidate = override ? resolve54(override) : join90(homedir52(), ".switchroom-config");
92068
+ if (!existsSync92(candidate))
91894
92069
  return null;
91895
- return join89(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
92070
+ return join90(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
91896
92071
  }
91897
92072
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
91898
92073
  function sweepMirrorPriors(configSkillsRoot) {
91899
92074
  try {
91900
- if (!existsSync91(configSkillsRoot))
92075
+ if (!existsSync92(configSkillsRoot))
91901
92076
  return;
91902
92077
  const now = Date.now();
91903
92078
  for (const ent of readdirSync34(configSkillsRoot)) {
@@ -91910,7 +92085,7 @@ function sweepMirrorPriors(configSkillsRoot) {
91910
92085
  if (now - ts < MIRROR_PRIOR_TTL_MS)
91911
92086
  continue;
91912
92087
  try {
91913
- rmSync19(join89(configSkillsRoot, ent), { recursive: true, force: true });
92088
+ rmSync19(join90(configSkillsRoot, ent), { recursive: true, force: true });
91914
92089
  } catch {}
91915
92090
  }
91916
92091
  } catch {}
@@ -91919,11 +92094,11 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
91919
92094
  const configSkillsRoot = resolveConfigSkillsDir(agent);
91920
92095
  if (!configSkillsRoot)
91921
92096
  return;
91922
- const dest = join89(configSkillsRoot, name);
92097
+ const dest = join90(configSkillsRoot, name);
91923
92098
  try {
91924
92099
  if (liveSkillDir !== null) {
91925
92100
  try {
91926
- const st = lstatSync11(liveSkillDir);
92101
+ const st = lstatSync12(liveSkillDir);
91927
92102
  if (st.isSymbolicLink()) {
91928
92103
  process.stderr.write(source_default.yellow(`warning: refusing to mirror ${liveSkillDir} \u2014 source is a symlink
91929
92104
  `));
@@ -91933,20 +92108,20 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
91933
92108
  }
91934
92109
  if (liveSkillDir === null) {
91935
92110
  sweepMirrorPriors(configSkillsRoot);
91936
- if (existsSync91(dest)) {
91937
- const trash = join89(configSkillsRoot, `.${name}-trash-${Date.now()}`);
92111
+ if (existsSync92(dest)) {
92112
+ const trash = join90(configSkillsRoot, `.${name}-trash-${Date.now()}`);
91938
92113
  renameSync22(dest, trash);
91939
92114
  }
91940
92115
  return;
91941
92116
  }
91942
92117
  mkdirSync51(configSkillsRoot, { recursive: true, mode: 493 });
91943
92118
  sweepMirrorPriors(configSkillsRoot);
91944
- const staging = mkdtempSync6(join89(configSkillsRoot, `.${name}-staging-`));
92119
+ const staging = mkdtempSync6(join90(configSkillsRoot, `.${name}-staging-`));
91945
92120
  const walk2 = (src, dst) => {
91946
92121
  mkdirSync51(dst, { recursive: true, mode: 493 });
91947
92122
  for (const ent of readdirSync34(src, { withFileTypes: true })) {
91948
- const s = join89(src, ent.name);
91949
- const d = join89(dst, ent.name);
92123
+ const s = join90(src, ent.name);
92124
+ const d = join90(dst, ent.name);
91950
92125
  if (ent.isSymbolicLink())
91951
92126
  continue;
91952
92127
  if (ent.isDirectory())
@@ -91957,8 +92132,8 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
91957
92132
  }
91958
92133
  };
91959
92134
  walk2(liveSkillDir, staging);
91960
- if (existsSync91(dest)) {
91961
- const prior = join89(configSkillsRoot, `.${name}-prior-${Date.now()}`);
92135
+ if (existsSync92(dest)) {
92136
+ const prior = join90(configSkillsRoot, `.${name}-prior-${Date.now()}`);
91962
92137
  renameSync22(dest, prior);
91963
92138
  }
91964
92139
  renameSync22(staging, dest);
@@ -91988,17 +92163,17 @@ function resolveAgent(opts) {
91988
92163
  function resolveAgentsRoot(opts) {
91989
92164
  if (opts.root)
91990
92165
  return resolve54(opts.root);
91991
- return join89(homedir52(), ".switchroom", "agents");
92166
+ return join90(homedir52(), ".switchroom", "agents");
91992
92167
  }
91993
92168
  function personalSkillDir(agentsRoot, agent, name) {
91994
- return join89(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
92169
+ return join90(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
91995
92170
  }
91996
92171
  function trashDir(agentsRoot, agent) {
91997
- return join89(agentsRoot, agent, ".claude", TRASH_DIRNAME);
92172
+ return join90(agentsRoot, agent, ".claude", TRASH_DIRNAME);
91998
92173
  }
91999
92174
  function countPersonalSkills(agentsRoot, agent) {
92000
- const skillsDir = join89(agentsRoot, agent, ".claude", "skills");
92001
- if (!existsSync91(skillsDir))
92175
+ const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
92176
+ if (!existsSync92(skillsDir))
92002
92177
  return 0;
92003
92178
  let n = 0;
92004
92179
  for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
@@ -92029,13 +92204,13 @@ function readStdinSync2() {
92029
92204
  }
92030
92205
  function loadFromDir2(dir) {
92031
92206
  const abs = resolve54(dir);
92032
- if (!statSync49(abs).isDirectory()) {
92207
+ if (!statSync50(abs).isDirectory()) {
92033
92208
  fail4(`--from path is not a directory: ${dir}`);
92034
92209
  }
92035
92210
  const files = {};
92036
92211
  const walk2 = (sub) => {
92037
92212
  for (const ent of readdirSync34(sub, { withFileTypes: true })) {
92038
- const full = join89(sub, ent.name);
92213
+ const full = join90(sub, ent.name);
92039
92214
  if (ent.isSymbolicLink()) {
92040
92215
  fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
92041
92216
  }
@@ -92088,8 +92263,8 @@ function behavioralValidate(files) {
92088
92263
  errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
92089
92264
  }
92090
92265
  } else if (PY_SCRIPT_RE.test(path8)) {
92091
- const tmp = mkdtempSync6(join89(tmpdir6(), "skill-personal-py-"));
92092
- const tmpPy = join89(tmp, "check.py");
92266
+ const tmp = mkdtempSync6(join90(tmpdir6(), "skill-personal-py-"));
92267
+ const tmpPy = join90(tmp, "check.py");
92093
92268
  try {
92094
92269
  writeFileSync33(tmpPy, content);
92095
92270
  const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
@@ -92107,15 +92282,15 @@ function behavioralValidate(files) {
92107
92282
  }
92108
92283
  function sweepTrash(agentsRoot, agent) {
92109
92284
  const trash = trashDir(agentsRoot, agent);
92110
- if (!existsSync91(trash))
92285
+ if (!existsSync92(trash))
92111
92286
  return;
92112
92287
  const now = Date.now();
92113
92288
  for (const ent of readdirSync34(trash, { withFileTypes: true })) {
92114
92289
  if (!ent.isDirectory())
92115
92290
  continue;
92116
- const entPath = join89(trash, ent.name);
92291
+ const entPath = join90(trash, ent.name);
92117
92292
  try {
92118
- const st = statSync49(entPath);
92293
+ const st = statSync50(entPath);
92119
92294
  if (now - st.mtimeMs > TRASH_TTL_MS) {
92120
92295
  rmSync19(entPath, { recursive: true, force: true });
92121
92296
  }
@@ -92125,7 +92300,7 @@ function sweepTrash(agentsRoot, agent) {
92125
92300
  function writePersonalSkill(targetDir, files) {
92126
92301
  let targetIsSymlink = false;
92127
92302
  try {
92128
- const st = lstatSync11(targetDir);
92303
+ const st = lstatSync12(targetDir);
92129
92304
  if (st.isSymbolicLink()) {
92130
92305
  targetIsSymlink = true;
92131
92306
  }
@@ -92134,11 +92309,11 @@ function writePersonalSkill(targetDir, files) {
92134
92309
  fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
92135
92310
  }
92136
92311
  mkdirSync51(dirname31(targetDir), { recursive: true, mode: 493 });
92137
- const staging = mkdtempSync6(join89(dirname31(targetDir), `.skill-personal-stage-`));
92312
+ const staging = mkdtempSync6(join90(dirname31(targetDir), `.skill-personal-stage-`));
92138
92313
  let oldRename = null;
92139
92314
  try {
92140
92315
  for (const [path8, content] of Object.entries(files)) {
92141
- const full = join89(staging, path8);
92316
+ const full = join90(staging, path8);
92142
92317
  mkdirSync51(dirname31(full), { recursive: true, mode: 493 });
92143
92318
  const fd = openSync17(full, "wx");
92144
92319
  try {
@@ -92153,7 +92328,7 @@ function writePersonalSkill(targetDir, files) {
92153
92328
  }
92154
92329
  let targetExists = false;
92155
92330
  try {
92156
- lstatSync11(targetDir);
92331
+ lstatSync12(targetDir);
92157
92332
  targetExists = true;
92158
92333
  } catch {}
92159
92334
  if (targetExists) {
@@ -92169,9 +92344,9 @@ function writePersonalSkill(targetDir, files) {
92169
92344
  try {
92170
92345
  rmSync19(staging, { recursive: true, force: true });
92171
92346
  } catch {}
92172
- if (oldRename && existsSync91(oldRename)) {
92347
+ if (oldRename && existsSync92(oldRename)) {
92173
92348
  try {
92174
- if (existsSync91(targetDir)) {
92349
+ if (existsSync92(targetDir)) {
92175
92350
  rmSync19(targetDir, { recursive: true, force: true });
92176
92351
  }
92177
92352
  renameSync22(oldRename, targetDir);
@@ -92188,7 +92363,7 @@ function loadValidateWrite(agentsRoot, agent, name, files, ensureNew) {
92188
92363
  const target = personalSkillDir(agentsRoot, agent, name);
92189
92364
  const exists = (() => {
92190
92365
  try {
92191
- lstatSync11(target);
92366
+ lstatSync12(target);
92192
92367
  return true;
92193
92368
  } catch {
92194
92369
  return false;
@@ -92237,10 +92412,10 @@ function loadFiles(opts) {
92237
92412
  return loadFromStdin2();
92238
92413
  }
92239
92414
  const p = resolve54(opts.from);
92240
- if (!existsSync91(p)) {
92415
+ if (!existsSync92(p)) {
92241
92416
  fail4(`--from path does not exist: ${opts.from}`);
92242
92417
  }
92243
- const st = statSync49(p);
92418
+ const st = statSync50(p);
92244
92419
  if (st.isDirectory()) {
92245
92420
  return loadFromDir2(p);
92246
92421
  }
@@ -92285,10 +92460,10 @@ function editPersonalAction(name, opts) {
92285
92460
  }
92286
92461
  var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
92287
92462
  function defaultSharedRoot() {
92288
- return join89(homedir52(), ".switchroom", "skills");
92463
+ return join90(homedir52(), ".switchroom", "skills");
92289
92464
  }
92290
92465
  function defaultBundledRoot() {
92291
- return join89(homedir52(), ".switchroom", "skills", "_bundled");
92466
+ return join90(homedir52(), ".switchroom", "skills", "_bundled");
92292
92467
  }
92293
92468
  function resolveCloneSource(source, opts) {
92294
92469
  const m = CLONE_SOURCE_RE.exec(source);
@@ -92298,11 +92473,11 @@ function resolveCloneSource(source, opts) {
92298
92473
  const tier = m[1];
92299
92474
  const slug = m[2];
92300
92475
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
92301
- const dir = join89(root, slug);
92302
- if (!existsSync91(dir)) {
92476
+ const dir = join90(root, slug);
92477
+ if (!existsSync92(dir)) {
92303
92478
  fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
92304
92479
  }
92305
- const st = lstatSync11(dir);
92480
+ const st = lstatSync12(dir);
92306
92481
  if (st.isSymbolicLink()) {
92307
92482
  fail4(`clone source ${JSON.stringify(source)} is a symlink at ${dir}; ` + `point clone at the canonical pool path instead`);
92308
92483
  }
@@ -92314,7 +92489,7 @@ function readSourceFiles(dir) {
92314
92489
  const skipped = [];
92315
92490
  const walk2 = (sub) => {
92316
92491
  for (const ent of readdirSync34(sub, { withFileTypes: true })) {
92317
- const full = join89(sub, ent.name);
92492
+ const full = join90(sub, ent.name);
92318
92493
  if (ent.isSymbolicLink()) {
92319
92494
  continue;
92320
92495
  }
@@ -92329,7 +92504,7 @@ function readSourceFiles(dir) {
92329
92504
  continue;
92330
92505
  }
92331
92506
  try {
92332
- const st = lstatSync11(full);
92507
+ const st = lstatSync12(full);
92333
92508
  if (st.size > CLONE_MAX_FILE_BYTES) {
92334
92509
  fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
92335
92510
  }
@@ -92411,7 +92586,7 @@ function removePersonalAction(name, opts) {
92411
92586
  }
92412
92587
  const target = personalSkillDir(agentsRoot, agent, name);
92413
92588
  try {
92414
- const st = lstatSync11(target);
92589
+ const st = lstatSync12(target);
92415
92590
  if (st.isSymbolicLink()) {
92416
92591
  fail4(`refusing to remove symlink at ${target}; investigate manually`);
92417
92592
  }
@@ -92425,7 +92600,7 @@ function removePersonalAction(name, opts) {
92425
92600
  const trashRoot2 = trashDir(agentsRoot, agent);
92426
92601
  mkdirSync51(trashRoot2, { recursive: true, mode: 493 });
92427
92602
  const ts = Date.now();
92428
- const trashTarget = join89(trashRoot2, `${name}-${ts}`);
92603
+ const trashTarget = join90(trashRoot2, `${name}-${ts}`);
92429
92604
  renameSync22(target, trashTarget);
92430
92605
  const now = new Date(ts);
92431
92606
  utimesSync(trashTarget, now, now);
@@ -92444,16 +92619,16 @@ function listPersonalAction(opts) {
92444
92619
  const agent = resolveAgent(opts);
92445
92620
  const agentsRoot = resolveAgentsRoot(opts);
92446
92621
  sweepTrash(agentsRoot, agent);
92447
- const skillsDir = join89(agentsRoot, agent, ".claude", "skills");
92622
+ const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
92448
92623
  const personal = [];
92449
- if (existsSync91(skillsDir)) {
92624
+ if (existsSync92(skillsDir)) {
92450
92625
  for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
92451
92626
  if (!ent.isDirectory())
92452
92627
  continue;
92453
92628
  if (!ent.name.startsWith(PERSONAL_PREFIX))
92454
92629
  continue;
92455
92630
  const skillName = ent.name.slice(PERSONAL_PREFIX.length);
92456
- const skillPath = join89(skillsDir, ent.name);
92631
+ const skillPath = join90(skillsDir, ent.name);
92457
92632
  let fileCount = 0;
92458
92633
  let totalBytes = 0;
92459
92634
  const walk2 = (sub) => {
@@ -92461,10 +92636,10 @@ function listPersonalAction(opts) {
92461
92636
  if (e.isFile()) {
92462
92637
  fileCount += 1;
92463
92638
  try {
92464
- totalBytes += statSync49(join89(sub, e.name)).size;
92639
+ totalBytes += statSync50(join90(sub, e.name)).size;
92465
92640
  } catch {}
92466
92641
  } else if (e.isDirectory()) {
92467
- walk2(join89(sub, e.name));
92642
+ walk2(join90(sub, e.name));
92468
92643
  }
92469
92644
  }
92470
92645
  };
@@ -92503,11 +92678,11 @@ function registerSkillPersonalCommands(program3) {
92503
92678
  // src/cli/self-improve-propose-skill.ts
92504
92679
  import { createConnection as createConnection4 } from "node:net";
92505
92680
  import { homedir as homedir53 } from "node:os";
92506
- import { join as join90 } from "node:path";
92681
+ import { join as join91 } from "node:path";
92507
92682
  import { readFileSync as readFileSync79 } from "node:fs";
92508
92683
  var IPC_CONNECT_TIMEOUT_MS = 5000;
92509
92684
  function gatewaySocketPath() {
92510
- return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join90(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join90(homedir53(), ".claude", "channels", "telegram", "gateway.sock"));
92685
+ return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join91(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join91(homedir53(), ".claude", "channels", "telegram", "gateway.sock"));
92511
92686
  }
92512
92687
  function fail5(msg, code = 1) {
92513
92688
  console.error(msg);
@@ -92579,9 +92754,9 @@ function registerSelfImproveProposeSkillCommand(program3) {
92579
92754
  init_esm();
92580
92755
  init_helpers();
92581
92756
  var import_yaml25 = __toESM(require_dist(), 1);
92582
- import { existsSync as existsSync92, readdirSync as readdirSync35, readFileSync as readFileSync80, statSync as statSync50 } from "node:fs";
92757
+ import { existsSync as existsSync93, readdirSync as readdirSync35, readFileSync as readFileSync80, statSync as statSync51 } from "node:fs";
92583
92758
  import { homedir as homedir54 } from "node:os";
92584
- import { join as join91, resolve as resolve55 } from "node:path";
92759
+ import { join as join92, resolve as resolve55 } from "node:path";
92585
92760
  var PERSONAL_PREFIX2 = "personal-";
92586
92761
  var BUNDLED_SUBDIR = "_bundled";
92587
92762
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
@@ -92595,8 +92770,8 @@ function defaultBundledRoot2() {
92595
92770
  return resolve55(homedir54(), ".switchroom/skills/_bundled");
92596
92771
  }
92597
92772
  function readSkillFrontmatter(skillDir) {
92598
- const mdPath = join91(skillDir, "SKILL.md");
92599
- if (!existsSync92(mdPath))
92773
+ const mdPath = join92(skillDir, "SKILL.md");
92774
+ if (!existsSync93(mdPath))
92600
92775
  return null;
92601
92776
  let content;
92602
92777
  try {
@@ -92628,9 +92803,9 @@ function readSkillFrontmatter(skillDir) {
92628
92803
  return { fm: parsed };
92629
92804
  }
92630
92805
  function statSkillMd(skillDir) {
92631
- const mdPath = join91(skillDir, "SKILL.md");
92806
+ const mdPath = join92(skillDir, "SKILL.md");
92632
92807
  try {
92633
- const st = statSync50(mdPath);
92808
+ const st = statSync51(mdPath);
92634
92809
  return { size: st.size, mtime: st.mtime.toISOString() };
92635
92810
  } catch {
92636
92811
  return null;
@@ -92639,8 +92814,8 @@ function statSkillMd(skillDir) {
92639
92814
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
92640
92815
  if (!AGENT_NAME_RE3.test(agent))
92641
92816
  return [];
92642
- const skillsDir = join91(agentsRoot, agent, ".claude/skills");
92643
- if (!existsSync92(skillsDir))
92817
+ const skillsDir = join92(agentsRoot, agent, ".claude/skills");
92818
+ if (!existsSync93(skillsDir))
92644
92819
  return [];
92645
92820
  const out = [];
92646
92821
  let entries;
@@ -92652,9 +92827,9 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
92652
92827
  for (const ent of entries) {
92653
92828
  if (!ent.startsWith(PERSONAL_PREFIX2))
92654
92829
  continue;
92655
- const dirPath = join91(skillsDir, ent);
92830
+ const dirPath = join92(skillsDir, ent);
92656
92831
  try {
92657
- if (!statSync50(dirPath).isDirectory())
92832
+ if (!statSync51(dirPath).isDirectory())
92658
92833
  continue;
92659
92834
  } catch {
92660
92835
  continue;
@@ -92678,7 +92853,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
92678
92853
  return out;
92679
92854
  }
92680
92855
  function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
92681
- if (!existsSync92(sharedRoot))
92856
+ if (!existsSync93(sharedRoot))
92682
92857
  return [];
92683
92858
  const out = [];
92684
92859
  let entries;
@@ -92692,9 +92867,9 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
92692
92867
  continue;
92693
92868
  if (ent.startsWith("."))
92694
92869
  continue;
92695
- const dirPath = join91(sharedRoot, ent);
92870
+ const dirPath = join92(sharedRoot, ent);
92696
92871
  try {
92697
- if (!statSync50(dirPath).isDirectory())
92872
+ if (!statSync51(dirPath).isDirectory())
92698
92873
  continue;
92699
92874
  } catch {
92700
92875
  continue;
@@ -92716,7 +92891,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
92716
92891
  return out;
92717
92892
  }
92718
92893
  function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
92719
- if (!existsSync92(bundledRoot))
92894
+ if (!existsSync93(bundledRoot))
92720
92895
  return [];
92721
92896
  const out = [];
92722
92897
  let entries;
@@ -92728,9 +92903,9 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
92728
92903
  for (const ent of entries) {
92729
92904
  if (ent.startsWith("."))
92730
92905
  continue;
92731
- const dirPath = join91(bundledRoot, ent);
92906
+ const dirPath = join92(bundledRoot, ent);
92732
92907
  try {
92733
- if (!statSync50(dirPath).isDirectory())
92908
+ if (!statSync51(dirPath).isDirectory())
92734
92909
  continue;
92735
92910
  } catch {
92736
92911
  continue;
@@ -92874,18 +93049,18 @@ init_source();
92874
93049
  init_helpers();
92875
93050
  init_operator_uid();
92876
93051
  import {
92877
- existsSync as existsSync94,
93052
+ existsSync as existsSync95,
92878
93053
  mkdirSync as mkdirSync52,
92879
93054
  readdirSync as readdirSync36,
92880
93055
  readFileSync as readFileSync82,
92881
93056
  writeFileSync as writeFileSync34,
92882
- statSync as statSync51,
92883
- lstatSync as lstatSync12,
93057
+ statSync as statSync52,
93058
+ lstatSync as lstatSync13,
92884
93059
  realpathSync as realpathSync8,
92885
93060
  copyFileSync as copyFileSync13
92886
93061
  } from "node:fs";
92887
93062
  import { homedir as homedir55 } from "node:os";
92888
- import { join as join92 } from "node:path";
93063
+ import { join as join93 } from "node:path";
92889
93064
  import { spawnSync as spawnSync20 } from "node:child_process";
92890
93065
 
92891
93066
  // src/cli/singleton-stale-cleanup.ts
@@ -93234,10 +93409,10 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
93234
93409
  return resolved;
93235
93410
  }
93236
93411
  function resolveHostdSkillsTarget(hostHome) {
93237
- const skillsPath = join92(hostHome, ".switchroom", "skills");
93412
+ const skillsPath = join93(hostHome, ".switchroom", "skills");
93238
93413
  let st;
93239
93414
  try {
93240
- st = lstatSync12(skillsPath);
93415
+ st = lstatSync13(skillsPath);
93241
93416
  } catch {
93242
93417
  return;
93243
93418
  }
@@ -93251,21 +93426,21 @@ function resolveHostdSkillsTarget(hostHome) {
93251
93426
  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.`);
93252
93427
  return;
93253
93428
  }
93254
- if (!existsSync94(target)) {
93429
+ if (!existsSync95(target)) {
93255
93430
  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.`);
93256
93431
  return;
93257
93432
  }
93258
93433
  return target;
93259
93434
  }
93260
93435
  function hostdDir() {
93261
- return join92(homedir55(), ".switchroom", "hostd");
93436
+ return join93(homedir55(), ".switchroom", "hostd");
93262
93437
  }
93263
93438
  function hostdComposePath() {
93264
- return join92(hostdDir(), "docker-compose.yml");
93439
+ return join93(hostdDir(), "docker-compose.yml");
93265
93440
  }
93266
93441
  function backupExistingCompose() {
93267
93442
  const p = hostdComposePath();
93268
- if (!existsSync94(p))
93443
+ if (!existsSync95(p))
93269
93444
  return null;
93270
93445
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
93271
93446
  const bak = `${p}.bak-${ts}`;
@@ -93363,7 +93538,7 @@ function doStatus() {
93363
93538
  const composeYml = hostdComposePath();
93364
93539
  console.log(source_default.bold("switchroom-hostd"));
93365
93540
  console.log("");
93366
- if (!existsSync94(composeYml)) {
93541
+ if (!existsSync95(composeYml)) {
93367
93542
  console.log(source_default.yellow(" compose: not installed"));
93368
93543
  console.log(source_default.dim(" run `switchroom hostd install` to set up."));
93369
93544
  return;
@@ -93384,15 +93559,15 @@ function doStatus() {
93384
93559
  } else {
93385
93560
  console.log(source_default.green(` container: ${ps.stdout.trim()}`));
93386
93561
  }
93387
- if (existsSync94(dir)) {
93562
+ if (existsSync95(dir)) {
93388
93563
  const entries = [];
93389
93564
  try {
93390
93565
  for (const name of readdirSync36(dir)) {
93391
93566
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
93392
93567
  continue;
93393
- const sockPath = join92(dir, name, "sock");
93394
- if (existsSync94(sockPath)) {
93395
- const st = statSync51(sockPath);
93568
+ const sockPath = join93(dir, name, "sock");
93569
+ if (existsSync95(sockPath)) {
93570
+ const st = statSync52(sockPath);
93396
93571
  if ((st.mode & 61440) === 49152) {
93397
93572
  entries.push(`${name} \u2192 ${sockPath}`);
93398
93573
  }
@@ -93410,7 +93585,7 @@ function doStatus() {
93410
93585
  }
93411
93586
  function doUninstall() {
93412
93587
  const composeYml = hostdComposePath();
93413
- if (!existsSync94(composeYml)) {
93588
+ if (!existsSync95(composeYml)) {
93414
93589
  console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
93415
93590
  return;
93416
93591
  }
@@ -93434,7 +93609,7 @@ function registerHostdCommand(program3) {
93434
93609
  hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
93435
93610
  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) => {
93436
93611
  const logPath = opts.path ?? defaultAuditLogPath2();
93437
- if (!existsSync94(logPath)) {
93612
+ if (!existsSync95(logPath)) {
93438
93613
  console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
93439
93614
  The log is created when hostd handles its first privileged-verb request.`));
93440
93615
  return;
@@ -93482,9 +93657,9 @@ The log is created when hostd handles its first privileged-verb request.`));
93482
93657
  init_source();
93483
93658
  init_helpers();
93484
93659
  init_operator_uid();
93485
- import { chownSync as chownSync9, existsSync as existsSync95, mkdirSync as mkdirSync53, writeFileSync as writeFileSync35, copyFileSync as copyFileSync14 } from "node:fs";
93660
+ import { chownSync as chownSync9, existsSync as existsSync96, mkdirSync as mkdirSync53, writeFileSync as writeFileSync35, copyFileSync as copyFileSync14 } from "node:fs";
93486
93661
  import { homedir as homedir56 } from "node:os";
93487
- import { join as join93 } from "node:path";
93662
+ import { join as join94 } from "node:path";
93488
93663
  import { spawnSync as spawnSync21 } from "node:child_process";
93489
93664
  function resolveWebImageTag(explicitTag, release) {
93490
93665
  if (explicitTag)
@@ -93579,14 +93754,14 @@ services:
93579
93754
  `;
93580
93755
  }
93581
93756
  function webdDir() {
93582
- return join93(homedir56(), ".switchroom", "web");
93757
+ return join94(homedir56(), ".switchroom", "web");
93583
93758
  }
93584
93759
  function webdComposePath() {
93585
- return join93(webdDir(), "docker-compose.yml");
93760
+ return join94(webdDir(), "docker-compose.yml");
93586
93761
  }
93587
93762
  function backupExistingCompose2() {
93588
93763
  const p = webdComposePath();
93589
- if (!existsSync95(p))
93764
+ if (!existsSync96(p))
93590
93765
  return null;
93591
93766
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
93592
93767
  const bak = `${p}.bak-${ts}`;
@@ -93685,7 +93860,7 @@ function doStatus2() {
93685
93860
  const composeYml = webdComposePath();
93686
93861
  console.log(source_default.bold("switchroom-web"));
93687
93862
  console.log("");
93688
- if (!existsSync95(composeYml)) {
93863
+ if (!existsSync96(composeYml)) {
93689
93864
  console.log(source_default.yellow(" compose: not installed"));
93690
93865
  console.log(source_default.dim(" run `switchroom webd install` to set up."));
93691
93866
  return;
@@ -93709,7 +93884,7 @@ function doStatus2() {
93709
93884
  }
93710
93885
  function doUninstall2() {
93711
93886
  const composeYml = webdComposePath();
93712
- if (!existsSync95(composeYml)) {
93887
+ if (!existsSync96(composeYml)) {
93713
93888
  console.log(source_default.yellow(" No web-service install detected (no compose file at this path)."));
93714
93889
  return;
93715
93890
  }
@@ -93739,9 +93914,9 @@ function registerWebdCommand(program3) {
93739
93914
  // src/cli/host-repair.ts
93740
93915
  init_source();
93741
93916
  import { homedir as homedir57 } from "node:os";
93742
- import { join as join94 } from "node:path";
93917
+ import { join as join95 } from "node:path";
93743
93918
  var ARTIFACT_ALLOWLIST = {
93744
- dockerComposePluginDir: (home2) => join94(home2, ".docker", "cli-plugins", "docker-compose"),
93919
+ dockerComposePluginDir: (home2) => join95(home2, ".docker", "cli-plugins", "docker-compose"),
93745
93920
  stateSentinel: "/state"
93746
93921
  };
93747
93922
  function isStateBogusAutoDir(probe2) {
@@ -93895,11 +94070,11 @@ function applyMountRepairs(items, deps) {
93895
94070
  function registerHostCommand(program3) {
93896
94071
  const host = program3.command("host").description("Host-level maintenance operations for switchroom");
93897
94072
  host.command("repair-mounts").description("Detect and remove known auto-dir artifacts left by a container-context deploy " + "(2026-06-23 outage class). Default: dry-run. Pass --yes to apply.").option("--yes", "Actually perform the removals (default is dry-run)").action(async (opts) => {
93898
- const { rmdirSync: rmdirSync2, rmSync: rmSync20, lstatSync: lstatSync13, readdirSync: readdirSync37 } = await import("node:fs");
94073
+ const { rmdirSync: rmdirSync2, rmSync: rmSync20, lstatSync: lstatSync14, readdirSync: readdirSync37 } = await import("node:fs");
93899
94074
  const probe2 = {
93900
94075
  lstat(path8) {
93901
94076
  try {
93902
- return lstatSync13(path8);
94077
+ return lstatSync14(path8);
93903
94078
  } catch {
93904
94079
  return null;
93905
94080
  }