switchroom 0.18.3 → 0.18.7

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 (156) hide show
  1. package/dist/agent-scheduler/index.js +3 -1
  2. package/dist/auth-broker/index.js +3 -1
  3. package/dist/cli/autoaccept-poll.js +140 -33
  4. package/dist/cli/notion-write-pretool.mjs +3 -1
  5. package/dist/cli/switchroom.js +386 -128
  6. package/dist/host-control/main.js +4 -2
  7. package/dist/vault/approvals/kernel-server.js +3 -1
  8. package/dist/vault/broker/server.js +38 -8
  9. package/package.json +3 -3
  10. package/profiles/_base/cron-session.sh.hbs +55 -16
  11. package/profiles/_base/start.sh.hbs +35 -16
  12. package/profiles/default/CLAUDE.md.hbs +1 -1
  13. package/skills/switchroom-release/SKILL.md +78 -0
  14. package/telegram-plugin/auth-snapshot-format.ts +15 -1
  15. package/telegram-plugin/dist/bridge/bridge.js +22 -0
  16. package/telegram-plugin/dist/gateway/gateway.js +2852 -1032
  17. package/telegram-plugin/dist/server.js +24 -0
  18. package/telegram-plugin/gateway/always-allow-persist-queue.ts +438 -0
  19. package/telegram-plugin/gateway/approval-timeout-inbound-builders.ts +150 -0
  20. package/telegram-plugin/gateway/clean-shutdown-marker.ts +68 -20
  21. package/telegram-plugin/gateway/gateway.ts +1331 -151
  22. package/telegram-plugin/gateway/inbound-spool.ts +2 -1
  23. package/telegram-plugin/gateway/inject-handler.test.ts +19 -0
  24. package/telegram-plugin/gateway/inject-handler.ts +17 -0
  25. package/telegram-plugin/gateway/ipc-protocol.ts +44 -2
  26. package/telegram-plugin/gateway/ipc-server.ts +40 -0
  27. package/telegram-plugin/gateway/model-command.ts +212 -51
  28. package/telegram-plugin/gateway/pending-card-expiry.ts +98 -0
  29. package/telegram-plugin/gateway/pending-card-store.ts +173 -0
  30. package/telegram-plugin/gateway/pending-inbound-buffer.ts +12 -2
  31. package/telegram-plugin/gateway/resolve-person.ts +304 -0
  32. package/telegram-plugin/gateway/resume-inbound-builder.ts +240 -2
  33. package/telegram-plugin/gateway/session-model-source.ts +73 -0
  34. package/telegram-plugin/gateway/unhandled-rejection-policy.ts +21 -1
  35. package/telegram-plugin/gateway/worker-feed-dispatch.ts +24 -1
  36. package/telegram-plugin/hooks/silent-end-scan.mjs +164 -40
  37. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +30 -7
  38. package/telegram-plugin/model-label.ts +69 -0
  39. package/telegram-plugin/operator-events.ts +45 -0
  40. package/telegram-plugin/pending-work-progress.ts +42 -7
  41. package/telegram-plugin/permission-diff.ts +128 -0
  42. package/telegram-plugin/quota-bar-format.ts +360 -0
  43. package/telegram-plugin/registry/subagents-schema.ts +80 -1
  44. package/telegram-plugin/registry/subagents.test.ts +90 -0
  45. package/telegram-plugin/session-tail.ts +28 -0
  46. package/telegram-plugin/silent-end.ts +49 -4
  47. package/telegram-plugin/subagent-watcher.ts +249 -46
  48. package/telegram-plugin/tests/always-allow-persist-queue.test.ts +529 -0
  49. package/telegram-plugin/tests/approval-timeout-inbound-builders.test.ts +94 -0
  50. package/telegram-plugin/tests/auth-snapshot-format.test.ts +21 -0
  51. package/telegram-plugin/tests/button-tap-turn-gated.test.ts +263 -0
  52. package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
  53. package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +85 -27
  54. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +4 -2
  55. package/telegram-plugin/tests/ipc-server-query-pending-permission.test.ts +157 -0
  56. package/telegram-plugin/tests/mental-model-propose-callback-gate.test.ts +8 -5
  57. package/telegram-plugin/tests/model-command.test.ts +202 -42
  58. package/telegram-plugin/tests/model-label.test.ts +64 -0
  59. package/telegram-plugin/tests/operator-events.test.ts +17 -0
  60. package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +202 -0
  61. package/telegram-plugin/tests/pending-card-expiry.test.ts +190 -0
  62. package/telegram-plugin/tests/pending-card-store.test.ts +173 -0
  63. package/telegram-plugin/tests/pending-work-progress.test.ts +116 -3
  64. package/telegram-plugin/tests/permission-diff.test.ts +111 -0
  65. package/telegram-plugin/tests/quota-bar-format.test.ts +444 -0
  66. package/telegram-plugin/tests/resolve-person.test.ts +290 -0
  67. package/telegram-plugin/tests/resume-inbound-builder.test.ts +286 -0
  68. package/telegram-plugin/tests/session-model-source.test.ts +67 -0
  69. package/telegram-plugin/tests/session-tail.test.ts +64 -0
  70. package/telegram-plugin/tests/silent-end-interrupt-stop-integration.test.ts +53 -0
  71. package/telegram-plugin/tests/silent-end-interrupt-stop-scan.test.ts +138 -0
  72. package/telegram-plugin/tests/silent-end.test.ts +46 -1
  73. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +39 -0
  74. package/telegram-plugin/tests/subagent-watcher-boot-promotion-replay.test.ts +107 -4
  75. package/telegram-plugin/tests/subagent-watcher-handback-gaps.test.ts +42 -4
  76. package/telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts +47 -0
  77. package/telegram-plugin/tests/subagent-watcher-terminated-ids-cap.test.ts +150 -0
  78. package/telegram-plugin/tests/subagent-watcher.test.ts +115 -0
  79. package/telegram-plugin/tests/tool-activity-summary.test.ts +37 -0
  80. package/telegram-plugin/tests/typing-wrap.test.ts +23 -0
  81. package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +19 -0
  82. package/telegram-plugin/tests/worker-activity-feed.test.ts +108 -0
  83. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +126 -0
  84. package/telegram-plugin/tool-activity-summary.ts +22 -2
  85. package/telegram-plugin/typing-wrap.ts +72 -25
  86. package/telegram-plugin/worker-activity-feed.ts +229 -15
  87. package/profiles/default/CLAUDE.md +0 -116
  88. package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  89. package/vendor/hindsight-memory/scripts/__pycache__/directive_verify.cpython-313.pyc +0 -0
  90. package/vendor/hindsight-memory/scripts/__pycache__/drain_pending.cpython-313.pyc +0 -0
  91. package/vendor/hindsight-memory/scripts/__pycache__/recall.cpython-313.pyc +0 -0
  92. package/vendor/hindsight-memory/scripts/__pycache__/retain.cpython-313.pyc +0 -0
  93. package/vendor/hindsight-memory/scripts/__pycache__/session_end.cpython-313.pyc +0 -0
  94. package/vendor/hindsight-memory/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  95. package/vendor/hindsight-memory/scripts/lib/__pycache__/bank.cpython-313.pyc +0 -0
  96. package/vendor/hindsight-memory/scripts/lib/__pycache__/client.cpython-313.pyc +0 -0
  97. package/vendor/hindsight-memory/scripts/lib/__pycache__/config.cpython-313.pyc +0 -0
  98. package/vendor/hindsight-memory/scripts/lib/__pycache__/content.cpython-313.pyc +0 -0
  99. package/vendor/hindsight-memory/scripts/lib/__pycache__/daemon.cpython-313.pyc +0 -0
  100. package/vendor/hindsight-memory/scripts/lib/__pycache__/directives.cpython-313.pyc +0 -0
  101. package/vendor/hindsight-memory/scripts/lib/__pycache__/gateway_ipc.cpython-313.pyc +0 -0
  102. package/vendor/hindsight-memory/scripts/lib/__pycache__/llm.cpython-313.pyc +0 -0
  103. package/vendor/hindsight-memory/scripts/lib/__pycache__/pending.cpython-313.pyc +0 -0
  104. package/vendor/hindsight-memory/scripts/lib/__pycache__/state.cpython-313.pyc +0 -0
  105. package/vendor/hindsight-memory/scripts/lib/__pycache__/switchroom_envelope.cpython-313.pyc +0 -0
  106. package/vendor/hindsight-memory/scripts/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  107. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313-pytest-9.1.1.pyc +0 -0
  108. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313.pyc +0 -0
  109. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313-pytest-9.1.1.pyc +0 -0
  110. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313.pyc +0 -0
  111. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313-pytest-9.1.1.pyc +0 -0
  112. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313.pyc +0 -0
  113. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313-pytest-9.1.1.pyc +0 -0
  114. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313.pyc +0 -0
  115. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313-pytest-9.1.1.pyc +0 -0
  116. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313.pyc +0 -0
  117. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313-pytest-9.1.1.pyc +0 -0
  118. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313.pyc +0 -0
  119. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313-pytest-9.1.1.pyc +0 -0
  120. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313.pyc +0 -0
  121. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313-pytest-9.1.1.pyc +0 -0
  122. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313.pyc +0 -0
  123. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313-pytest-9.1.1.pyc +0 -0
  124. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313.pyc +0 -0
  125. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313-pytest-9.1.1.pyc +0 -0
  126. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313.pyc +0 -0
  127. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313-pytest-9.1.1.pyc +0 -0
  128. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313.pyc +0 -0
  129. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313-pytest-9.1.1.pyc +0 -0
  130. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313.pyc +0 -0
  131. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_switchroom_envelope.cpython-313-pytest-9.1.1.pyc +0 -0
  132. package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  133. package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
  134. package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313-pytest-9.1.1.pyc +0 -0
  135. package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313.pyc +0 -0
  136. package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313-pytest-9.1.1.pyc +0 -0
  137. package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313.pyc +0 -0
  138. package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.0.3.pyc +0 -0
  139. package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.1.1.pyc +0 -0
  140. package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313.pyc +0 -0
  141. package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313-pytest-9.1.1.pyc +0 -0
  142. package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313.pyc +0 -0
  143. package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313-pytest-9.1.1.pyc +0 -0
  144. package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313.pyc +0 -0
  145. package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313-pytest-9.1.1.pyc +0 -0
  146. package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313.pyc +0 -0
  147. package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313-pytest-9.1.1.pyc +0 -0
  148. package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313.pyc +0 -0
  149. package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313-pytest-9.1.1.pyc +0 -0
  150. package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313.pyc +0 -0
  151. package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313-pytest-9.1.1.pyc +0 -0
  152. package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313.pyc +0 -0
  153. package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313-pytest-9.1.1.pyc +0 -0
  154. package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313.pyc +0 -0
  155. package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313-pytest-9.1.1.pyc +0 -0
  156. package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313.pyc +0 -0
@@ -13741,6 +13741,7 @@ var init_schema = __esm(() => {
13741
13741
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
13742
13742
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
13743
13743
  resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
13744
+ boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart \u2014 a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart \u2014 but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
13744
13745
  session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
13745
13746
  session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
13746
13747
  }).optional();
@@ -14205,7 +14206,8 @@ var init_schema = __esm(() => {
14205
14206
  UserSchema = exports_external.object({
14206
14207
  name: exports_external.string().optional().describe("Display name for the user."),
14207
14208
  telegram_ids: exports_external.array(exports_external.string()).min(1).describe("Telegram username(s) and/or numeric user id(s) identifying this user " + "(a leading @ is optional). Matched against the message sender for " + "per-speaker memory routing."),
14208
- profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`).")
14209
+ profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`)."),
14210
+ person_id: exports_external.string().optional().describe("Free-text display name projected into the inbound `<channel>` " + 'tag\'s `user` attribute (e.g. "Lisa") so an agent can greet by ' + "name instead of only seeing the raw Telegram id/username. NOT a " + "stable identity system \u2014 just a label. Resolution is boot-time-only " + "(no hot-reload; a config change needs an agent restart) and " + "chat-scoped (a resolved name is only shown in a chat/group the " + "person is actually a member of, per that chat's access.json " + "membership \u2014 never broadcast into every chat the agent operates " + "in). Keep this broadly safe to show, the same discipline as " + "picking a `profile_bank` name: there is no automated enforcement " + "that a `person_id` stays safe if a group's membership changes " + "later \u2014 see docs/configuration.md.")
14209
14211
  });
14210
14212
  SwitchroomConfigSchema = exports_external.object({
14211
14213
  switchroom: exports_external.object({
@@ -15177,6 +15179,16 @@ function resolveUsers(config, agentName) {
15177
15179
  ]);
15178
15180
  return { senderBanks, additionalBanks };
15179
15181
  }
15182
+ function resolvePersonEntries(config) {
15183
+ const users = config.users ?? {};
15184
+ const entries = [];
15185
+ for (const [key, u] of Object.entries(users)) {
15186
+ if (!u.person_id)
15187
+ continue;
15188
+ entries.push({ key, person_id: u.person_id, telegram_ids: u.telegram_ids });
15189
+ }
15190
+ return entries;
15191
+ }
15180
15192
  var init_users = __esm(() => {
15181
15193
  init_merge();
15182
15194
  });
@@ -26933,6 +26945,7 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
26933
26945
  handoffEnabled: agentConfig.session_continuity?.enabled !== false,
26934
26946
  resumeMode: agentConfig.session_continuity?.resume_mode ?? "handoff",
26935
26947
  resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000,
26948
+ bootResumeMode: agentConfig.session_continuity?.boot_resume ?? "in-flight",
26936
26949
  resumeModeHasContinuePath: (() => {
26937
26950
  const mode = agentConfig.session_continuity?.resume_mode ?? "handoff";
26938
26951
  return mode === "auto" || mode === "continue";
@@ -27360,6 +27373,7 @@ This file is auto-maintained. Do not edit manually.
27360
27373
  }, created, skipped, 384);
27361
27374
  writeIfMissing(join10(agentDir, "telegram", "access.json"), () => buildAccessJson2(agentConfig, telegramConfig, topicId, userId), created, skipped, 384);
27362
27375
  reconcileConfiguredGroup(join10(agentDir, "telegram", "access.json"), agentConfig, telegramConfig);
27376
+ writeFileSyncIfChanged(join10(agentDir, "telegram", "people.json"), buildPeopleJson(switchroomConfig), 384);
27363
27377
  if (agentConfig.subagents) {
27364
27378
  const agentsDir2 = join10(agentDir, ".claude", "agents");
27365
27379
  mkdirSync10(agentsDir2, { recursive: true });
@@ -28034,7 +28048,8 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
28034
28048
  sessionMaxTurns: agentConfig.session?.max_turns,
28035
28049
  handoffEnabled: agentConfig.session_continuity?.enabled !== false,
28036
28050
  resumeMode: agentConfig.session_continuity?.resume_mode ?? "handoff",
28037
- resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000
28051
+ resumeMaxBytes: agentConfig.session_continuity?.resume_max_bytes ?? 2000000,
28052
+ bootResumeMode: agentConfig.session_continuity?.boot_resume ?? "in-flight"
28038
28053
  };
28039
28054
  const beforeStartSh = existsSync15(startShPath) ? readFileSync13(startShPath, "utf-8") : "";
28040
28055
  const afterStartSh = renderTemplate(join10(basePath, "start.sh.hbs"), startShContext);
@@ -28582,6 +28597,11 @@ function reconcileConfiguredGroup(accessPath, agentConfig, telegramConfig) {
28582
28597
  renameSync4(tmp, accessPath);
28583
28598
  console.log(source_default.green(` registered supergroup ${forumChatId} in access.json ` + `(responds to all topics; requireMention=false)`));
28584
28599
  }
28600
+ function buildPeopleJson(switchroomConfig) {
28601
+ const entries = switchroomConfig ? resolvePersonEntries(switchroomConfig) : [];
28602
+ return JSON.stringify({ entries }, null, 2) + `
28603
+ `;
28604
+ }
28585
28605
  function buildAccessJson2(agentConfig, telegramConfig, resolvedTopicId, userId) {
28586
28606
  const allowFrom = userId ? [String(userId)] : [];
28587
28607
  if (allowFrom.length === 0) {
@@ -30030,7 +30050,25 @@ import { chownSync as chownSync2 } from "node:fs";
30030
30050
  import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
30031
30051
  import { homedir as homedir6 } from "node:os";
30032
30052
  import { basename as basename4, dirname as dirname6, join as join13 } from "node:path";
30033
- async function resolveLiteLLMConfirmedAgents(config) {
30053
+ function agentHadLiteLLMRouting(composeContent, agentName) {
30054
+ const lines = composeContent.split(`
30055
+ `);
30056
+ const header = ` agent-${agentName}:`;
30057
+ let i = lines.indexOf(header);
30058
+ if (i < 0)
30059
+ return false;
30060
+ for (i = i + 1;i < lines.length; i++) {
30061
+ const line = lines[i];
30062
+ if (/^ {2}\S/.test(line))
30063
+ break;
30064
+ if (/^\S/.test(line))
30065
+ break;
30066
+ if (/^\s+SWITCHROOM_LITELLM:\s*"1"\s*$/.test(line))
30067
+ return true;
30068
+ }
30069
+ return false;
30070
+ }
30071
+ async function resolveLiteLLMConfirmedAgents(config, previousCompose, deps) {
30034
30072
  const confirmed = new Set;
30035
30073
  const topEnabled = config.litellm?.enabled ?? false;
30036
30074
  const optedIn = [];
@@ -30045,16 +30083,52 @@ async function resolveLiteLLMConfirmedAgents(config) {
30045
30083
  }
30046
30084
  if (optedIn.length === 0)
30047
30085
  return confirmed;
30048
- try {
30049
- const { getViaBrokerStructured: getViaBrokerStructured2 } = await Promise.resolve().then(() => (init_client(), exports_client));
30050
- for (const name of optedIn) {
30051
- try {
30052
- const r = await getViaBrokerStructured2(`litellm/${name}/api-key`);
30053
- if (r.kind === "ok")
30054
- confirmed.add(name);
30055
- } catch {}
30086
+ let getKey = deps?.getKey ?? null;
30087
+ if (!getKey) {
30088
+ try {
30089
+ ({ getViaBrokerStructured: getKey } = await Promise.resolve().then(() => (init_client(), exports_client)));
30090
+ } catch {
30091
+ getKey = null;
30056
30092
  }
30057
- } catch {}
30093
+ }
30094
+ const unreachableAgents = [];
30095
+ for (const name of optedIn) {
30096
+ if (!getKey) {
30097
+ unreachableAgents.push(name);
30098
+ continue;
30099
+ }
30100
+ try {
30101
+ const r = await getKey(`litellm/${name}/api-key`);
30102
+ if (r.kind === "ok") {
30103
+ confirmed.add(name);
30104
+ } else if (r.kind === "unreachable") {
30105
+ unreachableAgents.push(name);
30106
+ } else if (r.kind === "denied" && r.code !== "DENIED") {
30107
+ unreachableAgents.push(name);
30108
+ }
30109
+ } catch {
30110
+ unreachableAgents.push(name);
30111
+ }
30112
+ }
30113
+ if (unreachableAgents.length > 0) {
30114
+ const preserved = [];
30115
+ const couldNotPreserve = [];
30116
+ for (const name of unreachableAgents) {
30117
+ if (previousCompose && agentHadLiteLLMRouting(previousCompose, name)) {
30118
+ confirmed.add(name);
30119
+ preserved.push(name);
30120
+ } else {
30121
+ couldNotPreserve.push(name);
30122
+ }
30123
+ }
30124
+ console.warn(`switchroom: vault-broker unreachable or unable to answer (locked/internal) while resolving LiteLLM key status for ${unreachableAgents.length} opted-in agent(s): ${unreachableAgents.join(", ")}. Refusing to silently strip proxy routing (that would boot them on untracked direct OAuth). Re-run \`switchroom apply\` once the broker is back to reconcile.`);
30125
+ if (preserved.length > 0) {
30126
+ console.warn(`switchroom: PRESERVED existing proxy routing from the on-disk compose for: ${preserved.join(", ")}.`);
30127
+ }
30128
+ if (couldNotPreserve.length > 0) {
30129
+ console.warn(`switchroom: no prior routing verdict on disk for: ${couldNotPreserve.join(", ")} ` + `\u2014 leaving them unrouted (they were not previously routed).`);
30130
+ }
30131
+ }
30058
30132
  return confirmed;
30059
30133
  }
30060
30134
  function resolveHostSwitchroomConfigPath(rawPath) {
@@ -30092,7 +30166,13 @@ async function computeComposeContent(opts) {
30092
30166
  const imageTag = resolveImageTag(release);
30093
30167
  const operatorUid = resolveOperatorUid();
30094
30168
  const resolvedConfigPath = opts.switchroomConfigPath !== undefined ? resolveHostSwitchroomConfigPath(opts.switchroomConfigPath) : undefined;
30095
- const litellmConfirmedAgents = await resolveLiteLLMConfirmedAgents(opts.config);
30169
+ let previous = null;
30170
+ try {
30171
+ previous = await readFile(opts.composePath, "utf8");
30172
+ } catch {
30173
+ previous = null;
30174
+ }
30175
+ const litellmConfirmedAgents = await resolveLiteLLMConfirmedAgents(opts.config, previous);
30096
30176
  const content = generateCompose({
30097
30177
  config: opts.config,
30098
30178
  imageTag,
@@ -30104,12 +30184,6 @@ async function computeComposeContent(opts) {
30104
30184
  switchroomConfigPath: resolvedConfigPath,
30105
30185
  operatorUid
30106
30186
  });
30107
- let previous = null;
30108
- try {
30109
- previous = await readFile(opts.composePath, "utf8");
30110
- } catch {
30111
- previous = null;
30112
- }
30113
30187
  const previousImageTag = previous ? AGENT_IMAGE_TAG_RE.exec(previous)?.[1] ?? null : null;
30114
30188
  return { content, imageTag, previous, previousImageTag };
30115
30189
  }
@@ -35505,7 +35579,7 @@ function formatForCli(entries, opts = {}) {
35505
35579
  var init_audit_reader = () => {};
35506
35580
 
35507
35581
  // node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
35508
- import { dirname as dirname12, posix, sep as sep2 } from "path";
35582
+ import { dirname as dirname13, posix, sep as sep2 } from "path";
35509
35583
  function createModulerModifier() {
35510
35584
  const getModuleFromFileName = createGetModuleFromFilename();
35511
35585
  return async (frames) => {
@@ -35514,7 +35588,7 @@ function createModulerModifier() {
35514
35588
  return frames;
35515
35589
  };
35516
35590
  }
35517
- function createGetModuleFromFilename(basePath = process.argv[1] ? dirname12(process.argv[1]) : process.cwd(), isWindows = sep2 === "\\") {
35591
+ function createGetModuleFromFilename(basePath = process.argv[1] ? dirname13(process.argv[1]) : process.cwd(), isWindows = sep2 === "\\") {
35518
35592
  const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
35519
35593
  return (filename) => {
35520
35594
  if (!filename)
@@ -40130,7 +40204,7 @@ import {
40130
40204
  readFileSync as readFileSync43,
40131
40205
  writeFileSync as writeFileSync23
40132
40206
  } from "node:fs";
40133
- import { dirname as dirname13 } from "node:path";
40207
+ import { dirname as dirname14 } from "node:path";
40134
40208
  import { randomUUID as randomUUID3 } from "node:crypto";
40135
40209
  function telemetryDisabled() {
40136
40210
  const v = process.env.SWITCHROOM_TELEMETRY_DISABLED;
@@ -40152,7 +40226,7 @@ function getDistinctId() {
40152
40226
  const id = randomUUID3();
40153
40227
  cachedDistinctId = id;
40154
40228
  try {
40155
- mkdirSync26(dirname13(path4), { recursive: true });
40229
+ mkdirSync26(dirname14(path4), { recursive: true });
40156
40230
  writeFileSync23(path4, id, "utf-8");
40157
40231
  } catch {}
40158
40232
  return id;
@@ -40399,7 +40473,7 @@ import {
40399
40473
  readFileSync as readFileSync52,
40400
40474
  readdirSync as readdirSync19
40401
40475
  } from "node:fs";
40402
- import { dirname as dirname16, join as join50 } from "node:path";
40476
+ import { dirname as dirname17, join as join50 } from "node:path";
40403
40477
  import { execSync as execSync2 } from "node:child_process";
40404
40478
  function locateManifestPath() {
40405
40479
  let dir = import.meta.dirname;
@@ -40407,7 +40481,7 @@ function locateManifestPath() {
40407
40481
  const candidate = join50(dir, "dependencies.json");
40408
40482
  if (existsSync57(candidate))
40409
40483
  return candidate;
40410
- dir = dirname16(dir);
40484
+ dir = dirname17(dir);
40411
40485
  }
40412
40486
  return null;
40413
40487
  }
@@ -43578,7 +43652,7 @@ import {
43578
43652
  readdirSync as readdirSync21,
43579
43653
  statSync as statSync26
43580
43654
  } from "node:fs";
43581
- import { dirname as dirname17, join as join62, resolve as resolve37 } from "node:path";
43655
+ import { dirname as dirname18, join as join62, resolve as resolve37 } from "node:path";
43582
43656
  import { createPublicKey, createPrivateKey } from "node:crypto";
43583
43657
  function findInNvm(bin) {
43584
43658
  const nvmRoot = join62(process.env.HOME ?? "", ".nvm", "versions", "node");
@@ -45060,7 +45134,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
45060
45134
  detail: "skipped (MFF_API_URL not set)"
45061
45135
  };
45062
45136
  }
45063
- const credDir = dirname17(envPath);
45137
+ const credDir = dirname18(envPath);
45064
45138
  const authScript = join62(credDir, "claude-auth.py");
45065
45139
  if (!existsSync62(authScript)) {
45066
45140
  return {
@@ -48866,6 +48940,7 @@ var init_install_detect = () => {};
48866
48940
  // src/litellm/provision.ts
48867
48941
  var exports_provision = {};
48868
48942
  __export(exports_provision, {
48943
+ validateKey: () => validateKey,
48869
48944
  ensureTeam: () => ensureTeam,
48870
48945
  ensureKey: () => ensureKey,
48871
48946
  LiteLLMProvisionError: () => LiteLLMProvisionError
@@ -48887,6 +48962,12 @@ function looksLikeAlreadyExists(status, body) {
48887
48962
  return true;
48888
48963
  return false;
48889
48964
  }
48965
+ function looksLikeUnknownKey(status, body) {
48966
+ if (status !== 400 && status !== 401 && status !== 403 && status !== 404)
48967
+ return false;
48968
+ const t = body.toLowerCase();
48969
+ return t.includes("not found") || t.includes("does not exist") || t.includes("no such key") || t.includes("no key found");
48970
+ }
48890
48971
  async function ensureTeam(baseUrl, masterKey, teamName, fetchFn = fetch) {
48891
48972
  const url = `${normalizeBase(baseUrl)}/team/new`;
48892
48973
  let resp;
@@ -48906,7 +48987,7 @@ async function ensureTeam(baseUrl, masterKey, teamName, fetchFn = fetch) {
48906
48987
  return;
48907
48988
  throw new LiteLLMProvisionError(`LiteLLM /team/new returned ${resp.status}`, resp.status, body);
48908
48989
  }
48909
- async function ensureKey(opts, fetchFn = fetch) {
48990
+ async function generateKeyOnce(opts, fetchFn) {
48910
48991
  const url = `${normalizeBase(opts.baseUrl)}/key/generate`;
48911
48992
  const payload = {
48912
48993
  key_alias: opts.alias
@@ -48926,23 +49007,129 @@ async function ensureKey(opts, fetchFn = fetch) {
48926
49007
  body: JSON.stringify(payload)
48927
49008
  });
48928
49009
  } catch (err) {
48929
- throw new LiteLLMProvisionError(`LiteLLM /key/generate request failed: ${err.message}`);
49010
+ return {
49011
+ kind: "error",
49012
+ error: new LiteLLMProvisionError(`LiteLLM /key/generate request failed: ${err.message}`)
49013
+ };
48930
49014
  }
48931
49015
  if (!resp.ok) {
48932
49016
  const body = await safeText(resp);
48933
- throw new LiteLLMProvisionError(`LiteLLM /key/generate returned ${resp.status}`, resp.status, body);
49017
+ if (looksLikeAlreadyExists(resp.status, body)) {
49018
+ return { kind: "duplicate-alias", status: resp.status, body };
49019
+ }
49020
+ return {
49021
+ kind: "error",
49022
+ error: new LiteLLMProvisionError(`LiteLLM /key/generate returned ${resp.status}`, resp.status, body)
49023
+ };
48934
49024
  }
48935
49025
  let json;
48936
49026
  try {
48937
49027
  json = await resp.json();
48938
49028
  } catch (err) {
48939
- throw new LiteLLMProvisionError(`LiteLLM /key/generate returned non-JSON body: ${err.message}`, resp.status);
49029
+ return {
49030
+ kind: "error",
49031
+ error: new LiteLLMProvisionError(`LiteLLM /key/generate returned non-JSON body: ${err.message}`, resp.status)
49032
+ };
48940
49033
  }
48941
49034
  const key = json?.key;
48942
49035
  if (typeof key !== "string" || key.length === 0) {
48943
- throw new LiteLLMProvisionError(`LiteLLM /key/generate response missing a "key" field`, resp.status, JSON.stringify(json));
49036
+ return {
49037
+ kind: "error",
49038
+ error: new LiteLLMProvisionError(`LiteLLM /key/generate response missing a "key" field`, resp.status, JSON.stringify(json))
49039
+ };
48944
49040
  }
48945
- return { key };
49041
+ return { kind: "ok", key };
49042
+ }
49043
+ async function resolveTokensForAlias(baseUrl, masterKey, alias, fetchFn) {
49044
+ const url = `${normalizeBase(baseUrl)}/key/list?key_alias=${encodeURIComponent(alias)}` + `&return_full_object=true&include_team_keys=true`;
49045
+ let resp;
49046
+ try {
49047
+ resp = await fetchFn(url, { method: "GET", headers: authHeaders3(masterKey) });
49048
+ } catch {
49049
+ return [];
49050
+ }
49051
+ if (!resp.ok)
49052
+ return [];
49053
+ let json;
49054
+ try {
49055
+ json = await resp.json();
49056
+ } catch {
49057
+ return [];
49058
+ }
49059
+ const keys = json?.keys;
49060
+ if (!Array.isArray(keys))
49061
+ return [];
49062
+ const tokens = [];
49063
+ for (const k of keys) {
49064
+ if (typeof k === "string" && k.length > 0)
49065
+ tokens.push(k);
49066
+ else if (k && typeof k === "object") {
49067
+ const tok = k.token;
49068
+ const raw = k.key;
49069
+ if (typeof tok === "string" && tok.length > 0)
49070
+ tokens.push(tok);
49071
+ else if (typeof raw === "string" && raw.length > 0)
49072
+ tokens.push(raw);
49073
+ }
49074
+ }
49075
+ return tokens;
49076
+ }
49077
+ async function deleteOrphanedAlias(opts, fetchFn, log) {
49078
+ const tokens = await resolveTokensForAlias(opts.baseUrl, opts.masterKey, opts.alias, fetchFn);
49079
+ const body = { key_aliases: [opts.alias] };
49080
+ if (tokens.length > 0)
49081
+ body.keys = tokens;
49082
+ const manual = `Manual remediation: delete the key with alias '${opts.alias}' in the LiteLLM ` + `admin UI, or run ` + `\`curl -X POST "${normalizeBase(opts.baseUrl)}/key/delete" -H "Authorization: Bearer <master-key>" ` + `-H "content-type: application/json" -d '{"key_aliases":["${opts.alias}"]}'\`, ` + `then re-run \`switchroom apply\`.`;
49083
+ const url = `${normalizeBase(opts.baseUrl)}/key/delete`;
49084
+ let resp;
49085
+ try {
49086
+ resp = await fetchFn(url, {
49087
+ method: "POST",
49088
+ headers: authHeaders3(opts.masterKey),
49089
+ body: JSON.stringify(body)
49090
+ });
49091
+ } catch (err) {
49092
+ throw new LiteLLMProvisionError(`LiteLLM /key/delete request failed while recovering orphaned alias ` + `'${opts.alias}': ${err.message}. ${manual}`);
49093
+ }
49094
+ if (!resp.ok) {
49095
+ const errBody = await safeText(resp);
49096
+ throw new LiteLLMProvisionError(`LiteLLM /key/delete returned ${resp.status} while recovering orphaned ` + `alias '${opts.alias}'. ${manual}`, resp.status, errBody);
49097
+ }
49098
+ log(`litellm: deleted orphaned key(s) for alias '${opts.alias}'` + (tokens.length > 0 ? ` (${tokens.length} token(s) resolved)` : "") + ` \u2014 regenerating`);
49099
+ }
49100
+ async function ensureKey(opts, fetchFn = fetch) {
49101
+ const log = opts.log ?? (() => {});
49102
+ const first = await generateKeyOnce(opts, fetchFn);
49103
+ if (first.kind === "ok")
49104
+ return { key: first.key };
49105
+ if (first.kind === "error")
49106
+ throw first.error;
49107
+ log(`litellm: key_alias '${opts.alias}' already exists in LiteLLM but the vault ` + `has no recoverable key (orphaned by a prior failed provision) \u2014 ` + `self-healing: deleting the orphan and regenerating`);
49108
+ await deleteOrphanedAlias(opts, fetchFn, log);
49109
+ const second = await generateKeyOnce(opts, fetchFn);
49110
+ if (second.kind === "ok") {
49111
+ log(`litellm: regenerated key for alias '${opts.alias}' after orphan recovery`);
49112
+ return { key: second.key };
49113
+ }
49114
+ if (second.kind === "duplicate-alias") {
49115
+ throw new LiteLLMProvisionError(`LiteLLM /key/generate still reports alias '${opts.alias}' already exists ` + `after deleting the orphan \u2014 the delete did not take effect. Manual ` + `remediation: remove the key with alias '${opts.alias}' via the LiteLLM ` + `admin UI (or POST /key/delete {"key_aliases":["${opts.alias}"]}) then ` + `re-run \`switchroom apply\`.`, second.status, second.body);
49116
+ }
49117
+ throw second.error;
49118
+ }
49119
+ async function validateKey(opts, fetchFn = fetch) {
49120
+ const url = `${normalizeBase(opts.baseUrl)}/key/info?key=${encodeURIComponent(opts.key)}`;
49121
+ let resp;
49122
+ try {
49123
+ resp = await fetchFn(url, { method: "GET", headers: authHeaders3(opts.masterKey) });
49124
+ } catch (err) {
49125
+ return { kind: "unreachable", detail: err.message };
49126
+ }
49127
+ if (resp.ok)
49128
+ return { kind: "valid" };
49129
+ const body = await safeText(resp);
49130
+ if (looksLikeUnknownKey(resp.status, body))
49131
+ return { kind: "unknown" };
49132
+ return { kind: "unreachable", detail: `HTTP ${resp.status}: ${body.slice(0, 200)}` };
48946
49133
  }
48947
49134
  async function safeText(resp) {
48948
49135
  try {
@@ -48983,7 +49170,7 @@ __export(exports_voice_sidecar_token, {
48983
49170
  });
48984
49171
  import { randomBytes as randomBytes14 } from "node:crypto";
48985
49172
  import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as existsSync81, mkdirSync as mkdirSync45, readFileSync as readFileSync68, rmSync as rmSync17, writeFileSync as writeFileSync37 } from "node:fs";
48986
- import { dirname as dirname26 } from "node:path";
49173
+ import { dirname as dirname27 } from "node:path";
48987
49174
  async function defaultResolveOrSeedToken(home2, writeErr) {
48988
49175
  const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
48989
49176
  Promise.resolve().then(() => (init_client(), exports_client)),
@@ -49037,7 +49224,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
49037
49224
  if (!token)
49038
49225
  return;
49039
49226
  try {
49040
- mkdirSync45(dirname26(envPath), { recursive: true });
49227
+ mkdirSync45(dirname27(envPath), { recursive: true });
49041
49228
  let body = "";
49042
49229
  try {
49043
49230
  if (existsSync81(envPath))
@@ -49099,11 +49286,11 @@ __export(exports_apply, {
49099
49286
  DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
49100
49287
  COMPOSE_PROJECT: () => COMPOSE_PROJECT2
49101
49288
  });
49102
- import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync82, mkdirSync as mkdirSync46, readFileSync as readFileSync69, readdirSync as readdirSync28, renameSync as renameSync18, writeFileSync as writeFileSync38 } from "node:fs";
49289
+ import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync82, mkdirSync as mkdirSync46, readFileSync as readFileSync69, readdirSync as readdirSync28, renameSync as renameSync18, writeFileSync as writeFileSync38 } from "node:fs";
49103
49290
  import { mkdir as mkdir2 } from "node:fs/promises";
49104
49291
  import { spawnSync as childSpawnSync } from "node:child_process";
49105
49292
  import readline from "node:readline";
49106
- import { dirname as dirname27, join as join80, resolve as resolve50 } from "node:path";
49293
+ import { dirname as dirname28, join as join80, resolve as resolve50 } from "node:path";
49107
49294
  import { homedir as homedir47 } from "node:os";
49108
49295
  import { execFileSync as execFileSync25 } from "node:child_process";
49109
49296
  function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
@@ -49137,7 +49324,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49137
49324
  const needsHindsight = config.litellm?.enabled === true && config.memory?.backend === "hindsight";
49138
49325
  if (optedIn.length === 0 && !needsHindsight)
49139
49326
  return;
49140
- const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
49327
+ const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { ensureTeam: ensureTeam2, ensureKey: ensureKey2, validateKey: validateKey2 }, { addAgentSecret: addAgentSecret2 }] = await Promise.all([
49141
49328
  Promise.resolve().then(() => (init_client(), exports_client)),
49142
49329
  Promise.resolve().then(() => (init_provision(), exports_provision)),
49143
49330
  Promise.resolve().then(() => (init_telegram_yaml(), exports_telegram_yaml))
@@ -49159,7 +49346,21 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49159
49346
  unprovisionedNew.push(name);
49160
49347
  }
49161
49348
  }
49162
- const hindsightPending = needsHindsight;
49349
+ let hindsightPending = false;
49350
+ if (needsHindsight) {
49351
+ const h = await getViaBrokerStructured3("litellm/hindsight/api-key");
49352
+ if (h.kind === "ok") {
49353
+ hindsightPending = false;
49354
+ writeOut(source_default.gray(` ~ litellm/hindsight: key already provisioned (skipped) \u2014 unaffected by missing passphrase.
49355
+ `));
49356
+ } else if (h.kind === "unreachable") {
49357
+ hindsightPending = false;
49358
+ ctx.writeErr(source_default.yellow(` ! litellm/hindsight: vault-broker unreachable \u2014 could not verify the ` + `service key; it keeps its previous state.
49359
+ `));
49360
+ } else {
49361
+ hindsightPending = true;
49362
+ }
49363
+ }
49163
49364
  if (alreadyProvisioned.length > 0) {
49164
49365
  writeOut(source_default.gray(` ~ litellm: ${alreadyProvisioned.length} agent(s) already provisioned (${alreadyProvisioned.join(", ")}) \u2014 unaffected by missing passphrase.
49165
49366
  `));
@@ -49210,43 +49411,62 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49210
49411
  continue;
49211
49412
  }
49212
49413
  const vaultKey = `litellm/${name}/api-key`;
49414
+ const alias = `agent:${name}`;
49213
49415
  const existing = await getViaBrokerStructured2(vaultKey);
49214
- if (existing.kind === "ok") {
49215
- writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned (skipped)
49216
- `));
49217
- if (configText !== null) {
49218
- const after = addAgentSecret2(configText, name, vaultKey);
49219
- if (after !== configText) {
49220
- configText = after;
49221
- pendingConfigEdits = true;
49222
- }
49223
- }
49224
- continue;
49225
- }
49226
49416
  if (existing.kind === "unreachable") {
49227
49417
  ctx.writeErr(source_default.yellow(` ! litellm/${name}: vault-broker unreachable (${existing.msg ?? "no detail"}) \u2014 ` + `skipping key provisioning; agent keeps its previous routing env.
49228
49418
  `));
49229
49419
  continue;
49230
49420
  }
49231
49421
  let masterKey = adminKeyRef;
49422
+ let masterKeyErr = null;
49232
49423
  if (isVaultReference(adminKeyRef)) {
49233
49424
  const refKey = parseVaultReference(adminKeyRef);
49234
49425
  const resolved = await getViaBrokerStructured2(refKey);
49235
49426
  if (resolved.kind !== "ok") {
49236
- failures.push({
49237
- agent: name,
49238
- message: `litellm: admin_key vault ref '${adminKeyRef}' did not resolve (${resolved.kind}${"msg" in resolved && resolved.msg ? `: ${resolved.msg}` : ""}).`
49239
- });
49240
- continue;
49427
+ masterKey = null;
49428
+ masterKeyErr = `litellm: admin_key vault ref '${adminKeyRef}' did not resolve (${resolved.kind}${"msg" in resolved && resolved.msg ? `: ${resolved.msg}` : ""}).`;
49429
+ } else if (resolved.entry.kind !== "string") {
49430
+ masterKey = null;
49431
+ masterKeyErr = `litellm: admin_key vault ref '${adminKeyRef}' is not a string secret.`;
49432
+ } else {
49433
+ masterKey = resolved.entry.value;
49241
49434
  }
49242
- if (resolved.entry.kind !== "string") {
49243
- failures.push({
49244
- agent: name,
49245
- message: `litellm: admin_key vault ref '${adminKeyRef}' is not a string secret.`
49246
- });
49435
+ }
49436
+ if (existing.kind === "ok") {
49437
+ const storedKey = existing.entry.kind === "string" ? existing.entry.value : null;
49438
+ let driftReprovision = false;
49439
+ if (storedKey && masterKey) {
49440
+ const validation = await validateKey2({ baseUrl, masterKey, key: storedKey });
49441
+ if (validation.kind === "unknown") {
49442
+ driftReprovision = true;
49443
+ ctx.writeErr(source_default.yellow(` ! litellm/${name}: stored virtual key not recognized by the proxy ` + `(DB drift) \u2014 re-provisioning a fresh key.
49444
+ `));
49445
+ } else if (validation.kind === "unreachable") {
49446
+ ctx.writeErr(source_default.yellow(` ! litellm/${name}: proxy unreachable during key validation (${validation.detail}) \u2014 keeping the stored key unverified.
49447
+ `));
49448
+ } else {
49449
+ writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned + validated (skipped)
49450
+ `));
49451
+ }
49452
+ } else {
49453
+ writeOut(source_default.gray(` ~ litellm/${name}: key already provisioned (skipped)
49454
+ `));
49455
+ }
49456
+ if (!driftReprovision) {
49457
+ if (configText !== null) {
49458
+ const after = addAgentSecret2(configText, name, vaultKey);
49459
+ if (after !== configText) {
49460
+ configText = after;
49461
+ pendingConfigEdits = true;
49462
+ }
49463
+ }
49247
49464
  continue;
49248
49465
  }
49249
- masterKey = resolved.entry.value;
49466
+ }
49467
+ if (!masterKey) {
49468
+ failures.push({ agent: name, message: masterKeyErr ?? `litellm: admin_key unresolved` });
49469
+ continue;
49250
49470
  }
49251
49471
  await ensureTeam2(baseUrl, masterKey, team);
49252
49472
  const metadata = {
@@ -49264,9 +49484,11 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49264
49484
  const { key } = await ensureKey2({
49265
49485
  baseUrl,
49266
49486
  masterKey,
49267
- alias: `agent:${name}`,
49487
+ alias,
49268
49488
  team,
49269
- metadata
49489
+ metadata,
49490
+ log: (m) => ctx.writeErr(source_default.gray(` ~ litellm/${name}: ${m}
49491
+ `))
49270
49492
  });
49271
49493
  const put = await putViaBroker2(vaultKey, { kind: "string", value: key }, { passphrase });
49272
49494
  if (put.kind !== "ok") {
@@ -49328,7 +49550,9 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49328
49550
  masterKey,
49329
49551
  alias: "service:hindsight",
49330
49552
  team,
49331
- metadata: { service: "hindsight", env: "fleet", ...oauthAccount ? { oauth_account: oauthAccount } : {} }
49553
+ metadata: { service: "hindsight", env: "fleet", ...oauthAccount ? { oauth_account: oauthAccount } : {} },
49554
+ log: (m) => ctx.writeErr(source_default.gray(` ~ litellm/hindsight: ${m}
49555
+ `))
49332
49556
  });
49333
49557
  const put = await putViaBroker2(hindsightVaultKey, { kind: "string", value: key }, { passphrase });
49334
49558
  if (put.kind === "ok") {
@@ -49348,7 +49572,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49348
49572
  function resolveVaultBindMountDir(homeDir, ctx) {
49349
49573
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
49350
49574
  if (isCustomPath && ctx.customVaultPath) {
49351
- return dirname27(ctx.customVaultPath);
49575
+ return dirname28(ctx.customVaultPath);
49352
49576
  }
49353
49577
  return join80(homeDir, ".switchroom", "vault");
49354
49578
  }
@@ -50003,7 +50227,7 @@ function copyExampleConfig2(name) {
50003
50227
  if (!existsSync82(exampleFile)) {
50004
50228
  throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
50005
50229
  }
50006
- copyFileSync11(exampleFile, dest);
50230
+ copyFileSync12(exampleFile, dest);
50007
50231
  console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
50008
50232
  }
50009
50233
  function findUnwritableAgentDirs(config, opts) {
@@ -64550,7 +64774,7 @@ import {
64550
64774
  mkdirSync as mkdirSync53,
64551
64775
  writeFileSync as writeFileSync44
64552
64776
  } from "node:fs";
64553
- import { resolve as resolve55, dirname as dirname30 } from "node:path";
64777
+ import { resolve as resolve55, dirname as dirname31 } from "node:path";
64554
64778
  import { homedir as homedir56 } from "node:os";
64555
64779
  function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir56()) {
64556
64780
  return resolve55(home2, ".switchroom");
@@ -64641,11 +64865,11 @@ function readLedgerIfPresent(base) {
64641
64865
  }
64642
64866
  }
64643
64867
  function ledgerPathForBase(base) {
64644
- return fleetHealthLedgerPath(dirname30(base));
64868
+ return fleetHealthLedgerPath(dirname31(base));
64645
64869
  }
64646
64870
  function writeLedger(base, ledger) {
64647
64871
  const path8 = ledgerPathForBase(base);
64648
- mkdirSync53(dirname30(path8), { recursive: true });
64872
+ mkdirSync53(dirname31(path8), { recursive: true });
64649
64873
  writeFileSync44(path8, JSON.stringify(ledger, null, 2) + `
64650
64874
  `, "utf-8");
64651
64875
  return path8;
@@ -64665,8 +64889,8 @@ import { existsSync, readFileSync } from "node:fs";
64665
64889
  import { dirname, join } from "node:path";
64666
64890
 
64667
64891
  // src/build-info.ts
64668
- var VERSION = "0.18.3";
64669
- var COMMIT_SHA = "dbd8b112";
64892
+ var VERSION = "0.18.7";
64893
+ var COMMIT_SHA = "21478348";
64670
64894
 
64671
64895
  // src/cli/resolve-version.ts
64672
64896
  function readPackageVersion() {
@@ -71499,7 +71723,7 @@ init_acl();
71499
71723
  init_protocol();
71500
71724
  import * as net3 from "node:net";
71501
71725
  import { mkdirSync as mkdirSync24, chmodSync as chmodSync7, chownSync as chownSync4, existsSync as existsSync41, readFileSync as readFileSync34, readdirSync as readdirSync17, statSync as statSync21, unlinkSync as unlinkSync10, writeFileSync as writeFileSync18, renameSync as renameSync13 } from "node:fs";
71502
- import { dirname as dirname11, resolve as resolve28, basename as basename7 } from "node:path";
71726
+ import { dirname as dirname12, resolve as resolve28, basename as basename7 } from "node:path";
71503
71727
  import * as os4 from "node:os";
71504
71728
  import * as path3 from "node:path";
71505
71729
 
@@ -71592,10 +71816,38 @@ function rotateAuditLog(logPath, maxFiles) {
71592
71816
  return;
71593
71817
  }
71594
71818
  }
71819
+ const snapshotPath = `${logPath}.1`;
71595
71820
  try {
71596
- fs2.renameSync(logPath, `${logPath}.1`);
71821
+ fs2.copyFileSync(logPath, snapshotPath);
71597
71822
  } catch (err) {
71598
- process.stderr.write(`[vault-audit] ERROR: could not rotate active audit log ${logPath}: ${err.message}
71823
+ process.stderr.write(`[vault-audit] ERROR: could not snapshot active audit log ${logPath} \u2192 ${snapshotPath}: ${err.message}
71824
+ `);
71825
+ return;
71826
+ }
71827
+ try {
71828
+ const fd = fs2.openSync(snapshotPath, "r");
71829
+ try {
71830
+ fs2.fsyncSync(fd);
71831
+ } finally {
71832
+ fs2.closeSync(fd);
71833
+ }
71834
+ } catch (err) {
71835
+ process.stderr.write(`[vault-audit] ERROR: could not fsync audit snapshot ${snapshotPath}; leaving active log intact to avoid data loss: ${err.message}
71836
+ `);
71837
+ return;
71838
+ }
71839
+ try {
71840
+ const dirFd = fs2.openSync(path.dirname(snapshotPath), "r");
71841
+ try {
71842
+ fs2.fsyncSync(dirFd);
71843
+ } finally {
71844
+ fs2.closeSync(dirFd);
71845
+ }
71846
+ } catch {}
71847
+ try {
71848
+ fs2.truncateSync(logPath, 0);
71849
+ } catch (err) {
71850
+ process.stderr.write(`[vault-audit] ERROR: could not truncate active audit log ${logPath}: ${err.message}
71599
71851
  `);
71600
71852
  }
71601
71853
  }
@@ -74004,7 +74256,7 @@ class VaultBroker {
74004
74256
  this.passphrase = this.testOpts._testPassphrase;
74005
74257
  }
74006
74258
  process.umask(63);
74007
- const parentDir = dirname11(this.socketPath);
74259
+ const parentDir = dirname12(this.socketPath);
74008
74260
  mkdirSync24(parentDir, { recursive: true, mode: 448 });
74009
74261
  try {
74010
74262
  chmodSync7(parentDir, 448);
@@ -75485,15 +75737,15 @@ class VaultBroker {
75485
75737
  }
75486
75738
  }
75487
75739
  function detectVaultLayoutDrift(vaultPath) {
75488
- const dir = dirname11(vaultPath);
75740
+ const dir = dirname12(vaultPath);
75489
75741
  if (basename7(dir) !== "vault")
75490
75742
  return;
75491
75743
  if (basename7(vaultPath) !== "vault.enc")
75492
75744
  return;
75493
- const switchroomDir = dirname11(dir);
75745
+ const switchroomDir = dirname12(dir);
75494
75746
  if (basename7(switchroomDir) !== ".switchroom")
75495
75747
  return;
75496
- const home2 = dirname11(switchroomDir);
75748
+ const home2 = dirname12(switchroomDir);
75497
75749
  const result = inspectVaultLayout(home2);
75498
75750
  if (result.kind === "divergent") {
75499
75751
  throw new VaultError(`Vault layout divergence detected at boot: ${result.details.oldPath} and ${result.details.newPath} are both regular files with different content. An older switchroom CLI may have written to the legacy path after migration ran. Run \`switchroom apply\` from the host to surface the recovery recipe (state E refusal with literal \`mv\` commands). See docs/operators/state-e-recovery.md.`);
@@ -76525,7 +76777,7 @@ import {
76525
76777
  appendFileSync as appendFileSync3,
76526
76778
  closeSync as closeSync10,
76527
76779
  existsSync as existsSync45,
76528
- fsyncSync as fsyncSync6,
76780
+ fsyncSync as fsyncSync7,
76529
76781
  mkdirSync as mkdirSync25,
76530
76782
  openSync as openSync10,
76531
76783
  readdirSync as readdirSync18,
@@ -76672,7 +76924,7 @@ function backupVault(opts) {
76672
76924
  const fd = openSync10(tmpPath, "wx", 384);
76673
76925
  try {
76674
76926
  writeSync7(fd, src);
76675
- fsyncSync6(fd);
76927
+ fsyncSync7(fd);
76676
76928
  } finally {
76677
76929
  closeSync10(fd);
76678
76930
  }
@@ -76703,7 +76955,7 @@ function backupVault(opts) {
76703
76955
  try {
76704
76956
  const dirFd = openSync10(opts.destDir, "r");
76705
76957
  try {
76706
- fsyncSync6(dirFd);
76958
+ fsyncSync7(dirFd);
76707
76959
  } finally {
76708
76960
  closeSync10(dirFd);
76709
76961
  }
@@ -76719,7 +76971,7 @@ function backupVault(opts) {
76719
76971
  try {
76720
76972
  const dirFd = openSync10(opts.destDir, "r");
76721
76973
  try {
76722
- fsyncSync6(dirFd);
76974
+ fsyncSync7(dirFd);
76723
76975
  } finally {
76724
76976
  closeSync10(dirFd);
76725
76977
  }
@@ -78889,7 +79141,7 @@ import {
78889
79141
  writeSync as writeSync8,
78890
79142
  constants as fsConstants3
78891
79143
  } from "node:fs";
78892
- import { resolve as resolve32, extname, join as join49, relative, dirname as dirname14 } from "node:path";
79144
+ import { resolve as resolve32, extname, join as join49, relative, dirname as dirname15 } from "node:path";
78893
79145
  import { homedir as homedir27 } from "node:os";
78894
79146
  import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
78895
79147
 
@@ -79509,7 +79761,8 @@ var SUBAGENTS_SCHEMA_SQL = `
79509
79761
  status TEXT NOT NULL,
79510
79762
  result_summary TEXT,
79511
79763
  jsonl_agent_id TEXT,
79512
- parent_agent_id TEXT
79764
+ parent_agent_id TEXT,
79765
+ model TEXT
79513
79766
  );
79514
79767
  CREATE INDEX IF NOT EXISTS subagents_turn ON subagents(parent_turn_key);
79515
79768
  CREATE INDEX IF NOT EXISTS subagents_status ON subagents(status);
@@ -79525,6 +79778,10 @@ function applySubagentsSchema(db) {
79525
79778
  if (!hasParentAgentId) {
79526
79779
  db.exec("ALTER TABLE subagents ADD COLUMN parent_agent_id TEXT");
79527
79780
  }
79781
+ const hasModel = cols.some((c) => c.name === "model");
79782
+ if (!hasModel) {
79783
+ db.exec("ALTER TABLE subagents ADD COLUMN model TEXT");
79784
+ }
79528
79785
  db.exec("CREATE INDEX IF NOT EXISTS subagents_jsonl_id ON subagents(jsonl_agent_id)");
79529
79786
  }
79530
79787
  function mapSubagentRow(row) {
@@ -79541,7 +79798,8 @@ function mapSubagentRow(row) {
79541
79798
  status: row.status,
79542
79799
  result_summary: row.result_summary,
79543
79800
  jsonl_agent_id: row.jsonl_agent_id,
79544
- parent_agent_id: row.parent_agent_id ?? null
79801
+ parent_agent_id: row.parent_agent_id ?? null,
79802
+ model: row.model ?? null
79545
79803
  };
79546
79804
  }
79547
79805
  function listSubagents(db, opts = {}) {
@@ -82165,7 +82423,7 @@ function resolveWebToken() {
82165
82423
  return existing;
82166
82424
  }
82167
82425
  const token = randomBytes11(32).toString("hex");
82168
- mkdirSync30(dirname14(tokenPath), { recursive: true, mode: 448 });
82426
+ mkdirSync30(dirname15(tokenPath), { recursive: true, mode: 448 });
82169
82427
  try {
82170
82428
  const fd = openSync11(tokenPath, fsConstants3.O_WRONLY | fsConstants3.O_CREAT | fsConstants3.O_EXCL, 384);
82171
82429
  try {
@@ -82923,8 +83181,8 @@ Starting Switchroom dashboard...
82923
83181
  init_source();
82924
83182
  init_loader();
82925
83183
  init_scaffold();
82926
- import { existsSync as existsSync56, copyFileSync as copyFileSync8, readFileSync as readFileSync51, writeFileSync as writeFileSync26, mkdirSync as mkdirSync31 } from "node:fs";
82927
- import { resolve as resolve33, dirname as dirname15 } from "node:path";
83184
+ import { existsSync as existsSync56, copyFileSync as copyFileSync9, readFileSync as readFileSync51, writeFileSync as writeFileSync26, mkdirSync as mkdirSync31 } from "node:fs";
83185
+ import { resolve as resolve33, dirname as dirname16 } from "node:path";
82928
83186
  init_state();
82929
83187
  init_vault();
82930
83188
  init_manager();
@@ -83229,8 +83487,8 @@ async function copyExampleConfig(nonInteractive) {
83229
83487
  if (!existsSync56(srcFile)) {
83230
83488
  throw new ConfigError(`Example config not found: ${choice}.yaml`);
83231
83489
  }
83232
- mkdirSync31(dirname15(destFile), { recursive: true });
83233
- copyFileSync8(srcFile, destFile);
83490
+ mkdirSync31(dirname16(destFile), { recursive: true });
83491
+ copyFileSync9(srcFile, destFile);
83234
83492
  console.log(source_default.green(` Copied ${choice}.yaml -> ${destFile}`));
83235
83493
  console.log(source_default.yellow(` Edit ${destFile} to customize, then re-run switchroom setup.`));
83236
83494
  await writeDetectedTimezone(destFile, nonInteractive);
@@ -84012,7 +84270,7 @@ init_lifecycle();
84012
84270
  init_compose_env();
84013
84271
  import { cpSync as cpSync2, existsSync as existsSync63, mkdirSync as mkdirSync33, readFileSync as readFileSync56, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync28, chownSync as chownSync5 } from "node:fs";
84014
84272
  import { spawnSync as spawnSync12 } from "node:child_process";
84015
- import { join as join63, dirname as dirname18, resolve as resolve38 } from "node:path";
84273
+ import { join as join63, dirname as dirname19, resolve as resolve38 } from "node:path";
84016
84274
  import { homedir as homedir38 } from "node:os";
84017
84275
 
84018
84276
  // src/cli/release-yaml.ts
@@ -84121,7 +84379,7 @@ function defaultPersistPin(configPath) {
84121
84379
  }
84122
84380
  var DEFAULT_COMPOSE_PATH = join63(homedir38(), ".switchroom", "compose", "docker-compose.yml");
84123
84381
  function runningFromSwitchroomCheckout(scriptPath) {
84124
- let dir = dirname18(scriptPath);
84382
+ let dir = dirname19(scriptPath);
84125
84383
  for (let i = 0;i < 12; i++) {
84126
84384
  if (existsSync63(join63(dir, ".git"))) {
84127
84385
  try {
@@ -84130,7 +84388,7 @@ function runningFromSwitchroomCheckout(scriptPath) {
84130
84388
  return true;
84131
84389
  } catch {}
84132
84390
  }
84133
- const parent = dirname18(dir);
84391
+ const parent = dirname19(dir);
84134
84392
  if (parent === dir)
84135
84393
  break;
84136
84394
  dir = parent;
@@ -84346,7 +84604,7 @@ function planUpdate(opts) {
84346
84604
  if (existsSync63(dest)) {
84347
84605
  rmSync12(dest, { recursive: true, force: true });
84348
84606
  }
84349
- mkdirSync33(dirname18(dest), { recursive: true });
84607
+ mkdirSync33(dirname19(dest), { recursive: true });
84350
84608
  cpSync2(source, dest, { recursive: true, dereference: false });
84351
84609
  } catch (err) {
84352
84610
  throw new Error(`sync-bundled-skills failed: ${err.message}`);
@@ -84459,7 +84717,7 @@ function defaultStatusProbe(composePath) {
84459
84717
  try {
84460
84718
  cliBuiltAt = new Date(statSync28(scriptPath).mtimeMs).toISOString();
84461
84719
  } catch {}
84462
- let dir = dirname18(scriptPath);
84720
+ let dir = dirname19(scriptPath);
84463
84721
  for (let i = 0;i < 8; i++) {
84464
84722
  const pkgPath = join63(dir, "package.json");
84465
84723
  if (existsSync63(pkgPath)) {
@@ -84472,7 +84730,7 @@ function defaultStatusProbe(composePath) {
84472
84730
  }
84473
84731
  break;
84474
84732
  }
84475
- const parent = dirname18(dir);
84733
+ const parent = dirname19(dir);
84476
84734
  if (parent === dir)
84477
84735
  break;
84478
84736
  dir = parent;
@@ -85103,7 +85361,7 @@ init_helpers();
85103
85361
  init_lifecycle();
85104
85362
  import { execSync as execSync4 } from "node:child_process";
85105
85363
  import { existsSync as existsSync64, readFileSync as readFileSync58 } from "node:fs";
85106
- import { dirname as dirname19, join as join64 } from "node:path";
85364
+ import { dirname as dirname20, join as join64 } from "node:path";
85107
85365
  function getClaudeCodeVersion() {
85108
85366
  try {
85109
85367
  const out = execSync4("claude --version 2>/dev/null", {
@@ -85162,7 +85420,7 @@ function locateSwitchroomInstallDir() {
85162
85420
  }
85163
85421
  } catch {}
85164
85422
  }
85165
- dir = dirname19(dir);
85423
+ dir = dirname20(dir);
85166
85424
  }
85167
85425
  return null;
85168
85426
  }
@@ -86363,7 +86621,7 @@ import {
86363
86621
  rmSync as rmSync13,
86364
86622
  writeFileSync as writeFileSync28
86365
86623
  } from "node:fs";
86366
- import { dirname as dirname20, join as join67 } from "node:path";
86624
+ import { dirname as dirname21, join as join67 } from "node:path";
86367
86625
  import { homedir as homedir40 } from "node:os";
86368
86626
  import { execFileSync as execFileSync19 } from "node:child_process";
86369
86627
 
@@ -86410,7 +86668,7 @@ function ensurePythonEnv(opts) {
86410
86668
  if (existsSync67(venvDir)) {
86411
86669
  rmSync13(venvDir, { recursive: true, force: true });
86412
86670
  }
86413
- mkdirSync35(dirname20(venvDir), { recursive: true });
86671
+ mkdirSync35(dirname21(venvDir), { recursive: true });
86414
86672
  try {
86415
86673
  execFileSync19(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
86416
86674
  } catch (err) {
@@ -86444,14 +86702,14 @@ function ensurePythonEnv(opts) {
86444
86702
  // src/deps/node.ts
86445
86703
  import { createHash as createHash13 } from "node:crypto";
86446
86704
  import {
86447
- copyFileSync as copyFileSync9,
86705
+ copyFileSync as copyFileSync10,
86448
86706
  existsSync as existsSync68,
86449
86707
  mkdirSync as mkdirSync36,
86450
86708
  readFileSync as readFileSync61,
86451
86709
  rmSync as rmSync14,
86452
86710
  writeFileSync as writeFileSync29
86453
86711
  } from "node:fs";
86454
- import { dirname as dirname21, join as join68 } from "node:path";
86712
+ import { dirname as dirname22, join as join68 } from "node:path";
86455
86713
  import { homedir as homedir41 } from "node:os";
86456
86714
  import { execFileSync as execFileSync20 } from "node:child_process";
86457
86715
 
@@ -86478,7 +86736,7 @@ function defaultNodeCacheRoot() {
86478
86736
  return join68(homedir41(), ".switchroom", "deps", "node");
86479
86737
  }
86480
86738
  function hashDepInputs(packageJsonPath) {
86481
- const sourceDir = dirname21(packageJsonPath);
86739
+ const sourceDir = dirname22(packageJsonPath);
86482
86740
  const hasher = createHash13("sha256");
86483
86741
  hasher.update(`package.json
86484
86742
  `);
@@ -86503,7 +86761,7 @@ function ensureNodeEnv(opts) {
86503
86761
  if (!existsSync68(packageJsonPath)) {
86504
86762
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
86505
86763
  }
86506
- const sourceDir = dirname21(packageJsonPath);
86764
+ const sourceDir = dirname22(packageJsonPath);
86507
86765
  const envDir = join68(cacheRoot, skillName);
86508
86766
  const stampPath = join68(envDir, ".package.sha256");
86509
86767
  const nodeModulesDir = join68(envDir, "node_modules");
@@ -86525,12 +86783,12 @@ function ensureNodeEnv(opts) {
86525
86783
  rmSync14(envDir, { recursive: true, force: true });
86526
86784
  }
86527
86785
  mkdirSync36(envDir, { recursive: true });
86528
- copyFileSync9(packageJsonPath, join68(envDir, "package.json"));
86786
+ copyFileSync10(packageJsonPath, join68(envDir, "package.json"));
86529
86787
  let copiedLockfile = false;
86530
86788
  for (const lockName of LOCKFILES_FOR[installer]) {
86531
86789
  const lockPath = join68(sourceDir, lockName);
86532
86790
  if (existsSync68(lockPath)) {
86533
- copyFileSync9(lockPath, join68(envDir, lockName));
86791
+ copyFileSync10(lockPath, join68(envDir, lockName));
86534
86792
  copiedLockfile = true;
86535
86793
  }
86536
86794
  }
@@ -87529,7 +87787,7 @@ function safeParseInt(value, fallback) {
87529
87787
  init_helpers();
87530
87788
  init_loader();
87531
87789
  init_merge();
87532
- import { copyFileSync as copyFileSync10, existsSync as existsSync71, readFileSync as readFileSync62, writeFileSync as writeFileSync30 } from "node:fs";
87790
+ import { copyFileSync as copyFileSync11, existsSync as existsSync71, readFileSync as readFileSync62, writeFileSync as writeFileSync30 } from "node:fs";
87533
87791
  import { join as join70, resolve as resolve44 } from "node:path";
87534
87792
  init_scaffold();
87535
87793
  init_profiles();
@@ -87606,7 +87864,7 @@ function registerSoulCommand(program3) {
87606
87864
  if (existsSync71(backupPath)) {
87607
87865
  backupPath = `${t.soulPath}.bak.${Date.now()}`;
87608
87866
  }
87609
- copyFileSync10(t.soulPath, backupPath);
87867
+ copyFileSync11(t.soulPath, backupPath);
87610
87868
  }
87611
87869
  writeFileSync30(t.soulPath, content, "utf-8");
87612
87870
  if (backupPath) {
@@ -89092,7 +89350,7 @@ function registerDriveMcpLauncherCommand(program3) {
89092
89350
  init_scaffold_integration();
89093
89351
  import { spawn as spawn5 } from "node:child_process";
89094
89352
  import { writeFileSync as writeFileSync33, mkdirSync as mkdirSync41 } from "node:fs";
89095
- import { dirname as dirname22, join as join76 } from "node:path";
89353
+ import { dirname as dirname23, join as join76 } from "node:path";
89096
89354
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
89097
89355
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
89098
89356
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
@@ -89117,7 +89375,7 @@ function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS)
89117
89375
  function writeRefreshHeartbeat(agentName, data) {
89118
89376
  const path7 = heartbeatPath(agentName);
89119
89377
  try {
89120
- mkdirSync41(dirname22(path7), { recursive: true });
89378
+ mkdirSync41(dirname23(path7), { recursive: true });
89121
89379
  writeFileSync33(path7, JSON.stringify(data, null, 2), { mode: 420 });
89122
89380
  } catch {}
89123
89381
  }
@@ -89310,7 +89568,7 @@ function registerM365McpLauncherCommand(program3) {
89310
89568
  init_scaffold_integration();
89311
89569
  import { spawn as spawn6 } from "node:child_process";
89312
89570
  import { existsSync as existsSync78, mkdirSync as mkdirSync42, writeFileSync as writeFileSync34 } from "node:fs";
89313
- import { dirname as dirname23 } from "node:path";
89571
+ import { dirname as dirname24 } from "node:path";
89314
89572
  var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
89315
89573
  var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
89316
89574
  var DEFAULT_VAULT_KEY = "notion/integration-token";
@@ -89320,7 +89578,7 @@ function buildNotionMcpArgs(opts) {
89320
89578
  }
89321
89579
  function defaultWriteHeartbeat(path7, contents) {
89322
89580
  try {
89323
- const dir = dirname23(path7);
89581
+ const dir = dirname24(path7);
89324
89582
  if (!existsSync78(dir))
89325
89583
  mkdirSync42(dir, { recursive: true });
89326
89584
  writeFileSync34(path7, contents);
@@ -90475,7 +90733,7 @@ init_paths();
90475
90733
  import {
90476
90734
  closeSync as closeSync14,
90477
90735
  existsSync as existsSync84,
90478
- fsyncSync as fsyncSync7,
90736
+ fsyncSync as fsyncSync8,
90479
90737
  mkdirSync as mkdirSync47,
90480
90738
  openSync as openSync14,
90481
90739
  readdirSync as readdirSync30,
@@ -90558,7 +90816,7 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
90558
90816
  const fd = openSync14(stagingPath, "w", 384);
90559
90817
  try {
90560
90818
  writeSync10(fd, yamlText);
90561
- fsyncSync7(fd);
90819
+ fsyncSync8(fd);
90562
90820
  } finally {
90563
90821
  closeSync14(fd);
90564
90822
  }
@@ -90575,7 +90833,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90575
90833
  const fd = openSync14(stagingPath, "w", 384);
90576
90834
  try {
90577
90835
  writeSync10(fd, yamlText);
90578
- fsyncSync7(fd);
90836
+ fsyncSync8(fd);
90579
90837
  } finally {
90580
90838
  closeSync14(fd);
90581
90839
  }
@@ -90782,7 +91040,7 @@ function reconcileAgentCronOnly(agent) {
90782
91040
  import {
90783
91041
  closeSync as closeSync15,
90784
91042
  existsSync as existsSync85,
90785
- fsyncSync as fsyncSync8,
91043
+ fsyncSync as fsyncSync9,
90786
91044
  mkdirSync as mkdirSync48,
90787
91045
  openSync as openSync15,
90788
91046
  readdirSync as readdirSync31,
@@ -90826,7 +91084,7 @@ function stagePendingScheduleEntry(opts) {
90826
91084
  const fd = openSync15(yamlTmp, "w", 384);
90827
91085
  try {
90828
91086
  writeSync11(fd, opts.yamlText);
90829
- fsyncSync8(fd);
91087
+ fsyncSync9(fd);
90830
91088
  } finally {
90831
91089
  closeSync15(fd);
90832
91090
  }
@@ -91794,7 +92052,7 @@ import {
91794
92052
  writeFileSync as writeFileSync40
91795
92053
  } from "node:fs";
91796
92054
  import { tmpdir as tmpdir5, homedir as homedir49 } from "node:os";
91797
- import { dirname as dirname28, join as join85, relative as relative2, resolve as resolve52 } from "node:path";
92055
+ import { dirname as dirname29, join as join85, relative as relative2, resolve as resolve52 } from "node:path";
91798
92056
  import { spawnSync as spawnSync15 } from "node:child_process";
91799
92057
 
91800
92058
  // src/cli/skill-common.ts
@@ -92264,7 +92522,7 @@ function writePayload(poolDir, name, files) {
92264
92522
  try {
92265
92523
  for (const [path8, content] of Object.entries(files)) {
92266
92524
  const full = join85(staging, path8);
92267
- mkdirSync49(dirname28(full), { recursive: true, mode: 493 });
92525
+ mkdirSync49(dirname29(full), { recursive: true, mode: 493 });
92268
92526
  const fd = openSync16(full, "wx");
92269
92527
  try {
92270
92528
  writeFileSync40(fd, content);
@@ -92386,7 +92644,7 @@ import {
92386
92644
  utimesSync,
92387
92645
  writeFileSync as writeFileSync41
92388
92646
  } from "node:fs";
92389
- import { dirname as dirname29, join as join86, relative as relative3, resolve as resolve53 } from "node:path";
92647
+ import { dirname as dirname30, join as join86, relative as relative3, resolve as resolve53 } from "node:path";
92390
92648
  import { homedir as homedir50, tmpdir as tmpdir6 } from "node:os";
92391
92649
  import { spawnSync as spawnSync16 } from "node:child_process";
92392
92650
  init_helpers();
@@ -92639,13 +92897,13 @@ function writePersonalSkill(targetDir, files) {
92639
92897
  if (targetIsSymlink) {
92640
92898
  fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
92641
92899
  }
92642
- mkdirSync50(dirname29(targetDir), { recursive: true, mode: 493 });
92643
- const staging = mkdtempSync6(join86(dirname29(targetDir), `.skill-personal-stage-`));
92900
+ mkdirSync50(dirname30(targetDir), { recursive: true, mode: 493 });
92901
+ const staging = mkdtempSync6(join86(dirname30(targetDir), `.skill-personal-stage-`));
92644
92902
  let oldRename = null;
92645
92903
  try {
92646
92904
  for (const [path8, content] of Object.entries(files)) {
92647
92905
  const full = join86(staging, path8);
92648
- mkdirSync50(dirname29(full), { recursive: true, mode: 493 });
92906
+ mkdirSync50(dirname30(full), { recursive: true, mode: 493 });
92649
92907
  const fd = openSync17(full, "wx");
92650
92908
  try {
92651
92909
  writeFileSync41(fd, content);
@@ -93388,7 +93646,7 @@ import {
93388
93646
  statSync as statSync39,
93389
93647
  lstatSync as lstatSync11,
93390
93648
  realpathSync as realpathSync8,
93391
- copyFileSync as copyFileSync12
93649
+ copyFileSync as copyFileSync13
93392
93650
  } from "node:fs";
93393
93651
  import { homedir as homedir53 } from "node:os";
93394
93652
  import { join as join89 } from "node:path";
@@ -93659,7 +93917,7 @@ function backupExistingCompose() {
93659
93917
  return null;
93660
93918
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
93661
93919
  const bak = `${p}.bak-${ts}`;
93662
- copyFileSync12(p, bak);
93920
+ copyFileSync13(p, bak);
93663
93921
  return bak;
93664
93922
  }
93665
93923
  function runDocker(args) {
@@ -93872,7 +94130,7 @@ The log is created when hostd handles its first privileged-verb request.`));
93872
94130
  init_source();
93873
94131
  init_helpers();
93874
94132
  init_operator_uid();
93875
- import { existsSync as existsSync93, mkdirSync as mkdirSync52, writeFileSync as writeFileSync43, copyFileSync as copyFileSync13 } from "node:fs";
94133
+ import { existsSync as existsSync93, mkdirSync as mkdirSync52, writeFileSync as writeFileSync43, copyFileSync as copyFileSync14 } from "node:fs";
93876
94134
  import { homedir as homedir54 } from "node:os";
93877
94135
  import { join as join90 } from "node:path";
93878
94136
  import { spawnSync as spawnSync21 } from "node:child_process";
@@ -93970,7 +94228,7 @@ function backupExistingCompose2() {
93970
94228
  return null;
93971
94229
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
93972
94230
  const bak = `${p}.bak-${ts}`;
93973
- copyFileSync13(p, bak);
94231
+ copyFileSync14(p, bak);
93974
94232
  return bak;
93975
94233
  }
93976
94234
  function runDocker2(args) {