switchroom 0.17.10 → 0.18.6

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 (149) hide show
  1. package/bin/workspace-dynamic-hook.sh +12 -13
  2. package/dist/agent-scheduler/index.js +29 -2
  3. package/dist/auth-broker/index.js +6163 -152
  4. package/dist/cli/notion-write-pretool.mjs +31 -3
  5. package/dist/cli/switchroom.js +695 -526
  6. package/dist/host-control/main.js +6184 -173
  7. package/dist/vault/approvals/kernel-server.js +5893 -165
  8. package/dist/vault/broker/server.js +6666 -921
  9. package/package.json +1 -1
  10. package/profiles/_base/settings.json.hbs +2 -2
  11. package/profiles/_base/start.sh.hbs +170 -21
  12. package/profiles/coding/CLAUDE.md.hbs +1 -1
  13. package/profiles/default/CLAUDE.md.hbs +2 -2
  14. package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
  15. package/profiles/health-coach/CLAUDE.md.hbs +1 -1
  16. package/skills/switchroom-release/SKILL.md +78 -0
  17. package/telegram-plugin/auth-snapshot-format.ts +37 -25
  18. package/telegram-plugin/context-exhaustion.ts +124 -0
  19. package/telegram-plugin/dist/gateway/gateway.js +25025 -9203
  20. package/telegram-plugin/gateway/activity-card-store.ts +76 -0
  21. package/telegram-plugin/gateway/gateway.ts +740 -106
  22. package/telegram-plugin/gateway/inbound-delivery-gate.ts +26 -0
  23. package/telegram-plugin/gateway/model-command.ts +70 -10
  24. package/telegram-plugin/gateway/resolve-person.ts +304 -0
  25. package/telegram-plugin/gateway/unhandled-rejection-policy.ts +21 -1
  26. package/telegram-plugin/hooks/silent-end-scan.mjs +164 -40
  27. package/telegram-plugin/operator-events.ts +21 -0
  28. package/telegram-plugin/package.json +6 -0
  29. package/telegram-plugin/pending-work-progress.ts +42 -7
  30. package/telegram-plugin/quota-bar-format.ts +360 -0
  31. package/telegram-plugin/quota-watch.ts +4 -6
  32. package/telegram-plugin/registry/turns-schema.test.ts +97 -0
  33. package/telegram-plugin/registry/turns-schema.ts +78 -0
  34. package/telegram-plugin/render/ir.ts +209 -0
  35. package/telegram-plugin/render/parse.ts +363 -0
  36. package/telegram-plugin/render/render.ts +440 -0
  37. package/telegram-plugin/render/rich-render.ts +72 -0
  38. package/telegram-plugin/stream-controller.ts +14 -3
  39. package/telegram-plugin/subagent-watcher.ts +27 -9
  40. package/telegram-plugin/tests/activity-card-store.test.ts +94 -0
  41. package/telegram-plugin/tests/auth-command-format2.test.ts +1 -1
  42. package/telegram-plugin/tests/auth-snapshot-format.test.ts +51 -16
  43. package/telegram-plugin/tests/claude-code-event-contract.test.ts +48 -0
  44. package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +11 -0
  45. package/telegram-plugin/tests/feed-survival.test.ts +39 -0
  46. package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
  47. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +81 -0
  48. package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +82 -0
  49. package/telegram-plugin/tests/liveness-tracker.test.ts +228 -0
  50. package/telegram-plugin/tests/model-command.test.ts +193 -16
  51. package/telegram-plugin/tests/narrative-render.test.ts +125 -0
  52. package/telegram-plugin/tests/operator-events.test.ts +16 -0
  53. package/telegram-plugin/tests/orphaned-reply-rearm.test.ts +123 -163
  54. package/telegram-plugin/tests/pending-work-progress.test.ts +116 -3
  55. package/telegram-plugin/tests/quota-bar-format.test.ts +444 -0
  56. package/telegram-plugin/tests/quota-watch.test.ts +1 -4
  57. package/telegram-plugin/tests/rapid-fire-delivery-ordering.test.ts +149 -0
  58. package/telegram-plugin/tests/render/parse-torture.test.ts +136 -0
  59. package/telegram-plugin/tests/render/parse.test.ts +393 -0
  60. package/telegram-plugin/tests/render/render.test.ts +436 -0
  61. package/telegram-plugin/tests/render/rich-render.test.ts +85 -0
  62. package/telegram-plugin/tests/resolve-person.test.ts +290 -0
  63. package/telegram-plugin/tests/silent-end-interrupt-stop-integration.test.ts +53 -0
  64. package/telegram-plugin/tests/silent-end-interrupt-stop-scan.test.ts +138 -0
  65. package/telegram-plugin/tests/subagent-watcher.test.ts +61 -0
  66. package/telegram-plugin/tests/telegram-activity-visibility-integration.test.ts +155 -1
  67. package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +19 -0
  68. package/telegram-plugin/tests/worker-activity-feed.test.ts +97 -0
  69. package/telegram-plugin/tests/worktree-watch-cwds.test.ts +98 -3
  70. package/telegram-plugin/turn-liveness-floor.ts +35 -1
  71. package/telegram-plugin/uat/scenarios/jtbd-rich-formatting-render-dm.test.ts +99 -7
  72. package/telegram-plugin/worker-activity-feed.ts +220 -15
  73. package/telegram-plugin/worktree-watch-cwds.ts +92 -17
  74. package/vendor/hindsight-memory/scripts/lib/client.py +11 -1
  75. package/vendor/hindsight-memory/scripts/lib/config.py +9 -2
  76. package/vendor/hindsight-memory/scripts/recall.py +64 -6
  77. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +1 -0
  78. package/vendor/hindsight-memory/tests/test_client.py +43 -0
  79. package/vendor/hindsight-memory/tests/test_recall_precision.py +114 -0
  80. package/profiles/default/CLAUDE.md +0 -116
  81. package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  82. package/vendor/hindsight-memory/scripts/__pycache__/directive_verify.cpython-313.pyc +0 -0
  83. package/vendor/hindsight-memory/scripts/__pycache__/drain_pending.cpython-313.pyc +0 -0
  84. package/vendor/hindsight-memory/scripts/__pycache__/recall.cpython-313.pyc +0 -0
  85. package/vendor/hindsight-memory/scripts/__pycache__/retain.cpython-313.pyc +0 -0
  86. package/vendor/hindsight-memory/scripts/__pycache__/session_end.cpython-313.pyc +0 -0
  87. package/vendor/hindsight-memory/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  88. package/vendor/hindsight-memory/scripts/lib/__pycache__/bank.cpython-313.pyc +0 -0
  89. package/vendor/hindsight-memory/scripts/lib/__pycache__/client.cpython-313.pyc +0 -0
  90. package/vendor/hindsight-memory/scripts/lib/__pycache__/config.cpython-313.pyc +0 -0
  91. package/vendor/hindsight-memory/scripts/lib/__pycache__/content.cpython-313.pyc +0 -0
  92. package/vendor/hindsight-memory/scripts/lib/__pycache__/daemon.cpython-313.pyc +0 -0
  93. package/vendor/hindsight-memory/scripts/lib/__pycache__/directives.cpython-313.pyc +0 -0
  94. package/vendor/hindsight-memory/scripts/lib/__pycache__/gateway_ipc.cpython-313.pyc +0 -0
  95. package/vendor/hindsight-memory/scripts/lib/__pycache__/llm.cpython-313.pyc +0 -0
  96. package/vendor/hindsight-memory/scripts/lib/__pycache__/pending.cpython-313.pyc +0 -0
  97. package/vendor/hindsight-memory/scripts/lib/__pycache__/state.cpython-313.pyc +0 -0
  98. package/vendor/hindsight-memory/scripts/lib/__pycache__/switchroom_envelope.cpython-313.pyc +0 -0
  99. package/vendor/hindsight-memory/scripts/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  100. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313-pytest-9.1.1.pyc +0 -0
  101. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313.pyc +0 -0
  102. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313-pytest-9.1.1.pyc +0 -0
  103. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313.pyc +0 -0
  104. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313-pytest-9.1.1.pyc +0 -0
  105. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313.pyc +0 -0
  106. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313-pytest-9.1.1.pyc +0 -0
  107. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313.pyc +0 -0
  108. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313-pytest-9.1.1.pyc +0 -0
  109. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313.pyc +0 -0
  110. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313-pytest-9.1.1.pyc +0 -0
  111. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313.pyc +0 -0
  112. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313-pytest-9.1.1.pyc +0 -0
  113. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313.pyc +0 -0
  114. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313-pytest-9.1.1.pyc +0 -0
  115. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313.pyc +0 -0
  116. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313-pytest-9.1.1.pyc +0 -0
  117. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313.pyc +0 -0
  118. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313-pytest-9.1.1.pyc +0 -0
  119. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313.pyc +0 -0
  120. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313-pytest-9.1.1.pyc +0 -0
  121. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313.pyc +0 -0
  122. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313-pytest-9.1.1.pyc +0 -0
  123. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313.pyc +0 -0
  124. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_switchroom_envelope.cpython-313-pytest-9.1.1.pyc +0 -0
  125. package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  126. package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
  127. package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313-pytest-9.1.1.pyc +0 -0
  128. package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313.pyc +0 -0
  129. package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313-pytest-9.1.1.pyc +0 -0
  130. package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313.pyc +0 -0
  131. package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.0.3.pyc +0 -0
  132. package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.1.1.pyc +0 -0
  133. package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313.pyc +0 -0
  134. package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313-pytest-9.1.1.pyc +0 -0
  135. package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313.pyc +0 -0
  136. package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313-pytest-9.1.1.pyc +0 -0
  137. package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313.pyc +0 -0
  138. package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313-pytest-9.1.1.pyc +0 -0
  139. package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313.pyc +0 -0
  140. package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313-pytest-9.1.1.pyc +0 -0
  141. package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313.pyc +0 -0
  142. package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313-pytest-9.1.1.pyc +0 -0
  143. package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313.pyc +0 -0
  144. package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313-pytest-9.1.1.pyc +0 -0
  145. package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313.pyc +0 -0
  146. package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313-pytest-9.1.1.pyc +0 -0
  147. package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313.pyc +0 -0
  148. package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313-pytest-9.1.1.pyc +0 -0
  149. package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313.pyc +0 -0
@@ -13542,7 +13542,7 @@ var init_zod = __esm(() => {
13542
13542
  function isValidTimezone(value) {
13543
13543
  return TIMEZONE_REGEX.test(value);
13544
13544
  }
13545
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
13545
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
13546
13546
  var init_schema = __esm(() => {
13547
13547
  init_zod();
13548
13548
  CodeRepoEntrySchema = exports_external.object({
@@ -13694,7 +13694,11 @@ var init_schema = __esm(() => {
13694
13694
  sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender \u2192 extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username \u2014 a leading @ is " + "optional \u2014 or numeric user_id), merged " + "into its own results \u2014 so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
13695
13695
  skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true \u2014 saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
13696
13696
  topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) \u2192 soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: \u2026' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
13697
- }).optional().describe("Auto-recall tuning knobs")
13697
+ }).optional().describe("Auto-recall tuning knobs"),
13698
+ retain: exports_external.object({
13699
+ every_n_turns: exports_external.number().int().min(1).optional().describe("How often the Stop hook fires auto-retention, in turns. The " + "vendor plugin default is 10 (a short session can end before " + "retention ever fires); switchroom's scaffold default is 3. " + "Lower = more frequent, smaller retains + tighter crash " + "durability (at 1, every turn is retained before a restart can " + "lose it); higher = fewer, larger retains + less LLM churn. " + "Raised from the historical 1 to 3 because the local reasoning " + "consolidation model (Ollama gpt-oss-20b) ran away on the large " + "overlapping every-turn payloads. Set to 1 for the old " + "every-turn crash-durability guarantee. Min 1. Cascade: " + "per-field merge (agent wins over default)."),
13700
+ overlap_turns: exports_external.number().int().min(0).optional().describe("Extra recent turns included in each chunked retain window on " + "top of `every_n_turns`, so window = overlap_turns + " + "every_n_turns recent turns. Vendor default is 2; switchroom's " + "scaffold default is 1 (smaller payloads for the local " + "reasoning consolidation model). Higher = more redundant " + "context re-sent per fire. Min 0. Cascade: per-field merge.")
13701
+ }).optional().describe("Auto-retain (Stop-hook consolidation) cadence knobs")
13698
13702
  }).optional();
13699
13703
  HookEntrySchema = exports_external.object({
13700
13704
  command: exports_external.string().describe("Shell command to run. Supports ${CLAUDE_CONFIG_DIR} and ${CLAUDE_PLUGIN_ROOT} substitution."),
@@ -13872,6 +13876,21 @@ var init_schema = __esm(() => {
13872
13876
  small_fast_model: exports_external.string().optional().describe("Model id exported as ANTHROPIC_SMALL_FAST_MODEL for the claude CLI's " + "background/fast lane, e.g. 'claude-haiku-4-5-20251001'."),
13873
13877
  tags: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Extra key/value metadata tags attached to the provisioned LiteLLM " + "virtual key. Merged per-key across cascade layers (agent wins).")
13874
13878
  }).optional().describe("LiteLLM routing config \u2014 opt-in per-agent virtual-key auto-provisioning " + "+ routing env. Default OFF. See LiteLLMConfigSchema doc for the full flow.");
13879
+ HindsightPerOpLlmSchema = exports_external.object({
13880
+ model: exports_external.string().min(1).optional().describe("Per-op model (upstream `HINDSIGHT_API_<OP>_LLM_MODEL`). Absent \u2192 " + "inherit the global `hindsight.llm.model`."),
13881
+ provider: exports_external.string().min(1).optional().describe("Per-op provider (upstream `HINDSIGHT_API_<OP>_LLM_PROVIDER`). " + "Absent \u2192 inherit the global `hindsight.llm.provider`."),
13882
+ base_url: exports_external.string().min(1).optional().describe("Per-op base URL (upstream `HINDSIGHT_API_<OP>_LLM_BASE_URL`). " + "Optional passthrough; absent \u2192 inherit the global."),
13883
+ api_key: exports_external.string().min(1).optional().describe("Per-op API key (upstream `HINDSIGHT_API_<OP>_LLM_API_KEY`). Literal " + "or `vault:` reference. Optional passthrough; absent \u2192 inherit global.")
13884
+ }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback \u2014 switchroom emits only the vars set.");
13885
+ HindsightConfigSchema = exports_external.object({
13886
+ llm: exports_external.object({
13887
+ provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
13888
+ model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
13889
+ retain: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `retain` LLM op (memory ingestion). Emits " + "`HINDSIGHT_API_RETAIN_LLM_*`. Absent \u2192 uses the global model/provider."),
13890
+ reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent \u2192 uses global."),
13891
+ consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent \u2192 global.")
13892
+ }).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults.")
13893
+ });
13875
13894
  MicrosoftWorkspaceConfigSchema = exports_external.object({
13876
13895
  microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL \u2014 omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
13877
13896
  microsoft_client_secret: exports_external.string().min(1).optional().describe("Microsoft OAuth client secret. Optional \u2014 public-client apps " + "(Mobile + Desktop platform with 'Allow public client flows' " + "enabled) work without a secret; confidential clients pass " + "one. Either literal or vault reference e.g. " + "'vault:microsoft-oauth-client-secret'."),
@@ -14120,6 +14139,7 @@ var init_schema = __esm(() => {
14120
14139
  config: exports_external.object({
14121
14140
  provider: exports_external.string().default("ollama").describe("Embedding provider (ollama, openai, anthropic)"),
14122
14141
  model: exports_external.string().optional().describe("Embedding model (e.g., 'nomic-embed-text')"),
14142
+ llm_model: exports_external.string().optional().describe("LiteLLM model name for Hindsight's LLM ops (retain/reflect/" + "consolidation) when the top-level `litellm.enabled` carve-out is " + "on. Defaults to a cheap OpenRouter model (routed via LiteLLM's " + "model-mapped path, not the Anthropic OAuth pass-through) to keep " + "background memory-op cost off the Claude subscription quota. Set " + "to a `claude-*` model name to route it back through the OAuth " + "pass-through instead. Has no effect when litellm is disabled."),
14123
14143
  api_key: exports_external.string().optional().describe("API key or vault reference for embedding provider"),
14124
14144
  docker_service: exports_external.boolean().default(true).describe("Whether to include Hindsight in docker-compose"),
14125
14145
  url: exports_external.string().url("Hindsight URL must be a valid URL (no shell-special characters)").optional().describe("Hindsight MCP endpoint URL (e.g., http://localhost:18888/mcp/). Defaults to http://localhost:8888/mcp/.")
@@ -14185,7 +14205,8 @@ var init_schema = __esm(() => {
14185
14205
  UserSchema = exports_external.object({
14186
14206
  name: exports_external.string().optional().describe("Display name for the user."),
14187
14207
  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."),
14188
- profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`).")
14208
+ profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`)."),
14209
+ 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.")
14189
14210
  });
14190
14211
  SwitchroomConfigSchema = exports_external.object({
14191
14212
  switchroom: exports_external.object({
@@ -14197,6 +14218,7 @@ var init_schema = __esm(() => {
14197
14218
  telegram: TelegramConfigSchema,
14198
14219
  release: ReleaseBlock.optional().describe("Fleet-wide default release-channel pin / pointer for the update " + "flow. Either `channel` (dev|rc|latest) or `pin` (sha-<hex>|v<semver>) " + "\u2014 mutually exclusive. Per-agent `release` REPLACES this entirely."),
14199
14220
  memory: MemoryBackendConfigSchema.optional(),
14221
+ hindsight: HindsightConfigSchema.optional().describe("Fleet-singleton Hindsight (memory backend) configuration. Currently " + "just the LLM knob (provider + model) used for retain/reflect/" + "consolidation. Both fields optional; when unset the container falls " + "back to provider=claude-code + the hard-coded HINDSIGHT_DEFAULT_MODEL " + "so nothing changes for operators who don't set it. Read at container " + "launch by startHindsight() (src/setup/hindsight.ts) \u2014 takes effect on " + "the next `switchroom apply` / `memory setup --recreate`."),
14200
14222
  vault: VaultConfigSchema.optional(),
14201
14223
  auth: exports_external.object({
14202
14224
  active: exports_external.string().min(1).optional().describe("Fleet-wide active Anthropic account label. Every agent without " + "an explicit `agent.auth.override` uses this account. See " + "docs/auth.md for the full model. Set by `switchroom auth use <label>`."),
@@ -14645,6 +14667,11 @@ function mergeAgentConfig(defaultsIn, agentIn) {
14645
14667
  continue;
14646
14668
  if (k === "recall" && base.recall && typeof v === "object" && v !== null && !Array.isArray(v)) {
14647
14669
  combined[k] = { ...base.recall, ...v };
14670
+ } else if (k === "retain" && base.retain && typeof v === "object" && v !== null && !Array.isArray(v)) {
14671
+ combined[k] = {
14672
+ ...base.retain,
14673
+ ...v
14674
+ };
14648
14675
  } else if (k === "disposition" && base.disposition && typeof v === "object" && v !== null && !Array.isArray(v)) {
14649
14676
  combined[k] = {
14650
14677
  ...base.disposition,
@@ -15098,7 +15125,7 @@ function resolveAgentsDir(config) {
15098
15125
  if (override && override.length > 0 && override.startsWith("/")) {
15099
15126
  return override;
15100
15127
  }
15101
- return resolveDualPath(config.switchroom.agents_dir);
15128
+ return resolveDualPath(config.switchroom.agents_dir || "~/.switchroom/agents");
15102
15129
  }
15103
15130
  function resolvePath(pathStr) {
15104
15131
  return resolveDualPath(pathStr);
@@ -15151,10 +15178,388 @@ function resolveUsers(config, agentName) {
15151
15178
  ]);
15152
15179
  return { senderBanks, additionalBanks };
15153
15180
  }
15181
+ function resolvePersonEntries(config) {
15182
+ const users = config.users ?? {};
15183
+ const entries = [];
15184
+ for (const [key, u] of Object.entries(users)) {
15185
+ if (!u.person_id)
15186
+ continue;
15187
+ entries.push({ key, person_id: u.person_id, telegram_ids: u.telegram_ids });
15188
+ }
15189
+ return entries;
15190
+ }
15154
15191
  var init_users = __esm(() => {
15155
15192
  init_merge();
15156
15193
  });
15157
15194
 
15195
+ // src/agents/pane-lock.ts
15196
+ function withPaneLock(key, fn) {
15197
+ const tail = tails.get(key) ?? Promise.resolve();
15198
+ const run = tail.then(() => fn());
15199
+ const next = run.then(() => {}, () => {});
15200
+ tails.set(key, next);
15201
+ next.then(() => {
15202
+ if (tails.get(key) === next)
15203
+ tails.delete(key);
15204
+ });
15205
+ return run;
15206
+ }
15207
+ var tails;
15208
+ var init_pane_lock = __esm(() => {
15209
+ tails = new Map;
15210
+ });
15211
+
15212
+ // src/agents/inject.ts
15213
+ var exports_inject = {};
15214
+ __export(exports_inject, {
15215
+ validateInjectCommand: () => validateInjectCommand,
15216
+ normalizeInjectCommand: () => normalizeInjectCommand,
15217
+ makeTmuxRunner: () => makeTmuxRunner,
15218
+ isTuiChromeLine: () => isTuiChromeLine,
15219
+ injectSlashCommandWith: () => injectSlashCommandWith,
15220
+ injectSlashCommand: () => injectSlashCommand,
15221
+ diffPane: () => diffPane,
15222
+ dedupeInjectQueue: () => dedupeInjectQueue,
15223
+ InjectError: () => InjectError,
15224
+ INJECT_COMMANDS: () => INJECT_COMMANDS,
15225
+ INJECT_BLOCKLIST: () => INJECT_BLOCKLIST,
15226
+ INJECT_BLOCKED: () => INJECT_BLOCKED,
15227
+ INJECT_ALLOWLIST: () => INJECT_ALLOWLIST
15228
+ });
15229
+ import { execFileSync } from "node:child_process";
15230
+ function validateInjectCommand(command) {
15231
+ if (typeof command !== "string" || command.trim().length === 0) {
15232
+ throw new InjectError("invalid", "command is empty");
15233
+ }
15234
+ const trimmed = command.trim();
15235
+ if (!trimmed.startsWith("/")) {
15236
+ throw new InjectError("invalid", `command must start with "/": got ${trimmed}`);
15237
+ }
15238
+ const bare = trimmed.split(/\s+/, 1)[0].toLowerCase();
15239
+ const blockedMeta = INJECT_BLOCKED.get(bare);
15240
+ if (blockedMeta) {
15241
+ throw new InjectError("blocked", `${bare} is explicitly blocked from inject (${blockedMeta.reason}).`);
15242
+ }
15243
+ if (!INJECT_COMMANDS.has(bare)) {
15244
+ const allowed = [...INJECT_COMMANDS.keys()].sort().join(", ");
15245
+ throw new InjectError("not_allowed", `${bare} is not in the inject allowlist. Allowed: ${allowed}`);
15246
+ }
15247
+ return bare;
15248
+ }
15249
+ function normalizeInjectCommand(command) {
15250
+ return command.trim().replace(/\s+/g, " ").toLowerCase();
15251
+ }
15252
+ function dedupeInjectQueue(commands) {
15253
+ const seen = new Set;
15254
+ const out = [];
15255
+ for (const raw of commands) {
15256
+ if (typeof raw !== "string")
15257
+ continue;
15258
+ const key = normalizeInjectCommand(raw);
15259
+ if (key.length === 0)
15260
+ continue;
15261
+ if (seen.has(key))
15262
+ continue;
15263
+ seen.add(key);
15264
+ out.push(raw.trim());
15265
+ }
15266
+ return out;
15267
+ }
15268
+ function defaultSocketName(agentName) {
15269
+ return `switchroom-${agentName}`;
15270
+ }
15271
+ function makeTmuxRunner(tmuxBin) {
15272
+ return {
15273
+ capture(socket, session) {
15274
+ try {
15275
+ return execFileSync(tmuxBin, ["-L", socket, "capture-pane", "-p", "-t", session, "-S", "-200"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
15276
+ } catch {
15277
+ return null;
15278
+ }
15279
+ },
15280
+ send(socket, session, args) {
15281
+ const [subcmd, ...rest] = args;
15282
+ const flagEnd = rest.findIndex((a) => !a.startsWith("-"));
15283
+ const flagsBeforeKeys = flagEnd === -1 ? rest : rest.slice(0, flagEnd);
15284
+ const keys = flagEnd === -1 ? [] : rest.slice(flagEnd);
15285
+ execFileSync(tmuxBin, ["-L", socket, subcmd, ...flagsBeforeKeys, "-t", session, ...keys], { stdio: ["pipe", "pipe", "pipe"] });
15286
+ },
15287
+ hasSession(socket, session) {
15288
+ try {
15289
+ execFileSync(tmuxBin, ["-L", socket, "has-session", "-t", session], {
15290
+ stdio: ["pipe", "pipe", "pipe"]
15291
+ });
15292
+ return true;
15293
+ } catch {
15294
+ return false;
15295
+ }
15296
+ }
15297
+ };
15298
+ }
15299
+ function sleep(ms) {
15300
+ return new Promise((res) => setTimeout(res, ms));
15301
+ }
15302
+ function isTuiChromeLine(line) {
15303
+ const t = line.trim();
15304
+ if (t.length === 0)
15305
+ return false;
15306
+ if (/^[\s\-_\u2502\u2500\u256d\u256e\u2570\u256f\u250c\u2510\u2514\u2518|]+$/.test(t))
15307
+ return true;
15308
+ if (/^[\u256d\u256e\u2570\u256f\u2502\s]*[>)\u276f][\u256d\u256e\u2570\u256f\u2502\s]*$/.test(t))
15309
+ return true;
15310
+ if (/accept edits on/i.test(t))
15311
+ return true;
15312
+ if (/shift\+tab to cycle/i.test(t))
15313
+ return true;
15314
+ if (/for agents/i.test(t))
15315
+ return true;
15316
+ if (/\? for shortcuts/i.test(t))
15317
+ return true;
15318
+ if (/bypassing permissions/i.test(t))
15319
+ return true;
15320
+ if (t === "copy")
15321
+ return true;
15322
+ return false;
15323
+ }
15324
+ function diffPane(before, after, command) {
15325
+ if (command) {
15326
+ const escaped = command.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15327
+ const lines = after.split(`
15328
+ `);
15329
+ let anchorIdx = -1;
15330
+ for (let i = lines.length - 1;i >= 0; i--) {
15331
+ if (/[\u276f>]\s+/.test(lines[i]) && lines[i].includes(escaped)) {
15332
+ anchorIdx = i;
15333
+ break;
15334
+ }
15335
+ }
15336
+ if (anchorIdx >= 0) {
15337
+ const tail = lines.slice(anchorIdx + 1);
15338
+ const trimmed = [];
15339
+ for (const raw of tail) {
15340
+ const line = raw.trimEnd();
15341
+ if (line.length === 0 && trimmed.length === 0)
15342
+ continue;
15343
+ if (isTuiChromeLine(line))
15344
+ continue;
15345
+ trimmed.push(line);
15346
+ }
15347
+ while (trimmed.length > 0 && trimmed[trimmed.length - 1].length === 0) {
15348
+ trimmed.pop();
15349
+ }
15350
+ const affordances = /^(esc to cancel|press any key|\u21b5 select)/i;
15351
+ while (trimmed.length > 0 && affordances.test(trimmed[trimmed.length - 1].trim())) {
15352
+ trimmed.pop();
15353
+ }
15354
+ if (trimmed.length > 0) {
15355
+ return { output: trimmed.join(`
15356
+ `), anchored: true };
15357
+ }
15358
+ }
15359
+ }
15360
+ const beforeSet = new Set(before.split(`
15361
+ `).map((l) => l.trimEnd()));
15362
+ const newLines = [];
15363
+ for (const raw of after.split(`
15364
+ `)) {
15365
+ const line = raw.trimEnd();
15366
+ if (line.length === 0)
15367
+ continue;
15368
+ if (beforeSet.has(line))
15369
+ continue;
15370
+ if (isTuiChromeLine(line))
15371
+ continue;
15372
+ newLines.push(line);
15373
+ }
15374
+ return { output: newLines.join(`
15375
+ `), anchored: false };
15376
+ }
15377
+ async function injectSlashCommand(agentName, command, opts = {}) {
15378
+ validateInjectCommand(command);
15379
+ const tmuxBin = opts.tmuxBin ?? "tmux";
15380
+ const socket = opts.socketName ?? defaultSocketName(agentName);
15381
+ const session = opts.sessionName ?? agentName;
15382
+ const settleMs = opts.settleMs ?? 2000;
15383
+ const timeoutMs = opts.timeoutMs ?? 5000;
15384
+ return withPaneLock(`${socket}:${session}`, () => injectSlashCommandWith(makeTmuxRunner(tmuxBin), {
15385
+ socket,
15386
+ session,
15387
+ command: command.trim(),
15388
+ settleMs,
15389
+ timeoutMs
15390
+ }));
15391
+ }
15392
+ async function injectSlashCommandWith(runner, args) {
15393
+ const { socket, session, command, settleMs, timeoutMs } = args;
15394
+ let bareVerb;
15395
+ try {
15396
+ bareVerb = validateInjectCommand(command);
15397
+ } catch (err) {
15398
+ if (err instanceof InjectError) {
15399
+ return {
15400
+ outcome: "failed",
15401
+ output: "",
15402
+ truncated: false,
15403
+ command: command.trim().split(/\s+/, 1)[0]?.toLowerCase() ?? "",
15404
+ meta: null,
15405
+ errorCode: err.code,
15406
+ errorMessage: err.message
15407
+ };
15408
+ }
15409
+ throw err;
15410
+ }
15411
+ const meta = INJECT_COMMANDS.get(bareVerb) ?? null;
15412
+ if (!runner.hasSession(socket, session)) {
15413
+ return {
15414
+ outcome: "failed",
15415
+ output: "",
15416
+ truncated: false,
15417
+ command: bareVerb,
15418
+ meta,
15419
+ errorCode: "session_missing",
15420
+ errorMessage: `tmux session "${session}" on socket "${socket}" not found. ` + `Is the agent running under the tmux supervisor (the default)? ` + `If experimental.legacy_pty=true is set, inject is unsupported.`
15421
+ };
15422
+ }
15423
+ const before = runner.capture(socket, session) ?? "";
15424
+ try {
15425
+ runner.send(socket, session, ["send-keys", "-l", command]);
15426
+ runner.send(socket, session, ["send-keys", "Enter"]);
15427
+ } catch (err) {
15428
+ return {
15429
+ outcome: "failed",
15430
+ output: "",
15431
+ truncated: false,
15432
+ command: bareVerb,
15433
+ meta,
15434
+ errorCode: "tmux_failed",
15435
+ errorMessage: `tmux send-keys failed: ${err instanceof Error ? err.message : String(err)}`
15436
+ };
15437
+ }
15438
+ const start = Date.now();
15439
+ let last = before;
15440
+ let stableSince = null;
15441
+ while (Date.now() - start < timeoutMs) {
15442
+ await sleep(POLL_INTERVAL_MS);
15443
+ const cur = runner.capture(socket, session) ?? "";
15444
+ if (cur === last && cur !== before) {
15445
+ if (stableSince === null) {
15446
+ stableSince = Date.now();
15447
+ } else if (Date.now() - stableSince >= POLL_INTERVAL_MS) {
15448
+ last = cur;
15449
+ break;
15450
+ }
15451
+ } else {
15452
+ stableSince = null;
15453
+ }
15454
+ last = cur;
15455
+ if (Date.now() - start >= settleMs && cur !== before) {
15456
+ break;
15457
+ }
15458
+ }
15459
+ const { output: rawOutput, anchored } = diffPane(before, last, command);
15460
+ let output = rawOutput;
15461
+ let truncated = false;
15462
+ const bytes = Buffer.byteLength(output, "utf-8");
15463
+ if (bytes > OUTPUT_BYTE_CAP) {
15464
+ while (Buffer.byteLength(output, "utf-8") > OUTPUT_BYTE_CAP) {
15465
+ output = output.slice(Math.floor(output.length * 0.1) + 1);
15466
+ }
15467
+ truncated = true;
15468
+ }
15469
+ if (meta?.dialog) {
15470
+ try {
15471
+ runner.send(socket, session, ["send-keys", "Escape"]);
15472
+ } catch {}
15473
+ }
15474
+ if (output.trim().length === 0) {
15475
+ return {
15476
+ outcome: "ok_no_output",
15477
+ output: "",
15478
+ truncated: false,
15479
+ command: bareVerb,
15480
+ meta,
15481
+ ...anchored ? {} : { diagnostic: "anchor_missing" }
15482
+ };
15483
+ }
15484
+ return {
15485
+ outcome: "ok",
15486
+ output,
15487
+ truncated,
15488
+ command: bareVerb,
15489
+ meta,
15490
+ ...truncated ? { diagnostic: "truncated_output" } : {}
15491
+ };
15492
+ }
15493
+ var INJECT_COMMANDS, INJECT_ALLOWLIST, INJECT_BLOCKED, INJECT_BLOCKLIST, InjectError, POLL_INTERVAL_MS = 150, OUTPUT_BYTE_CAP = 3000;
15494
+ var init_inject = __esm(() => {
15495
+ init_pane_lock();
15496
+ INJECT_COMMANDS = new Map([
15497
+ ["/cost", { description: "Show session cost", expectsOutput: true, dialog: true }],
15498
+ ["/status", { description: "Show session status", expectsOutput: true, dialog: true }],
15499
+ ["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true }],
15500
+ ["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true }],
15501
+ ["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true }],
15502
+ ["/model", { description: "Open model picker", expectsOutput: true }],
15503
+ [
15504
+ "/clear",
15505
+ {
15506
+ description: "Clear session screen",
15507
+ expectsOutput: false,
15508
+ silentNote: "context cleared \u2014 fresh slate"
15509
+ }
15510
+ ],
15511
+ [
15512
+ "/compact",
15513
+ {
15514
+ description: "Compact conversation history",
15515
+ expectsOutput: false,
15516
+ silentNote: "compaction runs silently"
15517
+ }
15518
+ ]
15519
+ ]);
15520
+ INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
15521
+ INJECT_BLOCKED = new Map([
15522
+ ["/login", { reason: "would mutate auth state" }],
15523
+ ["/logout", { reason: "would terminate the agent's auth session" }],
15524
+ ["/exit", { reason: "would kill the agent process" }],
15525
+ ["/quit", { reason: "would kill the agent process" }],
15526
+ [
15527
+ "/effort",
15528
+ {
15529
+ reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
15530
+ }
15531
+ ]
15532
+ ]);
15533
+ INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
15534
+ InjectError = class InjectError extends Error {
15535
+ code;
15536
+ constructor(code, message) {
15537
+ super(message);
15538
+ this.name = "InjectError";
15539
+ this.code = code;
15540
+ }
15541
+ };
15542
+ });
15543
+
15544
+ // src/agents/model-picker.ts
15545
+ var init_model_picker = __esm(() => {
15546
+ init_inject();
15547
+ init_pane_lock();
15548
+ });
15549
+
15550
+ // telegram-plugin/gateway/model-command.ts
15551
+ function isClaudeModel(name) {
15552
+ const lower = name.toLowerCase();
15553
+ if (MODEL_ALIASES.includes(lower))
15554
+ return true;
15555
+ return lower.startsWith("claude-");
15556
+ }
15557
+ var MODEL_ALIASES;
15558
+ var init_model_command = __esm(() => {
15559
+ init_model_picker();
15560
+ MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
15561
+ });
15562
+
15158
15563
  // src/util/atomic.ts
15159
15564
  var exports_atomic = {};
15160
15565
  __export(exports_atomic, {
@@ -15223,7 +15628,7 @@ var init_atomic = __esm(() => {
15223
15628
  });
15224
15629
 
15225
15630
  // src/setup/hindsight.ts
15226
- import { execFileSync } from "node:child_process";
15631
+ import { execFileSync as execFileSync2 } from "node:child_process";
15227
15632
  import { createServer } from "node:net";
15228
15633
  function hindsightImageRef(tag) {
15229
15634
  const t = tag?.trim();
@@ -15249,7 +15654,7 @@ function describePortHolder(port) {
15249
15654
  ];
15250
15655
  for (const [cmd, args] of probes) {
15251
15656
  try {
15252
- const out = execFileSync(cmd, args, {
15657
+ const out = execFileSync2(cmd, args, {
15253
15658
  stdio: "pipe",
15254
15659
  encoding: "utf-8"
15255
15660
  }).trim();
@@ -15306,7 +15711,7 @@ async function pickHindsightPorts() {
15306
15711
  }
15307
15712
  function isDockerAvailable() {
15308
15713
  try {
15309
- execFileSync("docker", ["--version"], { stdio: "pipe" });
15714
+ execFileSync2("docker", ["--version"], { stdio: "pipe" });
15310
15715
  return true;
15311
15716
  } catch {
15312
15717
  return false;
@@ -15314,7 +15719,7 @@ function isDockerAvailable() {
15314
15719
  }
15315
15720
  function isHindsightRunning() {
15316
15721
  try {
15317
- const output = execFileSync("docker", ["ps", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
15722
+ const output = execFileSync2("docker", ["ps", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
15318
15723
  return output.trim().length > 0;
15319
15724
  } catch {
15320
15725
  return false;
@@ -15322,24 +15727,54 @@ function isHindsightRunning() {
15322
15727
  }
15323
15728
  function isHindsightContainerExists() {
15324
15729
  try {
15325
- const output = execFileSync("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf-8" });
15730
+ const output = execFileSync2("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf-8" });
15326
15731
  return output.trim().length > 0;
15327
15732
  } catch {
15328
15733
  return false;
15329
15734
  }
15330
15735
  }
15331
- function startHindsight(ports, litellm, imageTag) {
15736
+ function resolveHindsightPerOpLlm(llm) {
15737
+ const out = [];
15738
+ if (!llm)
15739
+ return out;
15740
+ for (const op of HINDSIGHT_LLM_OPS) {
15741
+ const cfg = llm[op];
15742
+ if (!cfg)
15743
+ continue;
15744
+ const prefix = `HINDSIGHT_API_${op.toUpperCase()}_LLM`;
15745
+ const model = cfg.model?.trim();
15746
+ const provider = cfg.provider?.trim();
15747
+ const baseUrl = cfg.base_url?.trim();
15748
+ const apiKey = cfg.api_key?.trim();
15749
+ if (model)
15750
+ out.push([`${prefix}_MODEL`, model]);
15751
+ if (provider)
15752
+ out.push([`${prefix}_PROVIDER`, provider]);
15753
+ if (baseUrl)
15754
+ out.push([`${prefix}_BASE_URL`, baseUrl]);
15755
+ if (apiKey)
15756
+ out.push([`${prefix}_API_KEY`, apiKey]);
15757
+ }
15758
+ return out;
15759
+ }
15760
+ function resolveHindsightLlm(llm, litellm) {
15761
+ const provider = llm?.provider?.trim() || "claude-code";
15762
+ const model = llm?.model?.trim() || (litellm ? litellm.model ?? HINDSIGHT_DEFAULT_LITELLM_MODEL : HINDSIGHT_DEFAULT_MODEL);
15763
+ return { provider, model };
15764
+ }
15765
+ function startHindsight(ports, litellm, imageTag, llm) {
15332
15766
  const apiPort = ports?.apiPort ?? HINDSIGHT_DEFAULT_API_PORT;
15333
15767
  const uiPort = ports?.uiPort ?? HINDSIGHT_DEFAULT_UI_PORT;
15768
+ const { provider: llmProvider, model: llmModel } = resolveHindsightLlm(llm, litellm);
15769
+ const perOpLlm = resolveHindsightPerOpLlm(llm);
15334
15770
  const envArgs = [
15335
15771
  "-e",
15336
15772
  `HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
15337
15773
  "-e",
15338
- "HINDSIGHT_API_LLM_PROVIDER=claude-code",
15774
+ `HINDSIGHT_API_LLM_PROVIDER=${llmProvider}`,
15339
15775
  "-e",
15340
- `HINDSIGHT_API_LLM_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
15341
- "-e",
15342
- `ANTHROPIC_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
15776
+ `HINDSIGHT_API_LLM_MODEL=${llmModel}`,
15777
+ ...perOpLlm.flatMap(([k, v]) => ["-e", `${k}=${v}`]),
15343
15778
  "-e",
15344
15779
  `HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`,
15345
15780
  "-e",
@@ -15361,8 +15796,13 @@ function startHindsight(ports, litellm, imageTag) {
15361
15796
  "-e",
15362
15797
  `HINDSIGHT_API_CONSOLIDATION_MAX_MEMORIES_PER_ROUND=${HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND}`
15363
15798
  ];
15799
+ if (llmProvider === "claude-code") {
15800
+ envArgs.push("-e", `ANTHROPIC_MODEL=${llmModel}`);
15801
+ }
15364
15802
  if (litellm) {
15365
- envArgs.push("-e", `HINDSIGHT_API_PORT=${apiPort}`, "-e", `ANTHROPIC_BASE_URL=${litellm.baseUrl.replace(/\/+$/, "")}/anthropic`, "-e", `ANTHROPIC_CUSTOM_HEADERS=x-litellm-api-key: Bearer ${litellm.apiKey}
15803
+ const litellmRoot = litellm.baseUrl.replace(/\/+$/, "");
15804
+ const anthropicBaseUrl = isClaudeModel(llmModel) ? `${litellmRoot}/anthropic` : litellmRoot;
15805
+ envArgs.push("-e", `HINDSIGHT_API_PORT=${apiPort}`, "-e", `HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:${apiPort}`, "-e", `ANTHROPIC_BASE_URL=${anthropicBaseUrl}`, "-e", `ANTHROPIC_CUSTOM_HEADERS=x-litellm-api-key: Bearer ${litellm.apiKey}
15366
15806
  x-litellm-customer-id: hindsight
15367
15807
  x-litellm-tags: service:hindsight`);
15368
15808
  }
@@ -15394,25 +15834,25 @@ x-litellm-tags: service:hindsight`);
15394
15834
  args.push("-p", `127.0.0.1:${apiPort}:8888`, "-p", `127.0.0.1:${uiPort}:9999`);
15395
15835
  }
15396
15836
  args.push("-v", "switchroom-hindsight-data:/home/hindsight/.pg0", "-v", "switchroom-hindsight-backups:/backups", "-v", `${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`, "--tmpfs", `/run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`, ...envArgs, hindsightImageRef(imageTag));
15397
- execFileSync("docker", args, { stdio: "pipe" });
15837
+ execFileSync2("docker", args, { stdio: "pipe" });
15398
15838
  }
15399
15839
  function stopHindsight() {
15400
15840
  try {
15401
- execFileSync("docker", ["stop", "switchroom-hindsight"], { stdio: "pipe" });
15841
+ execFileSync2("docker", ["stop", "switchroom-hindsight"], { stdio: "pipe" });
15402
15842
  } catch {}
15403
15843
  try {
15404
- execFileSync("docker", ["rm", "switchroom-hindsight"], { stdio: "pipe" });
15844
+ execFileSync2("docker", ["rm", "switchroom-hindsight"], { stdio: "pipe" });
15405
15845
  } catch {}
15406
15846
  }
15407
15847
  function pullHindsightImage(imageTag) {
15408
- execFileSync("docker", ["pull", hindsightImageRef(imageTag)], { stdio: "inherit" });
15848
+ execFileSync2("docker", ["pull", hindsightImageRef(imageTag)], { stdio: "inherit" });
15409
15849
  }
15410
15850
  function getRunningHindsightPorts() {
15411
15851
  const CONTAINER_API_PORT = 8888;
15412
15852
  const CONTAINER_UI_PORT = 9999;
15413
15853
  const readPort = (containerPort) => {
15414
15854
  try {
15415
- const out = execFileSync("docker", ["port", "switchroom-hindsight", `${containerPort}/tcp`], { stdio: "pipe", encoding: "utf-8" });
15855
+ const out = execFileSync2("docker", ["port", "switchroom-hindsight", `${containerPort}/tcp`], { stdio: "pipe", encoding: "utf-8" });
15416
15856
  const m = out.split(`
15417
15857
  `).map((l) => l.trim()).find((l) => l.length > 0);
15418
15858
  if (!m)
@@ -15433,7 +15873,7 @@ function getRunningHindsightPorts() {
15433
15873
  function getHindsightPortsFromEnv() {
15434
15874
  let env2;
15435
15875
  try {
15436
- env2 = execFileSync("docker", ["inspect", "--format", "{{.Config.Env}}", "switchroom-hindsight"], { stdio: "pipe", encoding: "utf-8" });
15876
+ env2 = execFileSync2("docker", ["inspect", "--format", "{{.Config.Env}}", "switchroom-hindsight"], { stdio: "pipe", encoding: "utf-8" });
15437
15877
  } catch {
15438
15878
  return null;
15439
15879
  }
@@ -15453,14 +15893,24 @@ function getHindsightPortsFromEnv() {
15453
15893
  }
15454
15894
  function getHindsightStatus() {
15455
15895
  try {
15456
- const output = execFileSync("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
15896
+ const output = execFileSync2("docker", ["ps", "-a", "--filter", "name=switchroom-hindsight", "--format", "{{.Status}}"], { stdio: "pipe", encoding: "utf-8" });
15457
15897
  const status = output.trim();
15458
15898
  return status.length > 0 ? status : null;
15459
15899
  } catch {
15460
15900
  return null;
15461
15901
  }
15462
15902
  }
15463
- function generateHindsightComposeSnippet() {
15903
+ function generateHindsightComposeSnippet(llm) {
15904
+ const { provider: llmProvider, model: llmModel } = resolveHindsightLlm(llm);
15905
+ const perOpLlm = resolveHindsightPerOpLlm(llm);
15906
+ const environment = [
15907
+ ` - HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
15908
+ ` - HINDSIGHT_API_LLM_PROVIDER=${llmProvider}`,
15909
+ ` - HINDSIGHT_API_LLM_MODEL=${llmModel}`,
15910
+ ...perOpLlm.map(([k, v]) => ` - ${k}=${v}`),
15911
+ ...llmProvider === "claude-code" ? [` - ANTHROPIC_MODEL=${llmModel}`] : [],
15912
+ ` - HINDSIGHT_CP_DATAPLANE_API_URL=http://localhost:8888`
15913
+ ];
15464
15914
  return [
15465
15915
  "services:",
15466
15916
  " switchroom-hindsight:",
@@ -15470,10 +15920,7 @@ function generateHindsightComposeSnippet() {
15470
15920
  ` - "127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}:8888"`,
15471
15921
  ' - "127.0.0.1:19999:9999"',
15472
15922
  " environment:",
15473
- ` - HINDSIGHT_API_MAX_OBSERVATIONS_PER_SCOPE=${HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE}`,
15474
- " - HINDSIGHT_API_LLM_PROVIDER=claude-code",
15475
- ` - HINDSIGHT_API_LLM_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
15476
- ` - ANTHROPIC_MODEL=${HINDSIGHT_DEFAULT_MODEL}`,
15923
+ ...environment,
15477
15924
  ` - HINDSIGHT_API_MCP_STATELESS=${HINDSIGHT_DEFAULT_MCP_STATELESS}`,
15478
15925
  ` - HINDSIGHT_API_RERANKER_LOCAL_BUCKET_BATCHING=${HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING}`,
15479
15926
  ` - HINDSIGHT_API_RERANKER_MAX_CANDIDATES=${HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES}`,
@@ -15564,13 +16011,15 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
15564
16011
  atomicWriteFileSync2(configPath, tail, mode);
15565
16012
  return { added: true, reason: "added" };
15566
16013
  }
15567
- var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 100, HINDSIGHT_DEFAULT_MEM_LIMIT = "8g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD;
16014
+ var HINDSIGHT_DEFAULT_API_PORT = 18888, HINDSIGHT_DEFAULT_UI_PORT = 9999, HINDSIGHT_DEFAULT_MCP_URL, HINDSIGHT_DEFAULT_API_BASE_URL, HINDSIGHT_DEFAULT_MAX_OBSERVATIONS_PER_SCOPE = 1000, HINDSIGHT_CONSUMER_NAME = "hindsight", HINDSIGHT_DEFAULT_UID = 11000, HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight", HINDSIGHT_IMAGE, HINDSIGHT_DEFAULT_MODEL = "claude-sonnet-5", HINDSIGHT_DEFAULT_LITELLM_MODEL = "openrouter/google/gemini-3.1-flash-lite", HINDSIGHT_DEFAULT_MCP_STATELESS = true, HINDSIGHT_BROKER_SOCK_VOLUME, HINDSIGHT_DEFAULT_RERANKER_BUCKET_BATCHING = "true", HINDSIGHT_DEFAULT_RERANKER_MAX_CANDIDATES = 150, HINDSIGHT_DEFAULT_RERANKER_LOCAL_MAX_CONCURRENT = 4, HINDSIGHT_DEFAULT_RECALL_MAX_CONCURRENT = 8, HINDSIGHT_DEFAULT_REFLECT_WALL_TIMEOUT_S = 600, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_BATCH_SIZE = 12, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_SLOTS = 1, HINDSIGHT_DEFAULT_CONSOLIDATION_LLM_PARALLELISM = 2, HINDSIGHT_DEFAULT_CONSOLIDATION_MAX_MEMORIES_PER_ROUND = 100, HINDSIGHT_DEFAULT_MEM_LIMIT = "8g", HINDSIGHT_DEFAULT_MEM_RESERVATION = "4g", HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000, HINDSIGHT_DEFAULT_SHM_SIZE = "2g", HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)', HINDSIGHT_HEALTHCHECK_CMD, HINDSIGHT_LLM_OPS;
15568
16015
  var init_hindsight = __esm(() => {
16016
+ init_model_command();
15569
16017
  HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
15570
16018
  HINDSIGHT_DEFAULT_API_BASE_URL = HINDSIGHT_DEFAULT_MCP_URL.replace(/\/mcp\/?$/, "");
15571
16019
  HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
15572
16020
  HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
15573
16021
  HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
16022
+ HINDSIGHT_LLM_OPS = ["retain", "reflect", "consolidation"];
15574
16023
  });
15575
16024
 
15576
16025
  // src/memory/hindsight.ts
@@ -23797,7 +24246,7 @@ function parseSocketIdentity(socketPath, spec) {
23797
24246
  }
23798
24247
 
23799
24248
  // src/vault/broker/peercred.ts
23800
- import { execFileSync as execFileSync2 } from "node:child_process";
24249
+ import { execFileSync as execFileSync3 } from "node:child_process";
23801
24250
  import { readFileSync as readFileSync10, readlinkSync as readlinkSync3, fstatSync } from "node:fs";
23802
24251
  function socketPathToIdentity(socketPath) {
23803
24252
  return parseSocketIdentity(socketPath, {
@@ -23970,7 +24419,7 @@ function identify(socketPath, socket, execFileSyncOverride) {
23970
24419
  if (process.platform !== "linux") {
23971
24420
  return null;
23972
24421
  }
23973
- const runner = execFileSyncOverride ?? execFileSync2;
24422
+ const runner = execFileSyncOverride ?? execFileSync3;
23974
24423
  let pid = null;
23975
24424
  if (socket !== undefined) {
23976
24425
  const fd = fdFromSocket(socket);
@@ -24454,7 +24903,8 @@ function brokerIsComposeManaged() {
24454
24903
  return fs.existsSync(OPERATOR_SOCKET_PATH);
24455
24904
  }
24456
24905
  function vaultTokenFilePath(agentSlug) {
24457
- return join6(homedir4(), ".switchroom", "agents", agentSlug, ".vault-token");
24906
+ const base = process.env.SWITCHROOM_AGENTS_DIR || join6(homedir4(), ".switchroom", "agents");
24907
+ return join6(base, agentSlug, ".vault-token");
24458
24908
  }
24459
24909
  function readVaultTokenFile(agentSlug) {
24460
24910
  const filePath = vaultTokenFilePath(agentSlug);
@@ -25263,7 +25713,7 @@ var init_onboarding = __esm(() => {
25263
25713
  });
25264
25714
 
25265
25715
  // src/repos/bare-clone.ts
25266
- import { execFileSync as execFileSync3 } from "node:child_process";
25716
+ import { execFileSync as execFileSync4 } from "node:child_process";
25267
25717
  import { existsSync as existsSync13, mkdirSync as mkdirSync8 } from "node:fs";
25268
25718
  import { resolve as resolve9 } from "node:path";
25269
25719
  function bareClonePath(slug) {
@@ -25276,7 +25726,7 @@ function ensureBareClone(slug, url) {
25276
25726
  if (!existsSync13(clonePath)) {
25277
25727
  process.stderr.write(`[switchroom] repo "${slug}": cloning ${url} \u2026
25278
25728
  `);
25279
- execFileSync3("git", ["clone", "--bare", url, clonePath], {
25729
+ execFileSync4("git", ["clone", "--bare", url, clonePath], {
25280
25730
  stdio: ["ignore", "pipe", "pipe"]
25281
25731
  });
25282
25732
  process.stderr.write(`[switchroom] repo "${slug}": bare clone ready at ${clonePath}
@@ -25285,7 +25735,7 @@ function ensureBareClone(slug, url) {
25285
25735
  process.stderr.write(`[switchroom] repo "${slug}": fetching ${clonePath} \u2026
25286
25736
  `);
25287
25737
  try {
25288
- execFileSync3("git", ["fetch", "--all"], {
25738
+ execFileSync4("git", ["fetch", "--all"], {
25289
25739
  cwd: clonePath,
25290
25740
  stdio: ["ignore", "pipe", "pipe"]
25291
25741
  });
@@ -25302,7 +25752,7 @@ var init_bare_clone = __esm(() => {
25302
25752
  });
25303
25753
 
25304
25754
  // src/repos/agent-worktree.ts
25305
- import { execFileSync as execFileSync4 } from "node:child_process";
25755
+ import { execFileSync as execFileSync5 } from "node:child_process";
25306
25756
  import { existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync5 } from "node:fs";
25307
25757
  import { join as join9, resolve as resolve10 } from "node:path";
25308
25758
  function agentBranchName(agentName) {
@@ -25313,7 +25763,7 @@ function agentWorktreePath(agentDir, slug) {
25313
25763
  }
25314
25764
  function isWorktreeDirty(worktreePath) {
25315
25765
  try {
25316
- const out = execFileSync4("git", ["status", "--porcelain"], {
25766
+ const out = execFileSync5("git", ["status", "--porcelain"], {
25317
25767
  cwd: worktreePath,
25318
25768
  stdio: ["ignore", "pipe", "pipe"],
25319
25769
  encoding: "utf-8"
@@ -25325,7 +25775,7 @@ function isWorktreeDirty(worktreePath) {
25325
25775
  }
25326
25776
  function headShortSha(worktreePath) {
25327
25777
  try {
25328
- return execFileSync4("git", ["rev-parse", "--short", "HEAD"], {
25778
+ return execFileSync5("git", ["rev-parse", "--short", "HEAD"], {
25329
25779
  cwd: worktreePath,
25330
25780
  stdio: ["ignore", "pipe", "pipe"],
25331
25781
  encoding: "utf-8"
@@ -25336,7 +25786,7 @@ function headShortSha(worktreePath) {
25336
25786
  }
25337
25787
  function branchExistsInBare(bareClonePath2, branchName) {
25338
25788
  try {
25339
- execFileSync4("git", ["rev-parse", "--verify", `refs/heads/${branchName}`], {
25789
+ execFileSync5("git", ["rev-parse", "--verify", `refs/heads/${branchName}`], {
25340
25790
  cwd: bareClonePath2,
25341
25791
  stdio: ["ignore", "pipe", "pipe"]
25342
25792
  });
@@ -25347,7 +25797,7 @@ function branchExistsInBare(bareClonePath2, branchName) {
25347
25797
  }
25348
25798
  function resolveDefaultBranch(bareClonePath2) {
25349
25799
  try {
25350
- const out = execFileSync4("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
25800
+ const out = execFileSync5("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
25351
25801
  cwd: bareClonePath2,
25352
25802
  stdio: ["ignore", "pipe", "pipe"],
25353
25803
  encoding: "utf-8"
@@ -25365,12 +25815,12 @@ function ensureAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
25365
25815
  if (!existsSync14(worktreePath)) {
25366
25816
  mkdirSync9(join9(agentDir, "work"), { recursive: true });
25367
25817
  if (branchExistsInBare(bareClonePath2, branch)) {
25368
- execFileSync4("git", ["worktree", "add", worktreePath, branch], {
25818
+ execFileSync5("git", ["worktree", "add", worktreePath, branch], {
25369
25819
  cwd: bareClonePath2,
25370
25820
  stdio: ["ignore", "pipe", "pipe"]
25371
25821
  });
25372
25822
  } else {
25373
- execFileSync4("git", [
25823
+ execFileSync5("git", [
25374
25824
  "worktree",
25375
25825
  "add",
25376
25826
  worktreePath,
@@ -25398,11 +25848,11 @@ function ensureAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
25398
25848
  };
25399
25849
  }
25400
25850
  try {
25401
- execFileSync4("git", ["fetch", "origin"], {
25851
+ execFileSync5("git", ["fetch", "origin"], {
25402
25852
  cwd: worktreePath,
25403
25853
  stdio: ["ignore", "pipe", "pipe"]
25404
25854
  });
25405
- execFileSync4("git", ["merge", "--ff-only", `origin/${defaultBranch}`], {
25855
+ execFileSync5("git", ["merge", "--ff-only", `origin/${defaultBranch}`], {
25406
25856
  cwd: worktreePath,
25407
25857
  stdio: ["ignore", "pipe", "pipe"]
25408
25858
  });
@@ -25420,7 +25870,7 @@ function removeAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
25420
25870
  const branch = agentBranchName(agentName);
25421
25871
  if (existsSync14(worktreePath)) {
25422
25872
  try {
25423
- execFileSync4("git", ["worktree", "remove", "--force", worktreePath], {
25873
+ execFileSync5("git", ["worktree", "remove", "--force", worktreePath], {
25424
25874
  cwd: bareClonePath2,
25425
25875
  stdio: ["ignore", "pipe", "pipe"]
25426
25876
  });
@@ -25432,7 +25882,7 @@ function removeAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
25432
25882
  }
25433
25883
  if (branchExistsInBare(bareClonePath2, branch)) {
25434
25884
  try {
25435
- execFileSync4("git", ["branch", "-D", branch], {
25885
+ execFileSync5("git", ["branch", "-D", branch], {
25436
25886
  cwd: bareClonePath2,
25437
25887
  stdio: ["ignore", "pipe", "pipe"]
25438
25888
  });
@@ -25443,7 +25893,7 @@ function removeAgentWorktree(agentName, slug, bareClonePath2, agentDir) {
25443
25893
  }
25444
25894
  }
25445
25895
  try {
25446
- execFileSync4("git", ["worktree", "prune"], {
25896
+ execFileSync5("git", ["worktree", "prune"], {
25447
25897
  cwd: bareClonePath2,
25448
25898
  stdio: ["ignore", "pipe", "pipe"]
25449
25899
  });
@@ -25472,7 +25922,7 @@ import {
25472
25922
  unlinkSync as unlinkSync4
25473
25923
  } from "node:fs";
25474
25924
  import { homedir as homedir5 } from "node:os";
25475
- import { execSync, execFileSync as execFileSync5 } from "node:child_process";
25925
+ import { execSync, execFileSync as execFileSync6 } from "node:child_process";
25476
25926
  import { join as join10, resolve as resolve11 } from "node:path";
25477
25927
  import { createHash as createHash2 } from "node:crypto";
25478
25928
  function prependReplyDiscipline(rendered, context) {
@@ -25606,13 +26056,13 @@ function alignAgentUid(name, agentDir, uid, opts = {}) {
25606
26056
  `));
25607
26057
  }
25608
26058
  try {
25609
- execFileSync5("chown", ["-R", `${uid}:${uid}`, ...paths], {
26059
+ execFileSync6("chown", ["-R", `${uid}:${uid}`, ...paths], {
25610
26060
  stdio: ["ignore", "ignore", "pipe"]
25611
26061
  });
25612
26062
  return { chowned: true, paths };
25613
26063
  } catch {}
25614
26064
  try {
25615
- execFileSync5("sudo", ["chown", "-R", `${uid}:${uid}`, ...paths], {
26065
+ execFileSync6("sudo", ["chown", "-R", `${uid}:${uid}`, ...paths], {
25616
26066
  stdio: "inherit"
25617
26067
  });
25618
26068
  return { chowned: true, paths };
@@ -26260,7 +26710,7 @@ Thumbs.db
26260
26710
  execSync("git add -A", { cwd: workspaceDir, stdio: "pipe" });
26261
26711
  const userEmail = process.env.GIT_AUTHOR_EMAIL || "switchroom@localhost";
26262
26712
  const userName = process.env.GIT_AUTHOR_NAME || "Switchroom Agent";
26263
- execFileSync5("git", [
26713
+ execFileSync6("git", [
26264
26714
  "-c",
26265
26715
  `user.email=${userEmail}`,
26266
26716
  "-c",
@@ -26298,11 +26748,12 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
26298
26748
  }
26299
26749
  const destPath = join10(agentDir, ".claude", "plugins", "hindsight-memory");
26300
26750
  const additionalBanks = resolveUsers(switchroomConfig, agentName).additionalBanks;
26751
+ const retainConfig = resolveHindsightRetainConfig(switchroomConfig, agentName);
26301
26752
  let expectedSettings = null;
26302
26753
  try {
26303
26754
  const vendorSettingsPath = join10(sourcePath, "settings.json");
26304
26755
  if (existsSync15(vendorSettingsPath)) {
26305
- expectedSettings = renderHindsightSettingsOverrides(readFileSync13(vendorSettingsPath, "utf-8"), additionalBanks);
26756
+ expectedSettings = renderHindsightSettingsOverrides(readFileSync13(vendorSettingsPath, "utf-8"), additionalBanks, retainConfig);
26306
26757
  }
26307
26758
  } catch {}
26308
26759
  const upToDate = dirContentEquals(sourcePath, destPath, expectedSettings != null ? { "settings.json": expectedSettings } : undefined);
@@ -26312,13 +26763,21 @@ function installHindsightPlugin(agentName, agentDir, switchroomConfig) {
26312
26763
  }
26313
26764
  copyDirRecursive2(sourcePath, destPath);
26314
26765
  }
26315
- applyHindsightSettingsOverrides(destPath, additionalBanks);
26766
+ applyHindsightSettingsOverrides(destPath, additionalBanks, retainConfig);
26316
26767
  const bankId = agentMemory?.collection ?? agentName;
26317
26768
  const mcpUrl = memory.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
26318
26769
  const apiBaseUrl = mcpUrl.replace(/\/mcp\/?$/, "").replace(/\/$/, "");
26319
26770
  return { pluginDir: destPath, apiBaseUrl, bankId };
26320
26771
  }
26321
- function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks) {
26772
+ function resolveHindsightRetainConfig(switchroomConfig, agentName) {
26773
+ const resolved = switchroomConfig ? resolveAgentConfig(switchroomConfig.defaults, switchroomConfig.profiles, switchroomConfig.agents[agentName] ?? {}) : undefined;
26774
+ const retain = resolved?.memory?.retain;
26775
+ return {
26776
+ everyNTurns: retain?.every_n_turns ?? HINDSIGHT_DEFAULT_RETAIN_EVERY_N_TURNS,
26777
+ overlapTurns: retain?.overlap_turns ?? HINDSIGHT_DEFAULT_RETAIN_OVERLAP_TURNS
26778
+ };
26779
+ }
26780
+ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks, retainConfig) {
26322
26781
  const settingsPath = join10(pluginDestPath, "settings.json");
26323
26782
  if (!existsSync15(settingsPath))
26324
26783
  return;
@@ -26328,20 +26787,21 @@ function applyHindsightSettingsOverrides(pluginDestPath, additionalBanks) {
26328
26787
  } catch {
26329
26788
  return;
26330
26789
  }
26331
- const next = renderHindsightSettingsOverrides(raw, additionalBanks);
26790
+ const next = renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig);
26332
26791
  if (next == null)
26333
26792
  return;
26334
26793
  writeFileSyncIfChanged(settingsPath, next);
26335
26794
  }
26336
- function renderHindsightSettingsOverrides(raw, additionalBanks) {
26795
+ function renderHindsightSettingsOverrides(raw, additionalBanks, retainConfig) {
26337
26796
  let settings;
26338
26797
  try {
26339
26798
  settings = JSON.parse(raw);
26340
26799
  } catch {
26341
26800
  return null;
26342
26801
  }
26343
- settings.retainEveryNTurns = 1;
26802
+ settings.retainEveryNTurns = retainConfig.everyNTurns;
26344
26803
  settings.retainMode = "chunked";
26804
+ settings.retainOverlapTurns = retainConfig.overlapTurns;
26345
26805
  settings.recallMaxMemories = 8;
26346
26806
  settings.recallMinOverlap = 0.1;
26347
26807
  settings.recallTypes = ["world", "experience", "observation"];
@@ -26353,17 +26813,25 @@ function renderHindsightSettingsOverrides(raw, additionalBanks) {
26353
26813
  return JSON.stringify(settings, null, 2) + `
26354
26814
  `;
26355
26815
  }
26816
+ function resolvePermissionDefaultMode(agentConfig, switchroomConfig) {
26817
+ const raw = agentConfig.permission_mode ?? switchroomConfig?.defaults?.permission_mode ?? SWITCHROOM_DEFAULT_PERMISSION_MODE;
26818
+ if (VALID_SETTINGS_DEFAULT_MODES.includes(raw)) {
26819
+ return raw;
26820
+ }
26821
+ console.warn(source_default.yellow(`[switchroom] permission_mode "${raw}" has no settings.json defaultMode ` + `equivalent \u2014 using "${SWITCHROOM_DEFAULT_PERMISSION_MODE}" for ` + `permissions.defaultMode (the --permission-mode CLI flag still carries "${raw}").`));
26822
+ return SWITCHROOM_DEFAULT_PERMISSION_MODE;
26823
+ }
26356
26824
  function buildWorkspaceContext(args) {
26357
26825
  const {
26358
26826
  name,
26359
26827
  agentDir,
26360
26828
  agentConfig,
26361
26829
  telegramConfig,
26830
+ switchroomConfig,
26362
26831
  switchroomConfigPath,
26363
26832
  topicId,
26364
26833
  tools,
26365
26834
  permissionAllow,
26366
- hasAllWildcard,
26367
26835
  resolvedBotToken,
26368
26836
  rawBotToken,
26369
26837
  hindsightAutoRecallEnabled,
@@ -26396,7 +26864,7 @@ function buildWorkspaceContext(args) {
26396
26864
  ...INTERACTIVE_TUI_FLEET_DENY_TOOLS
26397
26865
  ]),
26398
26866
  permissionAllow,
26399
- defaultModeAcceptEdits: hasAllWildcard,
26867
+ defaultMode: resolvePermissionDefaultMode(agentConfig, switchroomConfig),
26400
26868
  memory: agentConfig.memory,
26401
26869
  model: agentConfig.model,
26402
26870
  mcpServers: agentConfig.mcp_servers ? filterMcpServers(agentConfig.mcp_servers) : agentConfig.mcp_servers,
@@ -26903,6 +27371,7 @@ This file is auto-maintained. Do not edit manually.
26903
27371
  }, created, skipped, 384);
26904
27372
  writeIfMissing(join10(agentDir, "telegram", "access.json"), () => buildAccessJson2(agentConfig, telegramConfig, topicId, userId), created, skipped, 384);
26905
27373
  reconcileConfiguredGroup(join10(agentDir, "telegram", "access.json"), agentConfig, telegramConfig);
27374
+ writeFileSyncIfChanged(join10(agentDir, "telegram", "people.json"), buildPeopleJson(switchroomConfig), 384);
26906
27375
  if (agentConfig.subagents) {
26907
27376
  const agentsDir2 = join10(agentDir, ".claude", "agents");
26908
27377
  mkdirSync10(agentsDir2, { recursive: true });
@@ -27312,7 +27781,7 @@ function buildSettingsHooksBlock(p) {
27312
27781
  hooks: [
27313
27782
  {
27314
27783
  type: "command",
27315
- command: wrap("hook:workspace-dynamic", `${useInjectOnChange ? `env SWITCHROOM_INJECT_ON_CHANGE=1 ` : ""}bash "${join10(DOCKER_BIN_PATH, "workspace-dynamic-hook.sh")}"`),
27784
+ command: wrap("hook:workspace-dynamic", `${useInjectOnChange ? "" : `env SWITCHROOM_INJECT_ON_CHANGE=0 `}bash "${join10(DOCKER_BIN_PATH, "workspace-dynamic-hook.sh")}"`),
27316
27785
  timeout: 5
27317
27786
  }
27318
27787
  ]
@@ -27662,11 +28131,7 @@ ${baseAppend}` : TELEGRAM_FORMATTING_FLOOR_CARD;
27662
28131
  settings.permissions = settings.permissions ?? {};
27663
28132
  settings.permissions.allow = desiredAllow;
27664
28133
  settings.permissions.deny = desiredDeny;
27665
- if (hasAllWildcard) {
27666
- settings.permissions.defaultMode = "acceptEdits";
27667
- } else {
27668
- delete settings.permissions.defaultMode;
27669
- }
28134
+ settings.permissions.defaultMode = resolvePermissionDefaultMode(agentConfig, switchroomConfig);
27670
28135
  const mcpServers = {};
27671
28136
  const hindsightEntry = getHindsightSettingsEntry(name, switchroomConfig);
27672
28137
  if (hindsightEntry) {
@@ -28129,6 +28594,11 @@ function reconcileConfiguredGroup(accessPath, agentConfig, telegramConfig) {
28129
28594
  renameSync4(tmp, accessPath);
28130
28595
  console.log(source_default.green(` registered supergroup ${forumChatId} in access.json ` + `(responds to all topics; requireMention=false)`));
28131
28596
  }
28597
+ function buildPeopleJson(switchroomConfig) {
28598
+ const entries = switchroomConfig ? resolvePersonEntries(switchroomConfig) : [];
28599
+ return JSON.stringify({ entries }, null, 2) + `
28600
+ `;
28601
+ }
28132
28602
  function buildAccessJson2(agentConfig, telegramConfig, resolvedTopicId, userId) {
28133
28603
  const allowFrom = userId ? [String(userId)] : [];
28134
28604
  if (allowFrom.length === 0) {
@@ -28470,7 +28940,7 @@ in doubt, shorter and plainer wins.
28470
28940
 
28471
28941
  Every turn that answers a user message ends with a user-visible \`reply\`
28472
28942
  \u2014 Telegram is all the user sees; your terminal output
28473
- never reaches them.`, SWITCHROOM_TELEGRAM_MCP_TOOLS, HINDSIGHT_MCP_TOOLS, AGENT_CONFIG_MCP_TOOLS, HOSTD_MCP_TOOLS, WEBKITE_MCP_TOOLS, LEGACY_SWITCHROOM_MCP_TOKENS, LEGACY_HOSTD_BLANKET_TOKENS, DEFAULT_READ_ONLY_PREAPPROVED_TOOLS, WEBKITE_FLEET_DENY_TOOLS, INTERACTIVE_TUI_FLEET_DENY_TOOLS, WEBKITE_BINARY_CONTAINER_PATH = "/usr/local/bin/webkite", SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-5", SWITCHROOM_DEFAULT_THINKING_EFFORT = "low", SWITCHROOM_OWNED_SETTINGS_KEYS, ALL_BUILTIN_TOOLS, DOCKER_TELEGRAM_PLUGIN_PATH = "/opt/switchroom/telegram-plugin", DOCKER_HOOKS_PATH, DOCKER_BUNDLED_HOOKS_PATH = "/opt/switchroom/hooks", DOCKER_BIN_PATH = "/opt/switchroom/bin", DOCKER_SECURITY_PLUGIN_PATH = "/opt/switchroom/security-plugin", DOCKER_CONFIG_PATH = "/state/config/switchroom.yaml", DOCKER_SWITCHROOM_CLI_PATH = "/usr/local/bin/switchroom", DOCKER_AUTH_BROKER_SOCKET = "/run/switchroom/auth-broker/sock", DOCKER_VAULT_BROKER_SOCKET = "/run/switchroom/broker/sock", DOCKER_AGENT_HOME = "/state/agent/home", INTEGRATION_MCP_RESOLVERS;
28943
+ never reaches them.`, SWITCHROOM_TELEGRAM_MCP_TOOLS, HINDSIGHT_MCP_TOOLS, AGENT_CONFIG_MCP_TOOLS, HOSTD_MCP_TOOLS, WEBKITE_MCP_TOOLS, LEGACY_SWITCHROOM_MCP_TOKENS, LEGACY_HOSTD_BLANKET_TOKENS, DEFAULT_READ_ONLY_PREAPPROVED_TOOLS, WEBKITE_FLEET_DENY_TOOLS, INTERACTIVE_TUI_FLEET_DENY_TOOLS, WEBKITE_BINARY_CONTAINER_PATH = "/usr/local/bin/webkite", SWITCHROOM_DEFAULT_MAIN_MODEL = "claude-sonnet-5", SWITCHROOM_DEFAULT_THINKING_EFFORT = "low", SWITCHROOM_OWNED_SETTINGS_KEYS, ALL_BUILTIN_TOOLS, HINDSIGHT_DEFAULT_RETAIN_EVERY_N_TURNS = 3, HINDSIGHT_DEFAULT_RETAIN_OVERLAP_TURNS = 1, VALID_SETTINGS_DEFAULT_MODES, SWITCHROOM_DEFAULT_PERMISSION_MODE = "acceptEdits", DOCKER_TELEGRAM_PLUGIN_PATH = "/opt/switchroom/telegram-plugin", DOCKER_HOOKS_PATH, DOCKER_BUNDLED_HOOKS_PATH = "/opt/switchroom/hooks", DOCKER_BIN_PATH = "/opt/switchroom/bin", DOCKER_SECURITY_PLUGIN_PATH = "/opt/switchroom/security-plugin", DOCKER_CONFIG_PATH = "/state/config/switchroom.yaml", DOCKER_SWITCHROOM_CLI_PATH = "/usr/local/bin/switchroom", DOCKER_AUTH_BROKER_SOCKET = "/run/switchroom/auth-broker/sock", DOCKER_VAULT_BROKER_SOCKET = "/run/switchroom/broker/sock", DOCKER_AGENT_HOME = "/state/agent/home", INTEGRATION_MCP_RESOLVERS;
28474
28944
  var init_scaffold = __esm(() => {
28475
28945
  init_source();
28476
28946
  init_cron_unit_name();
@@ -28585,6 +29055,12 @@ var init_scaffold = __esm(() => {
28585
29055
  "Agent",
28586
29056
  "ExitPlanMode"
28587
29057
  ];
29058
+ VALID_SETTINGS_DEFAULT_MODES = [
29059
+ "default",
29060
+ "acceptEdits",
29061
+ "plan",
29062
+ "bypassPermissions"
29063
+ ];
28588
29064
  DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
28589
29065
  INTEGRATION_MCP_RESOLVERS = [
28590
29066
  {
@@ -28829,6 +29305,7 @@ function describeAgents(config, litellmConfirmedAgents) {
28829
29305
  baseUrl: resolved.litellm?.base_url ?? config.litellm?.base_url,
28830
29306
  smallFastModel: resolved.litellm?.small_fast_model ?? config.litellm?.small_fast_model ?? "claude-haiku-4-5-20251001"
28831
29307
  },
29308
+ model: resolveMainModel(resolved.model),
28832
29309
  timezone: resolveTimezone(config, resolved, {
28833
29310
  onUtcFallback: () => {
28834
29311
  console.warn(` \u26a0 timezone: no explicit timezone set and server detection resolved to UTC ` + `for agent "${name}" \u2014 cron schedules and the per-turn time hint will ` + `run in UTC. Add \`switchroom.timezone: "Region/City"\` (e.g. ` + `"Australia/Melbourne") to switchroom.yaml to silence this warning.`);
@@ -29261,7 +29738,7 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
29261
29738
  const root = a.litellm.baseUrl.replace(/\/+$/, "");
29262
29739
  env2.SWITCHROOM_LITELLM = "1";
29263
29740
  env2.SWITCHROOM_LITELLM_BASE = root;
29264
- env2.ANTHROPIC_BASE_URL = `${root}/anthropic`;
29741
+ env2.ANTHROPIC_BASE_URL = isClaudeModel(a.model) ? `${root}/anthropic` : root;
29265
29742
  env2.ANTHROPIC_SMALL_FAST_MODEL = a.litellm.smallFastModel;
29266
29743
  }
29267
29744
  env2.SWITCHROOM_VOICE_ENGINE = voiceEngine;
@@ -29359,6 +29836,8 @@ var init_compose = __esm(() => {
29359
29836
  init_schema();
29360
29837
  init_cron_routing();
29361
29838
  init_tier_selector();
29839
+ init_scaffold();
29840
+ init_model_command();
29362
29841
  init_merge();
29363
29842
  init_timezone();
29364
29843
  init_peercred();
@@ -29686,7 +30165,7 @@ var init_write_compose = __esm(() => {
29686
30165
  });
29687
30166
 
29688
30167
  // src/agents/tmux.ts
29689
- import { execFileSync as execFileSync6 } from "node:child_process";
30168
+ import { execFileSync as execFileSync7 } from "node:child_process";
29690
30169
  function sendAgentInterrupt(opts) {
29691
30170
  const { agentName } = opts;
29692
30171
  const attempts = typeof opts.attempts === "number" && opts.attempts > 0 ? opts.attempts : 1;
@@ -29696,7 +30175,7 @@ function sendAgentInterrupt(opts) {
29696
30175
  let lastError = null;
29697
30176
  for (let i = 0;i < attempts; i++) {
29698
30177
  try {
29699
- execFileSync6("tmux", args, {
30178
+ execFileSync7("tmux", args, {
29700
30179
  timeout: 3000,
29701
30180
  stdio: ["ignore", "pipe", "pipe"]
29702
30181
  });
@@ -29737,7 +30216,7 @@ var init_compose_env = () => {};
29737
30216
  import { resolve as resolve13 } from "node:path";
29738
30217
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync6 } from "node:fs";
29739
30218
  import { homedir as homedir7 } from "node:os";
29740
- import { execFileSync as execFileSync7 } from "node:child_process";
30219
+ import { execFileSync as execFileSync8 } from "node:child_process";
29741
30220
  function resolveSwitchroomHome(explicit) {
29742
30221
  if (explicit && explicit.length > 0)
29743
30222
  return explicit;
@@ -29768,7 +30247,7 @@ function bringUpAgentService(opts) {
29768
30247
  const dockerBin = opts.dockerBin ?? "docker";
29769
30248
  const stdio = opts.stdio ?? "inherit";
29770
30249
  for (const svc of ["vault-broker", "approval-kernel", "switchroom-auth-broker"]) {
29771
- execFileSync7(dockerBin, [
30250
+ execFileSync8(dockerBin, [
29772
30251
  "compose",
29773
30252
  "-f",
29774
30253
  composePath,
@@ -29780,7 +30259,7 @@ function bringUpAgentService(opts) {
29780
30259
  svc
29781
30260
  ], { stdio });
29782
30261
  }
29783
- execFileSync7(dockerBin, [
30262
+ execFileSync8(dockerBin, [
29784
30263
  "compose",
29785
30264
  "-f",
29786
30265
  composePath,
@@ -29799,7 +30278,7 @@ var init_docker_fleet = __esm(() => {
29799
30278
  });
29800
30279
 
29801
30280
  // src/agents/singleton-reconcile.ts
29802
- import { execFileSync as execFileSync8 } from "node:child_process";
30281
+ import { execFileSync as execFileSync9 } from "node:child_process";
29803
30282
  import { readFileSync as readFileSync16 } from "node:fs";
29804
30283
  function resolveSingletonServices(voiceEngine) {
29805
30284
  const engine = voiceEngine ?? loadHostCapabilities()?.voice.engine ?? "cloud";
@@ -29825,7 +30304,7 @@ function readPinnedSingletonImages(composeText, services = SINGLETON_SERVICES) {
29825
30304
  }
29826
30305
  function defaultInspectImage(dockerBin, container) {
29827
30306
  try {
29828
- const out = execFileSync8(dockerBin, ["inspect", "-f", "{{.Config.Image}}", container], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000 }).trim();
30307
+ const out = execFileSync9(dockerBin, ["inspect", "-f", "{{.Config.Image}}", container], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000 }).trim();
29829
30308
  return out.length > 0 ? out : null;
29830
30309
  } catch {
29831
30310
  return null;
@@ -29851,7 +30330,7 @@ function detectSingletonDrift(deps) {
29851
30330
  });
29852
30331
  }
29853
30332
  function defaultRecreate(dockerBin, project, composeFile, service) {
29854
- execFileSync8(dockerBin, ["compose", "-p", project, "-f", composeFile, ...composeEnvFileArgs(composeFile), "up", "-d", "--no-deps", service], { stdio: ["ignore", "pipe", "pipe"], timeout: 120000 });
30333
+ execFileSync9(dockerBin, ["compose", "-p", project, "-f", composeFile, ...composeEnvFileArgs(composeFile), "up", "-d", "--no-deps", service], { stdio: ["ignore", "pipe", "pipe"], timeout: 120000 });
29855
30334
  }
29856
30335
  function reconcileSingletons(deps) {
29857
30336
  const dockerBin = deps.dockerBin ?? "docker";
@@ -29894,7 +30373,7 @@ var init_singleton_reconcile = __esm(() => {
29894
30373
  });
29895
30374
 
29896
30375
  // src/agents/lifecycle.ts
29897
- import { execFileSync as execFileSync9, spawn, spawnSync } from "node:child_process";
30376
+ import { execFileSync as execFileSync10, spawn, spawnSync } from "node:child_process";
29898
30377
  import { existsSync as existsSync20, mkdirSync as mkdirSync14, writeFileSync as writeFileSync7, renameSync as renameSync5, readFileSync as readFileSync17 } from "node:fs";
29899
30378
  import { resolve as resolve14, join as join14 } from "node:path";
29900
30379
  import { connect } from "node:net";
@@ -29925,7 +30404,7 @@ function buildCliRestartReason(opts) {
29925
30404
  if (!buildCommit)
29926
30405
  return "cli: restart";
29927
30406
  try {
29928
- const head = execFileSync9("git", ["rev-parse", "HEAD"], {
30407
+ const head = execFileSync10("git", ["rev-parse", "HEAD"], {
29929
30408
  cwd: cwd ?? process.cwd(),
29930
30409
  encoding: "utf-8",
29931
30410
  stdio: ["ignore", "pipe", "ignore"]
@@ -29936,7 +30415,7 @@ function buildCliRestartReason(opts) {
29936
30415
  return "cli: restart";
29937
30416
  let subject = "";
29938
30417
  try {
29939
- subject = execFileSync9("git", ["log", "-1", "--pretty=%s", head], {
30418
+ subject = execFileSync10("git", ["log", "-1", "--pretty=%s", head], {
29940
30419
  cwd: cwd ?? process.cwd(),
29941
30420
  encoding: "utf-8",
29942
30421
  stdio: ["ignore", "pipe", "ignore"]
@@ -29966,7 +30445,7 @@ function reconcileSingletonImages(log) {
29966
30445
  }
29967
30446
  function dockerSync(args) {
29968
30447
  try {
29969
- return execFileSync9("docker", args, {
30448
+ return execFileSync10("docker", args, {
29970
30449
  encoding: "utf-8",
29971
30450
  stdio: ["ignore", "pipe", "pipe"]
29972
30451
  }).trim();
@@ -30355,12 +30834,12 @@ var init_lifecycle = __esm(() => {
30355
30834
  });
30356
30835
 
30357
30836
  // src/auth/pane-ready-probe.ts
30358
- import { execFileSync as execFileSync11 } from "node:child_process";
30837
+ import { execFileSync as execFileSync12 } from "node:child_process";
30359
30838
  function defaultPaneReadyDeps() {
30360
30839
  return {
30361
30840
  capturePane(sessionName) {
30362
30841
  try {
30363
- return execFileSync11("tmux", ["capture-pane", "-p", "-t", sessionName, "-S", "-200"], {
30842
+ return execFileSync12("tmux", ["capture-pane", "-p", "-t", sessionName, "-S", "-200"], {
30364
30843
  encoding: "utf-8",
30365
30844
  stdio: ["pipe", "pipe", "pipe"]
30366
30845
  }).trim();
@@ -30641,7 +31120,7 @@ var init_accounts = __esm(() => {
30641
31120
  });
30642
31121
 
30643
31122
  // src/auth/manager.ts
30644
- import { execFileSync as execFileSync12 } from "node:child_process";
31123
+ import { execFileSync as execFileSync13 } from "node:child_process";
30645
31124
  import {
30646
31125
  readFileSync as readFileSync22,
30647
31126
  readdirSync as readdirSync11,
@@ -30692,14 +31171,14 @@ function authSessionName(name, slot) {
30692
31171
  return `${base}-${slot.replace(/[^a-zA-Z0-9_.-]/g, "-")}`;
30693
31172
  }
30694
31173
  function tmux(args) {
30695
- return execFileSync12("tmux", args, {
31174
+ return execFileSync13("tmux", args, {
30696
31175
  encoding: "utf-8",
30697
31176
  stdio: ["pipe", "pipe", "pipe"]
30698
31177
  }).trim();
30699
31178
  }
30700
31179
  function tmuxSessionExists(sessionName) {
30701
31180
  try {
30702
- execFileSync12("tmux", ["has-session", "-t", sessionName], {
31181
+ execFileSync13("tmux", ["has-session", "-t", sessionName], {
30703
31182
  stdio: ["pipe", "pipe", "pipe"]
30704
31183
  });
30705
31184
  return true;
@@ -31215,355 +31694,6 @@ function readQuarantineMarkerForAgent(agentsDir, name) {
31215
31694
  var QUARANTINE_FILENAME = "quarantine.json";
31216
31695
  var init_quarantine = () => {};
31217
31696
 
31218
- // src/agents/pane-lock.ts
31219
- function withPaneLock(key, fn) {
31220
- const tail = tails.get(key) ?? Promise.resolve();
31221
- const run = tail.then(() => fn());
31222
- const next = run.then(() => {}, () => {});
31223
- tails.set(key, next);
31224
- next.then(() => {
31225
- if (tails.get(key) === next)
31226
- tails.delete(key);
31227
- });
31228
- return run;
31229
- }
31230
- var tails;
31231
- var init_pane_lock = __esm(() => {
31232
- tails = new Map;
31233
- });
31234
-
31235
- // src/agents/inject.ts
31236
- var exports_inject = {};
31237
- __export(exports_inject, {
31238
- validateInjectCommand: () => validateInjectCommand,
31239
- normalizeInjectCommand: () => normalizeInjectCommand,
31240
- makeTmuxRunner: () => makeTmuxRunner,
31241
- isTuiChromeLine: () => isTuiChromeLine,
31242
- injectSlashCommandWith: () => injectSlashCommandWith,
31243
- injectSlashCommand: () => injectSlashCommand,
31244
- diffPane: () => diffPane,
31245
- dedupeInjectQueue: () => dedupeInjectQueue,
31246
- InjectError: () => InjectError,
31247
- INJECT_COMMANDS: () => INJECT_COMMANDS,
31248
- INJECT_BLOCKLIST: () => INJECT_BLOCKLIST,
31249
- INJECT_BLOCKED: () => INJECT_BLOCKED,
31250
- INJECT_ALLOWLIST: () => INJECT_ALLOWLIST
31251
- });
31252
- import { execFileSync as execFileSync14 } from "node:child_process";
31253
- function validateInjectCommand(command) {
31254
- if (typeof command !== "string" || command.trim().length === 0) {
31255
- throw new InjectError("invalid", "command is empty");
31256
- }
31257
- const trimmed = command.trim();
31258
- if (!trimmed.startsWith("/")) {
31259
- throw new InjectError("invalid", `command must start with "/": got ${trimmed}`);
31260
- }
31261
- const bare = trimmed.split(/\s+/, 1)[0].toLowerCase();
31262
- const blockedMeta = INJECT_BLOCKED.get(bare);
31263
- if (blockedMeta) {
31264
- throw new InjectError("blocked", `${bare} is explicitly blocked from inject (${blockedMeta.reason}).`);
31265
- }
31266
- if (!INJECT_COMMANDS.has(bare)) {
31267
- const allowed = [...INJECT_COMMANDS.keys()].sort().join(", ");
31268
- throw new InjectError("not_allowed", `${bare} is not in the inject allowlist. Allowed: ${allowed}`);
31269
- }
31270
- return bare;
31271
- }
31272
- function normalizeInjectCommand(command) {
31273
- return command.trim().replace(/\s+/g, " ").toLowerCase();
31274
- }
31275
- function dedupeInjectQueue(commands) {
31276
- const seen = new Set;
31277
- const out = [];
31278
- for (const raw of commands) {
31279
- if (typeof raw !== "string")
31280
- continue;
31281
- const key = normalizeInjectCommand(raw);
31282
- if (key.length === 0)
31283
- continue;
31284
- if (seen.has(key))
31285
- continue;
31286
- seen.add(key);
31287
- out.push(raw.trim());
31288
- }
31289
- return out;
31290
- }
31291
- function defaultSocketName(agentName) {
31292
- return `switchroom-${agentName}`;
31293
- }
31294
- function makeTmuxRunner(tmuxBin) {
31295
- return {
31296
- capture(socket, session) {
31297
- try {
31298
- return execFileSync14(tmuxBin, ["-L", socket, "capture-pane", "-p", "-t", session, "-S", "-200"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
31299
- } catch {
31300
- return null;
31301
- }
31302
- },
31303
- send(socket, session, args) {
31304
- const [subcmd, ...rest] = args;
31305
- const flagEnd = rest.findIndex((a) => !a.startsWith("-"));
31306
- const flagsBeforeKeys = flagEnd === -1 ? rest : rest.slice(0, flagEnd);
31307
- const keys = flagEnd === -1 ? [] : rest.slice(flagEnd);
31308
- execFileSync14(tmuxBin, ["-L", socket, subcmd, ...flagsBeforeKeys, "-t", session, ...keys], { stdio: ["pipe", "pipe", "pipe"] });
31309
- },
31310
- hasSession(socket, session) {
31311
- try {
31312
- execFileSync14(tmuxBin, ["-L", socket, "has-session", "-t", session], {
31313
- stdio: ["pipe", "pipe", "pipe"]
31314
- });
31315
- return true;
31316
- } catch {
31317
- return false;
31318
- }
31319
- }
31320
- };
31321
- }
31322
- function sleep2(ms) {
31323
- return new Promise((res) => setTimeout(res, ms));
31324
- }
31325
- function isTuiChromeLine(line) {
31326
- const t = line.trim();
31327
- if (t.length === 0)
31328
- return false;
31329
- if (/^[\s\-_\u2502\u2500\u256d\u256e\u2570\u256f\u250c\u2510\u2514\u2518|]+$/.test(t))
31330
- return true;
31331
- if (/^[\u256d\u256e\u2570\u256f\u2502\s]*[>)\u276f][\u256d\u256e\u2570\u256f\u2502\s]*$/.test(t))
31332
- return true;
31333
- if (/accept edits on/i.test(t))
31334
- return true;
31335
- if (/shift\+tab to cycle/i.test(t))
31336
- return true;
31337
- if (/for agents/i.test(t))
31338
- return true;
31339
- if (/\? for shortcuts/i.test(t))
31340
- return true;
31341
- if (/bypassing permissions/i.test(t))
31342
- return true;
31343
- if (t === "copy")
31344
- return true;
31345
- return false;
31346
- }
31347
- function diffPane(before, after, command) {
31348
- if (command) {
31349
- const escaped = command.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31350
- const lines = after.split(`
31351
- `);
31352
- let anchorIdx = -1;
31353
- for (let i = lines.length - 1;i >= 0; i--) {
31354
- if (/[\u276f>]\s+/.test(lines[i]) && lines[i].includes(escaped)) {
31355
- anchorIdx = i;
31356
- break;
31357
- }
31358
- }
31359
- if (anchorIdx >= 0) {
31360
- const tail = lines.slice(anchorIdx + 1);
31361
- const trimmed = [];
31362
- for (const raw of tail) {
31363
- const line = raw.trimEnd();
31364
- if (line.length === 0 && trimmed.length === 0)
31365
- continue;
31366
- if (isTuiChromeLine(line))
31367
- continue;
31368
- trimmed.push(line);
31369
- }
31370
- while (trimmed.length > 0 && trimmed[trimmed.length - 1].length === 0) {
31371
- trimmed.pop();
31372
- }
31373
- const affordances = /^(esc to cancel|press any key|\u21b5 select)/i;
31374
- while (trimmed.length > 0 && affordances.test(trimmed[trimmed.length - 1].trim())) {
31375
- trimmed.pop();
31376
- }
31377
- if (trimmed.length > 0) {
31378
- return { output: trimmed.join(`
31379
- `), anchored: true };
31380
- }
31381
- }
31382
- }
31383
- const beforeSet = new Set(before.split(`
31384
- `).map((l) => l.trimEnd()));
31385
- const newLines = [];
31386
- for (const raw of after.split(`
31387
- `)) {
31388
- const line = raw.trimEnd();
31389
- if (line.length === 0)
31390
- continue;
31391
- if (beforeSet.has(line))
31392
- continue;
31393
- if (isTuiChromeLine(line))
31394
- continue;
31395
- newLines.push(line);
31396
- }
31397
- return { output: newLines.join(`
31398
- `), anchored: false };
31399
- }
31400
- async function injectSlashCommand(agentName, command, opts = {}) {
31401
- validateInjectCommand(command);
31402
- const tmuxBin = opts.tmuxBin ?? "tmux";
31403
- const socket = opts.socketName ?? defaultSocketName(agentName);
31404
- const session = opts.sessionName ?? agentName;
31405
- const settleMs = opts.settleMs ?? 2000;
31406
- const timeoutMs = opts.timeoutMs ?? 5000;
31407
- return withPaneLock(`${socket}:${session}`, () => injectSlashCommandWith(makeTmuxRunner(tmuxBin), {
31408
- socket,
31409
- session,
31410
- command: command.trim(),
31411
- settleMs,
31412
- timeoutMs
31413
- }));
31414
- }
31415
- async function injectSlashCommandWith(runner, args) {
31416
- const { socket, session, command, settleMs, timeoutMs } = args;
31417
- let bareVerb;
31418
- try {
31419
- bareVerb = validateInjectCommand(command);
31420
- } catch (err) {
31421
- if (err instanceof InjectError) {
31422
- return {
31423
- outcome: "failed",
31424
- output: "",
31425
- truncated: false,
31426
- command: command.trim().split(/\s+/, 1)[0]?.toLowerCase() ?? "",
31427
- meta: null,
31428
- errorCode: err.code,
31429
- errorMessage: err.message
31430
- };
31431
- }
31432
- throw err;
31433
- }
31434
- const meta = INJECT_COMMANDS.get(bareVerb) ?? null;
31435
- if (!runner.hasSession(socket, session)) {
31436
- return {
31437
- outcome: "failed",
31438
- output: "",
31439
- truncated: false,
31440
- command: bareVerb,
31441
- meta,
31442
- errorCode: "session_missing",
31443
- errorMessage: `tmux session "${session}" on socket "${socket}" not found. ` + `Is the agent running under the tmux supervisor (the default)? ` + `If experimental.legacy_pty=true is set, inject is unsupported.`
31444
- };
31445
- }
31446
- const before = runner.capture(socket, session) ?? "";
31447
- try {
31448
- runner.send(socket, session, ["send-keys", "-l", command]);
31449
- runner.send(socket, session, ["send-keys", "Enter"]);
31450
- } catch (err) {
31451
- return {
31452
- outcome: "failed",
31453
- output: "",
31454
- truncated: false,
31455
- command: bareVerb,
31456
- meta,
31457
- errorCode: "tmux_failed",
31458
- errorMessage: `tmux send-keys failed: ${err instanceof Error ? err.message : String(err)}`
31459
- };
31460
- }
31461
- const start = Date.now();
31462
- let last = before;
31463
- let stableSince = null;
31464
- while (Date.now() - start < timeoutMs) {
31465
- await sleep2(POLL_INTERVAL_MS);
31466
- const cur = runner.capture(socket, session) ?? "";
31467
- if (cur === last && cur !== before) {
31468
- if (stableSince === null) {
31469
- stableSince = Date.now();
31470
- } else if (Date.now() - stableSince >= POLL_INTERVAL_MS) {
31471
- last = cur;
31472
- break;
31473
- }
31474
- } else {
31475
- stableSince = null;
31476
- }
31477
- last = cur;
31478
- if (Date.now() - start >= settleMs && cur !== before) {
31479
- break;
31480
- }
31481
- }
31482
- const { output: rawOutput, anchored } = diffPane(before, last, command);
31483
- let output = rawOutput;
31484
- let truncated = false;
31485
- const bytes = Buffer.byteLength(output, "utf-8");
31486
- if (bytes > OUTPUT_BYTE_CAP) {
31487
- while (Buffer.byteLength(output, "utf-8") > OUTPUT_BYTE_CAP) {
31488
- output = output.slice(Math.floor(output.length * 0.1) + 1);
31489
- }
31490
- truncated = true;
31491
- }
31492
- if (meta?.dialog) {
31493
- try {
31494
- runner.send(socket, session, ["send-keys", "Escape"]);
31495
- } catch {}
31496
- }
31497
- if (output.trim().length === 0) {
31498
- return {
31499
- outcome: "ok_no_output",
31500
- output: "",
31501
- truncated: false,
31502
- command: bareVerb,
31503
- meta,
31504
- ...anchored ? {} : { diagnostic: "anchor_missing" }
31505
- };
31506
- }
31507
- return {
31508
- outcome: "ok",
31509
- output,
31510
- truncated,
31511
- command: bareVerb,
31512
- meta,
31513
- ...truncated ? { diagnostic: "truncated_output" } : {}
31514
- };
31515
- }
31516
- var INJECT_COMMANDS, INJECT_ALLOWLIST, INJECT_BLOCKED, INJECT_BLOCKLIST, InjectError, POLL_INTERVAL_MS = 150, OUTPUT_BYTE_CAP = 3000;
31517
- var init_inject = __esm(() => {
31518
- init_pane_lock();
31519
- INJECT_COMMANDS = new Map([
31520
- ["/cost", { description: "Show session cost", expectsOutput: true, dialog: true }],
31521
- ["/status", { description: "Show session status", expectsOutput: true, dialog: true }],
31522
- ["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true }],
31523
- ["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true }],
31524
- ["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true }],
31525
- ["/model", { description: "Open model picker", expectsOutput: true }],
31526
- [
31527
- "/clear",
31528
- {
31529
- description: "Clear session screen",
31530
- expectsOutput: false,
31531
- silentNote: "context cleared \u2014 fresh slate"
31532
- }
31533
- ],
31534
- [
31535
- "/compact",
31536
- {
31537
- description: "Compact conversation history",
31538
- expectsOutput: false,
31539
- silentNote: "compaction runs silently"
31540
- }
31541
- ]
31542
- ]);
31543
- INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
31544
- INJECT_BLOCKED = new Map([
31545
- ["/login", { reason: "would mutate auth state" }],
31546
- ["/logout", { reason: "would terminate the agent's auth session" }],
31547
- ["/exit", { reason: "would kill the agent process" }],
31548
- ["/quit", { reason: "would kill the agent process" }],
31549
- [
31550
- "/effort",
31551
- {
31552
- reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
31553
- }
31554
- ]
31555
- ]);
31556
- INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
31557
- InjectError = class InjectError extends Error {
31558
- code;
31559
- constructor(code, message) {
31560
- super(message);
31561
- this.name = "InjectError";
31562
- this.code = code;
31563
- }
31564
- };
31565
- });
31566
-
31567
31697
  // src/auth/broker/protocol.ts
31568
31698
  function encodeRequest2(req) {
31569
31699
  const line = JSON.stringify(RequestSchema2.parse(req)) + `
@@ -35392,7 +35522,7 @@ function formatForCli(entries, opts = {}) {
35392
35522
  var init_audit_reader = () => {};
35393
35523
 
35394
35524
  // node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
35395
- import { dirname as dirname12, posix, sep as sep2 } from "path";
35525
+ import { dirname as dirname13, posix, sep as sep2 } from "path";
35396
35526
  function createModulerModifier() {
35397
35527
  const getModuleFromFileName = createGetModuleFromFilename();
35398
35528
  return async (frames) => {
@@ -35401,7 +35531,7 @@ function createModulerModifier() {
35401
35531
  return frames;
35402
35532
  };
35403
35533
  }
35404
- function createGetModuleFromFilename(basePath = process.argv[1] ? dirname12(process.argv[1]) : process.cwd(), isWindows = sep2 === "\\") {
35534
+ function createGetModuleFromFilename(basePath = process.argv[1] ? dirname13(process.argv[1]) : process.cwd(), isWindows = sep2 === "\\") {
35405
35535
  const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
35406
35536
  return (filename) => {
35407
35537
  if (!filename)
@@ -40017,7 +40147,7 @@ import {
40017
40147
  readFileSync as readFileSync43,
40018
40148
  writeFileSync as writeFileSync23
40019
40149
  } from "node:fs";
40020
- import { dirname as dirname13 } from "node:path";
40150
+ import { dirname as dirname14 } from "node:path";
40021
40151
  import { randomUUID as randomUUID3 } from "node:crypto";
40022
40152
  function telemetryDisabled() {
40023
40153
  const v = process.env.SWITCHROOM_TELEMETRY_DISABLED;
@@ -40039,7 +40169,7 @@ function getDistinctId() {
40039
40169
  const id = randomUUID3();
40040
40170
  cachedDistinctId = id;
40041
40171
  try {
40042
- mkdirSync26(dirname13(path4), { recursive: true });
40172
+ mkdirSync26(dirname14(path4), { recursive: true });
40043
40173
  writeFileSync23(path4, id, "utf-8");
40044
40174
  } catch {}
40045
40175
  return id;
@@ -40286,7 +40416,7 @@ import {
40286
40416
  readFileSync as readFileSync52,
40287
40417
  readdirSync as readdirSync19
40288
40418
  } from "node:fs";
40289
- import { dirname as dirname16, join as join50 } from "node:path";
40419
+ import { dirname as dirname17, join as join50 } from "node:path";
40290
40420
  import { execSync as execSync2 } from "node:child_process";
40291
40421
  function locateManifestPath() {
40292
40422
  let dir = import.meta.dirname;
@@ -40294,7 +40424,7 @@ function locateManifestPath() {
40294
40424
  const candidate = join50(dir, "dependencies.json");
40295
40425
  if (existsSync57(candidate))
40296
40426
  return candidate;
40297
- dir = dirname16(dir);
40427
+ dir = dirname17(dir);
40298
40428
  }
40299
40429
  return null;
40300
40430
  }
@@ -43465,7 +43595,7 @@ import {
43465
43595
  readdirSync as readdirSync21,
43466
43596
  statSync as statSync26
43467
43597
  } from "node:fs";
43468
- import { dirname as dirname17, join as join62, resolve as resolve37 } from "node:path";
43598
+ import { dirname as dirname18, join as join62, resolve as resolve37 } from "node:path";
43469
43599
  import { createPublicKey, createPrivateKey } from "node:crypto";
43470
43600
  function findInNvm(bin) {
43471
43601
  const nvmRoot = join62(process.env.HOME ?? "", ".nvm", "versions", "node");
@@ -44947,7 +45077,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
44947
45077
  detail: "skipped (MFF_API_URL not set)"
44948
45078
  };
44949
45079
  }
44950
- const credDir = dirname17(envPath);
45080
+ const credDir = dirname18(envPath);
44951
45081
  const authScript = join62(credDir, "claude-auth.py");
44952
45082
  if (!existsSync62(authScript)) {
44953
45083
  return {
@@ -48870,7 +49000,7 @@ __export(exports_voice_sidecar_token, {
48870
49000
  });
48871
49001
  import { randomBytes as randomBytes14 } from "node:crypto";
48872
49002
  import { chmodSync as chmodSync11, chownSync as chownSync7, existsSync as existsSync81, mkdirSync as mkdirSync45, readFileSync as readFileSync68, rmSync as rmSync17, writeFileSync as writeFileSync37 } from "node:fs";
48873
- import { dirname as dirname26 } from "node:path";
49003
+ import { dirname as dirname27 } from "node:path";
48874
49004
  async function defaultResolveOrSeedToken(home2, writeErr) {
48875
49005
  const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
48876
49006
  Promise.resolve().then(() => (init_client(), exports_client)),
@@ -48924,7 +49054,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
48924
49054
  if (!token)
48925
49055
  return;
48926
49056
  try {
48927
- mkdirSync45(dirname26(envPath), { recursive: true });
49057
+ mkdirSync45(dirname27(envPath), { recursive: true });
48928
49058
  let body = "";
48929
49059
  try {
48930
49060
  if (existsSync81(envPath))
@@ -48986,11 +49116,11 @@ __export(exports_apply, {
48986
49116
  DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
48987
49117
  COMPOSE_PROJECT: () => COMPOSE_PROJECT2
48988
49118
  });
48989
- 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";
49119
+ import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync82, mkdirSync as mkdirSync46, readFileSync as readFileSync69, readdirSync as readdirSync28, renameSync as renameSync18, writeFileSync as writeFileSync38 } from "node:fs";
48990
49120
  import { mkdir as mkdir2 } from "node:fs/promises";
48991
49121
  import { spawnSync as childSpawnSync } from "node:child_process";
48992
49122
  import readline from "node:readline";
48993
- import { dirname as dirname27, join as join80, resolve as resolve50 } from "node:path";
49123
+ import { dirname as dirname28, join as join80, resolve as resolve50 } from "node:path";
48994
49124
  import { homedir as homedir47 } from "node:os";
48995
49125
  import { execFileSync as execFileSync25 } from "node:child_process";
48996
49126
  function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
@@ -49235,7 +49365,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
49235
49365
  function resolveVaultBindMountDir(homeDir, ctx) {
49236
49366
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
49237
49367
  if (isCustomPath && ctx.customVaultPath) {
49238
- return dirname27(ctx.customVaultPath);
49368
+ return dirname28(ctx.customVaultPath);
49239
49369
  }
49240
49370
  return join80(homeDir, ".switchroom", "vault");
49241
49371
  }
@@ -49890,7 +50020,7 @@ function copyExampleConfig2(name) {
49890
50020
  if (!existsSync82(exampleFile)) {
49891
50021
  throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
49892
50022
  }
49893
- copyFileSync11(exampleFile, dest);
50023
+ copyFileSync12(exampleFile, dest);
49894
50024
  console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
49895
50025
  }
49896
50026
  function findUnwritableAgentDirs(config, opts) {
@@ -64437,7 +64567,7 @@ import {
64437
64567
  mkdirSync as mkdirSync53,
64438
64568
  writeFileSync as writeFileSync44
64439
64569
  } from "node:fs";
64440
- import { resolve as resolve55, dirname as dirname30 } from "node:path";
64570
+ import { resolve as resolve55, dirname as dirname31 } from "node:path";
64441
64571
  import { homedir as homedir56 } from "node:os";
64442
64572
  function resolveSwitchroomBase(home2 = process.env.SWITCHROOM_HOME ?? process.env.HOME ?? homedir56()) {
64443
64573
  return resolve55(home2, ".switchroom");
@@ -64528,11 +64658,11 @@ function readLedgerIfPresent(base) {
64528
64658
  }
64529
64659
  }
64530
64660
  function ledgerPathForBase(base) {
64531
- return fleetHealthLedgerPath(dirname30(base));
64661
+ return fleetHealthLedgerPath(dirname31(base));
64532
64662
  }
64533
64663
  function writeLedger(base, ledger) {
64534
64664
  const path8 = ledgerPathForBase(base);
64535
- mkdirSync53(dirname30(path8), { recursive: true });
64665
+ mkdirSync53(dirname31(path8), { recursive: true });
64536
64666
  writeFileSync44(path8, JSON.stringify(ledger, null, 2) + `
64537
64667
  `, "utf-8");
64538
64668
  return path8;
@@ -64552,8 +64682,8 @@ import { existsSync, readFileSync } from "node:fs";
64552
64682
  import { dirname, join } from "node:path";
64553
64683
 
64554
64684
  // src/build-info.ts
64555
- var VERSION = "0.17.10";
64556
- var COMMIT_SHA = "d38372bf";
64685
+ var VERSION = "0.18.6";
64686
+ var COMMIT_SHA = "b62d558a";
64557
64687
 
64558
64688
  // src/cli/resolve-version.ts
64559
64689
  function readPackageVersion() {
@@ -64767,11 +64897,11 @@ function detectInFlight(opts) {
64767
64897
  async function waitUntilIdle(opts) {
64768
64898
  const pollMs = opts.pollMs ?? 2000;
64769
64899
  const clock = opts.now ?? Date.now;
64770
- const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
64900
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
64771
64901
  const deadline = clock() + opts.timeoutMs;
64772
64902
  let last = detectInFlight(opts);
64773
64903
  while (last.busy && clock() < deadline) {
64774
- await sleep(pollMs);
64904
+ await sleep2(pollMs);
64775
64905
  last = detectInFlight(opts);
64776
64906
  }
64777
64907
  return last;
@@ -64883,7 +65013,7 @@ function spinner(message) {
64883
65013
  // src/agents/status.ts
64884
65014
  import { existsSync as existsSync23, readFileSync as readFileSync20, statSync as statSync11 } from "node:fs";
64885
65015
  import { join as join16 } from "node:path";
64886
- import { execFileSync as execFileSync10 } from "node:child_process";
65016
+ import { execFileSync as execFileSync11 } from "node:child_process";
64887
65017
 
64888
65018
  // src/agents/handoff-summarizer.ts
64889
65019
  import { readFileSync as readFileSync18, writeFileSync as writeFileSync8, renameSync as renameSync6, mkdirSync as mkdirSync15, existsSync as existsSync21, statSync as statSync10, readdirSync as readdirSync9 } from "node:fs";
@@ -65468,7 +65598,7 @@ async function buildAgentStatusReport(inputs) {
65468
65598
  async function waitForAgentReady(inputs, options = {}) {
65469
65599
  const timeoutMs = options.timeoutMs ?? 30000;
65470
65600
  const pollIntervalMs = options.pollIntervalMs ?? 750;
65471
- const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
65601
+ const sleep2 = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
65472
65602
  const now = options.now ?? Date.now;
65473
65603
  const startedAt = now();
65474
65604
  const deadline = startedAt + timeoutMs;
@@ -65483,7 +65613,7 @@ async function waitForAgentReady(inputs, options = {}) {
65483
65613
  notReady
65484
65614
  };
65485
65615
  }
65486
- await sleep(pollIntervalMs);
65616
+ await sleep2(pollIntervalMs);
65487
65617
  report = await buildAgentStatusReport(inputs);
65488
65618
  notReady = readinessGaps(report);
65489
65619
  }
@@ -65560,14 +65690,14 @@ function readLastMessages(historyDbPath) {
65560
65690
  };
65561
65691
  }
65562
65692
  try {
65563
- const out = execFileSync10("bun", [
65693
+ const out = execFileSync11("bun", [
65564
65694
  "-e",
65565
65695
  `const { Database } = require("bun:sqlite"); ` + `const db = new Database(${JSON.stringify(historyDbPath)}, { readonly: true }); ` + `const rows = db.prepare("SELECT role, MAX(ts) as ts FROM messages GROUP BY role").all(); ` + `for (const r of rows) console.log(r.role + "|" + r.ts); ` + `db.close();`
65566
65696
  ], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
65567
65697
  return parseSqliteRoleTsOutput(out);
65568
65698
  } catch {}
65569
65699
  try {
65570
- const out = execFileSync10("sqlite3", [
65700
+ const out = execFileSync11("sqlite3", [
65571
65701
  historyDbPath,
65572
65702
  "SELECT role, MAX(ts) FROM messages GROUP BY role;"
65573
65703
  ], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
@@ -65679,7 +65809,7 @@ function escapeRegex(s) {
65679
65809
  }
65680
65810
  function readDockerContainer(containerName2) {
65681
65811
  try {
65682
- const out = execFileSync10("docker", ["inspect", "--format", "{{json .State}}", containerName2], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
65812
+ const out = execFileSync11("docker", ["inspect", "--format", "{{json .State}}", containerName2], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
65683
65813
  const state = JSON.parse(out.trim());
65684
65814
  const active = state.Status === "running" ? "active" : state.Status === "restarting" ? "restarting" : "inactive";
65685
65815
  const pid = typeof state.Pid === "number" && state.Pid > 0 ? state.Pid : null;
@@ -65794,7 +65924,7 @@ async function pollForDmStart(token, timeoutMs = 120000) {
65794
65924
  const response = await fetch(url);
65795
65925
  data = await response.json();
65796
65926
  } catch {
65797
- await sleep(2000);
65927
+ await sleep2(2000);
65798
65928
  continue;
65799
65929
  }
65800
65930
  if (data.ok && Array.isArray(data.result)) {
@@ -65810,11 +65940,11 @@ async function pollForDmStart(token, timeoutMs = 120000) {
65810
65940
  }
65811
65941
  }
65812
65942
  }
65813
- await sleep(2000);
65943
+ await sleep2(2000);
65814
65944
  }
65815
65945
  throw new Error("Timed out waiting for /start DM");
65816
65946
  }
65817
- function sleep(ms) {
65947
+ function sleep2(ms) {
65818
65948
  return new Promise((resolve17) => setTimeout(resolve17, ms));
65819
65949
  }
65820
65950
 
@@ -65958,7 +66088,7 @@ async function completeCreation(name, code, opts = {}) {
65958
66088
  // src/agents/add-orchestrator.ts
65959
66089
  import { resolve as resolve19, join as join19 } from "node:path";
65960
66090
  import { existsSync as existsSync28, rmSync as rmSync8, statSync as statSync15 } from "node:fs";
65961
- import { execFileSync as execFileSync13 } from "node:child_process";
66091
+ import { execFileSync as execFileSync14 } from "node:child_process";
65962
66092
  init_onboarding();
65963
66093
 
65964
66094
  // src/setup/botfather-walkthrough.ts
@@ -66532,7 +66662,7 @@ function pruneBundledSkills(agentDir, keep, scope) {
66532
66662
  }
66533
66663
  function defaultIsUnitActive(unitName) {
66534
66664
  try {
66535
- const out = execFileSync13("systemctl", ["--user", "is-active", unitName], {
66665
+ const out = execFileSync14("systemctl", ["--user", "is-active", unitName], {
66536
66666
  encoding: "utf-8",
66537
66667
  stdio: ["ignore", "pipe", "pipe"]
66538
66668
  }).trim();
@@ -71386,7 +71516,7 @@ init_acl();
71386
71516
  init_protocol();
71387
71517
  import * as net3 from "node:net";
71388
71518
  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";
71389
- import { dirname as dirname11, resolve as resolve28, basename as basename7 } from "node:path";
71519
+ import { dirname as dirname12, resolve as resolve28, basename as basename7 } from "node:path";
71390
71520
  import * as os4 from "node:os";
71391
71521
  import * as path3 from "node:path";
71392
71522
 
@@ -71479,10 +71609,38 @@ function rotateAuditLog(logPath, maxFiles) {
71479
71609
  return;
71480
71610
  }
71481
71611
  }
71612
+ const snapshotPath = `${logPath}.1`;
71482
71613
  try {
71483
- fs2.renameSync(logPath, `${logPath}.1`);
71614
+ fs2.copyFileSync(logPath, snapshotPath);
71484
71615
  } catch (err) {
71485
- process.stderr.write(`[vault-audit] ERROR: could not rotate active audit log ${logPath}: ${err.message}
71616
+ process.stderr.write(`[vault-audit] ERROR: could not snapshot active audit log ${logPath} \u2192 ${snapshotPath}: ${err.message}
71617
+ `);
71618
+ return;
71619
+ }
71620
+ try {
71621
+ const fd = fs2.openSync(snapshotPath, "r");
71622
+ try {
71623
+ fs2.fsyncSync(fd);
71624
+ } finally {
71625
+ fs2.closeSync(fd);
71626
+ }
71627
+ } catch (err) {
71628
+ process.stderr.write(`[vault-audit] ERROR: could not fsync audit snapshot ${snapshotPath}; leaving active log intact to avoid data loss: ${err.message}
71629
+ `);
71630
+ return;
71631
+ }
71632
+ try {
71633
+ const dirFd = fs2.openSync(path.dirname(snapshotPath), "r");
71634
+ try {
71635
+ fs2.fsyncSync(dirFd);
71636
+ } finally {
71637
+ fs2.closeSync(dirFd);
71638
+ }
71639
+ } catch {}
71640
+ try {
71641
+ fs2.truncateSync(logPath, 0);
71642
+ } catch (err) {
71643
+ process.stderr.write(`[vault-audit] ERROR: could not truncate active audit log ${logPath}: ${err.message}
71486
71644
  `);
71487
71645
  }
71488
71646
  }
@@ -73891,7 +74049,7 @@ class VaultBroker {
73891
74049
  this.passphrase = this.testOpts._testPassphrase;
73892
74050
  }
73893
74051
  process.umask(63);
73894
- const parentDir = dirname11(this.socketPath);
74052
+ const parentDir = dirname12(this.socketPath);
73895
74053
  mkdirSync24(parentDir, { recursive: true, mode: 448 });
73896
74054
  try {
73897
74055
  chmodSync7(parentDir, 448);
@@ -74986,7 +75144,8 @@ class VaultBroker {
74986
75144
  return;
74987
75145
  }
74988
75146
  try {
74989
- const tokenDir = path3.join(os4.homedir(), ".switchroom", "agents", agent);
75147
+ const agentsDir = this.config ? resolveAgentsDir(this.config) : path3.join(os4.homedir(), ".switchroom", "agents");
75148
+ const tokenDir = path3.join(agentsDir, agent);
74990
75149
  mkdirSync24(tokenDir, { recursive: true });
74991
75150
  const tokenPath = path3.join(tokenDir, ".vault-token");
74992
75151
  const tmpPath = `${tokenPath}.tmp.${process.pid}`;
@@ -75050,7 +75209,8 @@ class VaultBroker {
75050
75209
  try {
75051
75210
  const row = this.grantsDb.query("SELECT agent_slug FROM vault_grants WHERE id = ?").get(id);
75052
75211
  if (row && AgentNameSchema.safeParse(row.agent_slug).success) {
75053
- const tokenPath = path3.join(os4.homedir(), ".switchroom", "agents", row.agent_slug, ".vault-token");
75212
+ const agentsDir = this.config ? resolveAgentsDir(this.config) : path3.join(os4.homedir(), ".switchroom", "agents");
75213
+ const tokenPath = path3.join(agentsDir, row.agent_slug, ".vault-token");
75054
75214
  if (existsSync41(tokenPath)) {
75055
75215
  try {
75056
75216
  unlinkSync10(tokenPath);
@@ -75370,15 +75530,15 @@ class VaultBroker {
75370
75530
  }
75371
75531
  }
75372
75532
  function detectVaultLayoutDrift(vaultPath) {
75373
- const dir = dirname11(vaultPath);
75533
+ const dir = dirname12(vaultPath);
75374
75534
  if (basename7(dir) !== "vault")
75375
75535
  return;
75376
75536
  if (basename7(vaultPath) !== "vault.enc")
75377
75537
  return;
75378
- const switchroomDir = dirname11(dir);
75538
+ const switchroomDir = dirname12(dir);
75379
75539
  if (basename7(switchroomDir) !== ".switchroom")
75380
75540
  return;
75381
- const home2 = dirname11(switchroomDir);
75541
+ const home2 = dirname12(switchroomDir);
75382
75542
  const result = inspectVaultLayout(home2);
75383
75543
  if (result.kind === "divergent") {
75384
75544
  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.`);
@@ -76410,7 +76570,7 @@ import {
76410
76570
  appendFileSync as appendFileSync3,
76411
76571
  closeSync as closeSync10,
76412
76572
  existsSync as existsSync45,
76413
- fsyncSync as fsyncSync6,
76573
+ fsyncSync as fsyncSync7,
76414
76574
  mkdirSync as mkdirSync25,
76415
76575
  openSync as openSync10,
76416
76576
  readdirSync as readdirSync18,
@@ -76557,7 +76717,7 @@ function backupVault(opts) {
76557
76717
  const fd = openSync10(tmpPath, "wx", 384);
76558
76718
  try {
76559
76719
  writeSync7(fd, src);
76560
- fsyncSync6(fd);
76720
+ fsyncSync7(fd);
76561
76721
  } finally {
76562
76722
  closeSync10(fd);
76563
76723
  }
@@ -76588,7 +76748,7 @@ function backupVault(opts) {
76588
76748
  try {
76589
76749
  const dirFd = openSync10(opts.destDir, "r");
76590
76750
  try {
76591
- fsyncSync6(dirFd);
76751
+ fsyncSync7(dirFd);
76592
76752
  } finally {
76593
76753
  closeSync10(dirFd);
76594
76754
  }
@@ -76604,7 +76764,7 @@ function backupVault(opts) {
76604
76764
  try {
76605
76765
  const dirFd = openSync10(opts.destDir, "r");
76606
76766
  try {
76607
- fsyncSync6(dirFd);
76767
+ fsyncSync7(dirFd);
76608
76768
  } finally {
76609
76769
  closeSync10(dirFd);
76610
76770
  }
@@ -78288,7 +78448,11 @@ async function resolveLiteLLMForHindsight(config) {
78288
78448
  const result = await getViaBrokerStructured("litellm/hindsight/api-key").catch(() => null);
78289
78449
  if (!result || result.kind !== "ok" || result.entry.kind !== "string")
78290
78450
  return;
78291
- return { baseUrl: topLiteLLM.base_url, apiKey: result.entry.value };
78451
+ return {
78452
+ baseUrl: topLiteLLM.base_url,
78453
+ apiKey: result.entry.value,
78454
+ model: config.memory?.config?.llm_model
78455
+ };
78292
78456
  }
78293
78457
  function readRecallLog(agentDir, limit) {
78294
78458
  const path4 = join40(agentDir, ".claude", "plugins", "data", "hindsight-memory-inline", "state", "recall_log.jsonl");
@@ -78552,8 +78716,9 @@ Cross-agent reflection plan
78552
78716
  const provider = "claude-code";
78553
78717
  console.log(source_default.gray(" Starting Hindsight Docker container..."));
78554
78718
  try {
78555
- const litellmCfg = await resolveLiteLLMForHindsight(getConfig(program3));
78556
- startHindsight(ports, litellmCfg, opts.tag);
78719
+ const hindsightConfig = getConfig(program3);
78720
+ const litellmCfg = await resolveLiteLLMForHindsight(hindsightConfig);
78721
+ startHindsight(ports, litellmCfg, opts.tag, hindsightConfig.hindsight?.llm);
78557
78722
  if (litellmCfg) {
78558
78723
  console.log(source_default.gray(" LiteLLM routing enabled for hindsight (--network host)."));
78559
78724
  }
@@ -78599,7 +78764,7 @@ Cross-agent reflection plan
78599
78764
  console.log(source_default.bold(`
78600
78765
  # Add this to your docker-compose.yml:
78601
78766
  `));
78602
- console.log(generateHindsightComposeSnippet());
78767
+ console.log(generateHindsightComposeSnippet(getConfig(program3).hindsight?.llm));
78603
78768
  console.log();
78604
78769
  });
78605
78770
  memory.command("recall-log [agent]").description("Show recent auto-recall events (per-turn JSONL log) \u2014 see what was injected, when the cap fired, hit rate hints").option("-n, --limit <n>", "Tail the last N events per agent (default 20)", "20").option("--json", "Emit raw JSONL (one entry per line)").action(withConfigError(async (agent, opts) => {
@@ -78769,7 +78934,7 @@ import {
78769
78934
  writeSync as writeSync8,
78770
78935
  constants as fsConstants3
78771
78936
  } from "node:fs";
78772
- import { resolve as resolve32, extname, join as join49, relative, dirname as dirname14 } from "node:path";
78937
+ import { resolve as resolve32, extname, join as join49, relative, dirname as dirname15 } from "node:path";
78773
78938
  import { homedir as homedir27 } from "node:os";
78774
78939
  import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
78775
78940
 
@@ -82045,7 +82210,7 @@ function resolveWebToken() {
82045
82210
  return existing;
82046
82211
  }
82047
82212
  const token = randomBytes11(32).toString("hex");
82048
- mkdirSync30(dirname14(tokenPath), { recursive: true, mode: 448 });
82213
+ mkdirSync30(dirname15(tokenPath), { recursive: true, mode: 448 });
82049
82214
  try {
82050
82215
  const fd = openSync11(tokenPath, fsConstants3.O_WRONLY | fsConstants3.O_CREAT | fsConstants3.O_EXCL, 384);
82051
82216
  try {
@@ -82803,8 +82968,8 @@ Starting Switchroom dashboard...
82803
82968
  init_source();
82804
82969
  init_loader();
82805
82970
  init_scaffold();
82806
- import { existsSync as existsSync56, copyFileSync as copyFileSync8, readFileSync as readFileSync51, writeFileSync as writeFileSync26, mkdirSync as mkdirSync31 } from "node:fs";
82807
- import { resolve as resolve33, dirname as dirname15 } from "node:path";
82971
+ import { existsSync as existsSync56, copyFileSync as copyFileSync9, readFileSync as readFileSync51, writeFileSync as writeFileSync26, mkdirSync as mkdirSync31 } from "node:fs";
82972
+ import { resolve as resolve33, dirname as dirname16 } from "node:path";
82808
82973
  init_state();
82809
82974
  init_vault();
82810
82975
  init_manager();
@@ -83109,8 +83274,8 @@ async function copyExampleConfig(nonInteractive) {
83109
83274
  if (!existsSync56(srcFile)) {
83110
83275
  throw new ConfigError(`Example config not found: ${choice}.yaml`);
83111
83276
  }
83112
- mkdirSync31(dirname15(destFile), { recursive: true });
83113
- copyFileSync8(srcFile, destFile);
83277
+ mkdirSync31(dirname16(destFile), { recursive: true });
83278
+ copyFileSync9(srcFile, destFile);
83114
83279
  console.log(source_default.green(` Copied ${choice}.yaml -> ${destFile}`));
83115
83280
  console.log(source_default.yellow(` Edit ${destFile} to customize, then re-run switchroom setup.`));
83116
83281
  await writeDetectedTimezone(destFile, nonInteractive);
@@ -83403,7 +83568,11 @@ async function resolveLiteLLMForHindsight2(config) {
83403
83568
  const result = await getViaBrokerStructured("litellm/hindsight/api-key").catch(() => null);
83404
83569
  if (!result || result.kind !== "ok" || result.entry.kind !== "string")
83405
83570
  return;
83406
- return { baseUrl: topLiteLLM.base_url, apiKey: result.entry.value };
83571
+ return {
83572
+ baseUrl: topLiteLLM.base_url,
83573
+ apiKey: result.entry.value,
83574
+ model: config.memory?.config?.llm_model
83575
+ };
83407
83576
  }
83408
83577
  async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath) {
83409
83578
  stepHeader(6, "Memory backend", STEP_ACTIVE);
@@ -83495,7 +83664,7 @@ async function stepMemoryBackend(config, nonInteractive, switchroomConfigPath) {
83495
83664
  const spin = spinner("Starting Hindsight Docker container...");
83496
83665
  try {
83497
83666
  const litellmCfg = await resolveLiteLLMForHindsight2(config);
83498
- startHindsight(ports, litellmCfg);
83667
+ startHindsight(ports, litellmCfg, undefined, config.hindsight?.llm);
83499
83668
  if (litellmCfg) {
83500
83669
  console.log(source_default.gray(" LiteLLM routing enabled (--network host, ANTHROPIC_BASE_URL set)."));
83501
83670
  }
@@ -83888,7 +84057,7 @@ init_lifecycle();
83888
84057
  init_compose_env();
83889
84058
  import { cpSync as cpSync2, existsSync as existsSync63, mkdirSync as mkdirSync33, readFileSync as readFileSync56, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync28, chownSync as chownSync5 } from "node:fs";
83890
84059
  import { spawnSync as spawnSync12 } from "node:child_process";
83891
- import { join as join63, dirname as dirname18, resolve as resolve38 } from "node:path";
84060
+ import { join as join63, dirname as dirname19, resolve as resolve38 } from "node:path";
83892
84061
  import { homedir as homedir38 } from "node:os";
83893
84062
 
83894
84063
  // src/cli/release-yaml.ts
@@ -83997,7 +84166,7 @@ function defaultPersistPin(configPath) {
83997
84166
  }
83998
84167
  var DEFAULT_COMPOSE_PATH = join63(homedir38(), ".switchroom", "compose", "docker-compose.yml");
83999
84168
  function runningFromSwitchroomCheckout(scriptPath) {
84000
- let dir = dirname18(scriptPath);
84169
+ let dir = dirname19(scriptPath);
84001
84170
  for (let i = 0;i < 12; i++) {
84002
84171
  if (existsSync63(join63(dir, ".git"))) {
84003
84172
  try {
@@ -84006,7 +84175,7 @@ function runningFromSwitchroomCheckout(scriptPath) {
84006
84175
  return true;
84007
84176
  } catch {}
84008
84177
  }
84009
- const parent = dirname18(dir);
84178
+ const parent = dirname19(dir);
84010
84179
  if (parent === dir)
84011
84180
  break;
84012
84181
  dir = parent;
@@ -84222,7 +84391,7 @@ function planUpdate(opts) {
84222
84391
  if (existsSync63(dest)) {
84223
84392
  rmSync12(dest, { recursive: true, force: true });
84224
84393
  }
84225
- mkdirSync33(dirname18(dest), { recursive: true });
84394
+ mkdirSync33(dirname19(dest), { recursive: true });
84226
84395
  cpSync2(source, dest, { recursive: true, dereference: false });
84227
84396
  } catch (err) {
84228
84397
  throw new Error(`sync-bundled-skills failed: ${err.message}`);
@@ -84335,7 +84504,7 @@ function defaultStatusProbe(composePath) {
84335
84504
  try {
84336
84505
  cliBuiltAt = new Date(statSync28(scriptPath).mtimeMs).toISOString();
84337
84506
  } catch {}
84338
- let dir = dirname18(scriptPath);
84507
+ let dir = dirname19(scriptPath);
84339
84508
  for (let i = 0;i < 8; i++) {
84340
84509
  const pkgPath = join63(dir, "package.json");
84341
84510
  if (existsSync63(pkgPath)) {
@@ -84348,7 +84517,7 @@ function defaultStatusProbe(composePath) {
84348
84517
  }
84349
84518
  break;
84350
84519
  }
84351
- const parent = dirname18(dir);
84520
+ const parent = dirname19(dir);
84352
84521
  if (parent === dir)
84353
84522
  break;
84354
84523
  dir = parent;
@@ -84979,7 +85148,7 @@ init_helpers();
84979
85148
  init_lifecycle();
84980
85149
  import { execSync as execSync4 } from "node:child_process";
84981
85150
  import { existsSync as existsSync64, readFileSync as readFileSync58 } from "node:fs";
84982
- import { dirname as dirname19, join as join64 } from "node:path";
85151
+ import { dirname as dirname20, join as join64 } from "node:path";
84983
85152
  function getClaudeCodeVersion() {
84984
85153
  try {
84985
85154
  const out = execSync4("claude --version 2>/dev/null", {
@@ -85038,7 +85207,7 @@ function locateSwitchroomInstallDir() {
85038
85207
  }
85039
85208
  } catch {}
85040
85209
  }
85041
- dir = dirname19(dir);
85210
+ dir = dirname20(dir);
85042
85211
  }
85043
85212
  return null;
85044
85213
  }
@@ -86239,7 +86408,7 @@ import {
86239
86408
  rmSync as rmSync13,
86240
86409
  writeFileSync as writeFileSync28
86241
86410
  } from "node:fs";
86242
- import { dirname as dirname20, join as join67 } from "node:path";
86411
+ import { dirname as dirname21, join as join67 } from "node:path";
86243
86412
  import { homedir as homedir40 } from "node:os";
86244
86413
  import { execFileSync as execFileSync19 } from "node:child_process";
86245
86414
 
@@ -86286,7 +86455,7 @@ function ensurePythonEnv(opts) {
86286
86455
  if (existsSync67(venvDir)) {
86287
86456
  rmSync13(venvDir, { recursive: true, force: true });
86288
86457
  }
86289
- mkdirSync35(dirname20(venvDir), { recursive: true });
86458
+ mkdirSync35(dirname21(venvDir), { recursive: true });
86290
86459
  try {
86291
86460
  execFileSync19(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
86292
86461
  } catch (err) {
@@ -86320,14 +86489,14 @@ function ensurePythonEnv(opts) {
86320
86489
  // src/deps/node.ts
86321
86490
  import { createHash as createHash13 } from "node:crypto";
86322
86491
  import {
86323
- copyFileSync as copyFileSync9,
86492
+ copyFileSync as copyFileSync10,
86324
86493
  existsSync as existsSync68,
86325
86494
  mkdirSync as mkdirSync36,
86326
86495
  readFileSync as readFileSync61,
86327
86496
  rmSync as rmSync14,
86328
86497
  writeFileSync as writeFileSync29
86329
86498
  } from "node:fs";
86330
- import { dirname as dirname21, join as join68 } from "node:path";
86499
+ import { dirname as dirname22, join as join68 } from "node:path";
86331
86500
  import { homedir as homedir41 } from "node:os";
86332
86501
  import { execFileSync as execFileSync20 } from "node:child_process";
86333
86502
 
@@ -86354,7 +86523,7 @@ function defaultNodeCacheRoot() {
86354
86523
  return join68(homedir41(), ".switchroom", "deps", "node");
86355
86524
  }
86356
86525
  function hashDepInputs(packageJsonPath) {
86357
- const sourceDir = dirname21(packageJsonPath);
86526
+ const sourceDir = dirname22(packageJsonPath);
86358
86527
  const hasher = createHash13("sha256");
86359
86528
  hasher.update(`package.json
86360
86529
  `);
@@ -86379,7 +86548,7 @@ function ensureNodeEnv(opts) {
86379
86548
  if (!existsSync68(packageJsonPath)) {
86380
86549
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
86381
86550
  }
86382
- const sourceDir = dirname21(packageJsonPath);
86551
+ const sourceDir = dirname22(packageJsonPath);
86383
86552
  const envDir = join68(cacheRoot, skillName);
86384
86553
  const stampPath = join68(envDir, ".package.sha256");
86385
86554
  const nodeModulesDir = join68(envDir, "node_modules");
@@ -86401,12 +86570,12 @@ function ensureNodeEnv(opts) {
86401
86570
  rmSync14(envDir, { recursive: true, force: true });
86402
86571
  }
86403
86572
  mkdirSync36(envDir, { recursive: true });
86404
- copyFileSync9(packageJsonPath, join68(envDir, "package.json"));
86573
+ copyFileSync10(packageJsonPath, join68(envDir, "package.json"));
86405
86574
  let copiedLockfile = false;
86406
86575
  for (const lockName of LOCKFILES_FOR[installer]) {
86407
86576
  const lockPath = join68(sourceDir, lockName);
86408
86577
  if (existsSync68(lockPath)) {
86409
- copyFileSync9(lockPath, join68(envDir, lockName));
86578
+ copyFileSync10(lockPath, join68(envDir, lockName));
86410
86579
  copiedLockfile = true;
86411
86580
  }
86412
86581
  }
@@ -87405,7 +87574,7 @@ function safeParseInt(value, fallback) {
87405
87574
  init_helpers();
87406
87575
  init_loader();
87407
87576
  init_merge();
87408
- import { copyFileSync as copyFileSync10, existsSync as existsSync71, readFileSync as readFileSync62, writeFileSync as writeFileSync30 } from "node:fs";
87577
+ import { copyFileSync as copyFileSync11, existsSync as existsSync71, readFileSync as readFileSync62, writeFileSync as writeFileSync30 } from "node:fs";
87409
87578
  import { join as join70, resolve as resolve44 } from "node:path";
87410
87579
  init_scaffold();
87411
87580
  init_profiles();
@@ -87482,7 +87651,7 @@ function registerSoulCommand(program3) {
87482
87651
  if (existsSync71(backupPath)) {
87483
87652
  backupPath = `${t.soulPath}.bak.${Date.now()}`;
87484
87653
  }
87485
- copyFileSync10(t.soulPath, backupPath);
87654
+ copyFileSync11(t.soulPath, backupPath);
87486
87655
  }
87487
87656
  writeFileSync30(t.soulPath, content, "utf-8");
87488
87657
  if (backupPath) {
@@ -88968,7 +89137,7 @@ function registerDriveMcpLauncherCommand(program3) {
88968
89137
  init_scaffold_integration();
88969
89138
  import { spawn as spawn5 } from "node:child_process";
88970
89139
  import { writeFileSync as writeFileSync33, mkdirSync as mkdirSync41 } from "node:fs";
88971
- import { dirname as dirname22, join as join76 } from "node:path";
89140
+ import { dirname as dirname23, join as join76 } from "node:path";
88972
89141
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
88973
89142
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
88974
89143
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
@@ -88993,7 +89162,7 @@ function computeRefreshDelayMs(expiresAt, now, leadMs = DEFAULT_REFRESH_LEAD_MS)
88993
89162
  function writeRefreshHeartbeat(agentName, data) {
88994
89163
  const path7 = heartbeatPath(agentName);
88995
89164
  try {
88996
- mkdirSync41(dirname22(path7), { recursive: true });
89165
+ mkdirSync41(dirname23(path7), { recursive: true });
88997
89166
  writeFileSync33(path7, JSON.stringify(data, null, 2), { mode: 420 });
88998
89167
  } catch {}
88999
89168
  }
@@ -89186,7 +89355,7 @@ function registerM365McpLauncherCommand(program3) {
89186
89355
  init_scaffold_integration();
89187
89356
  import { spawn as spawn6 } from "node:child_process";
89188
89357
  import { existsSync as existsSync78, mkdirSync as mkdirSync42, writeFileSync as writeFileSync34 } from "node:fs";
89189
- import { dirname as dirname23 } from "node:path";
89358
+ import { dirname as dirname24 } from "node:path";
89190
89359
  var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
89191
89360
  var DEFAULT_HEARTBEAT_PATH = "/state/agent/notion-launcher.heartbeat.json";
89192
89361
  var DEFAULT_VAULT_KEY = "notion/integration-token";
@@ -89196,7 +89365,7 @@ function buildNotionMcpArgs(opts) {
89196
89365
  }
89197
89366
  function defaultWriteHeartbeat(path7, contents) {
89198
89367
  try {
89199
- const dir = dirname23(path7);
89368
+ const dir = dirname24(path7);
89200
89369
  if (!existsSync78(dir))
89201
89370
  mkdirSync42(dir, { recursive: true });
89202
89371
  writeFileSync34(path7, contents);
@@ -90351,7 +90520,7 @@ init_paths();
90351
90520
  import {
90352
90521
  closeSync as closeSync14,
90353
90522
  existsSync as existsSync84,
90354
- fsyncSync as fsyncSync7,
90523
+ fsyncSync as fsyncSync8,
90355
90524
  mkdirSync as mkdirSync47,
90356
90525
  openSync as openSync14,
90357
90526
  readdirSync as readdirSync30,
@@ -90434,7 +90603,7 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
90434
90603
  const fd = openSync14(stagingPath, "w", 384);
90435
90604
  try {
90436
90605
  writeSync10(fd, yamlText);
90437
- fsyncSync7(fd);
90606
+ fsyncSync8(fd);
90438
90607
  } finally {
90439
90608
  closeSync14(fd);
90440
90609
  }
@@ -90451,7 +90620,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90451
90620
  const fd = openSync14(stagingPath, "w", 384);
90452
90621
  try {
90453
90622
  writeSync10(fd, yamlText);
90454
- fsyncSync7(fd);
90623
+ fsyncSync8(fd);
90455
90624
  } finally {
90456
90625
  closeSync14(fd);
90457
90626
  }
@@ -90658,7 +90827,7 @@ function reconcileAgentCronOnly(agent) {
90658
90827
  import {
90659
90828
  closeSync as closeSync15,
90660
90829
  existsSync as existsSync85,
90661
- fsyncSync as fsyncSync8,
90830
+ fsyncSync as fsyncSync9,
90662
90831
  mkdirSync as mkdirSync48,
90663
90832
  openSync as openSync15,
90664
90833
  readdirSync as readdirSync31,
@@ -90702,7 +90871,7 @@ function stagePendingScheduleEntry(opts) {
90702
90871
  const fd = openSync15(yamlTmp, "w", 384);
90703
90872
  try {
90704
90873
  writeSync11(fd, opts.yamlText);
90705
- fsyncSync8(fd);
90874
+ fsyncSync9(fd);
90706
90875
  } finally {
90707
90876
  closeSync15(fd);
90708
90877
  }
@@ -91670,7 +91839,7 @@ import {
91670
91839
  writeFileSync as writeFileSync40
91671
91840
  } from "node:fs";
91672
91841
  import { tmpdir as tmpdir5, homedir as homedir49 } from "node:os";
91673
- import { dirname as dirname28, join as join85, relative as relative2, resolve as resolve52 } from "node:path";
91842
+ import { dirname as dirname29, join as join85, relative as relative2, resolve as resolve52 } from "node:path";
91674
91843
  import { spawnSync as spawnSync15 } from "node:child_process";
91675
91844
 
91676
91845
  // src/cli/skill-common.ts
@@ -92140,7 +92309,7 @@ function writePayload(poolDir, name, files) {
92140
92309
  try {
92141
92310
  for (const [path8, content] of Object.entries(files)) {
92142
92311
  const full = join85(staging, path8);
92143
- mkdirSync49(dirname28(full), { recursive: true, mode: 493 });
92312
+ mkdirSync49(dirname29(full), { recursive: true, mode: 493 });
92144
92313
  const fd = openSync16(full, "wx");
92145
92314
  try {
92146
92315
  writeFileSync40(fd, content);
@@ -92262,7 +92431,7 @@ import {
92262
92431
  utimesSync,
92263
92432
  writeFileSync as writeFileSync41
92264
92433
  } from "node:fs";
92265
- import { dirname as dirname29, join as join86, relative as relative3, resolve as resolve53 } from "node:path";
92434
+ import { dirname as dirname30, join as join86, relative as relative3, resolve as resolve53 } from "node:path";
92266
92435
  import { homedir as homedir50, tmpdir as tmpdir6 } from "node:os";
92267
92436
  import { spawnSync as spawnSync16 } from "node:child_process";
92268
92437
  init_helpers();
@@ -92515,13 +92684,13 @@ function writePersonalSkill(targetDir, files) {
92515
92684
  if (targetIsSymlink) {
92516
92685
  fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
92517
92686
  }
92518
- mkdirSync50(dirname29(targetDir), { recursive: true, mode: 493 });
92519
- const staging = mkdtempSync6(join86(dirname29(targetDir), `.skill-personal-stage-`));
92687
+ mkdirSync50(dirname30(targetDir), { recursive: true, mode: 493 });
92688
+ const staging = mkdtempSync6(join86(dirname30(targetDir), `.skill-personal-stage-`));
92520
92689
  let oldRename = null;
92521
92690
  try {
92522
92691
  for (const [path8, content] of Object.entries(files)) {
92523
92692
  const full = join86(staging, path8);
92524
- mkdirSync50(dirname29(full), { recursive: true, mode: 493 });
92693
+ mkdirSync50(dirname30(full), { recursive: true, mode: 493 });
92525
92694
  const fd = openSync17(full, "wx");
92526
92695
  try {
92527
92696
  writeFileSync41(fd, content);
@@ -93264,7 +93433,7 @@ import {
93264
93433
  statSync as statSync39,
93265
93434
  lstatSync as lstatSync11,
93266
93435
  realpathSync as realpathSync8,
93267
- copyFileSync as copyFileSync12
93436
+ copyFileSync as copyFileSync13
93268
93437
  } from "node:fs";
93269
93438
  import { homedir as homedir53 } from "node:os";
93270
93439
  import { join as join89 } from "node:path";
@@ -93535,7 +93704,7 @@ function backupExistingCompose() {
93535
93704
  return null;
93536
93705
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
93537
93706
  const bak = `${p}.bak-${ts}`;
93538
- copyFileSync12(p, bak);
93707
+ copyFileSync13(p, bak);
93539
93708
  return bak;
93540
93709
  }
93541
93710
  function runDocker(args) {
@@ -93748,7 +93917,7 @@ The log is created when hostd handles its first privileged-verb request.`));
93748
93917
  init_source();
93749
93918
  init_helpers();
93750
93919
  init_operator_uid();
93751
- import { existsSync as existsSync93, mkdirSync as mkdirSync52, writeFileSync as writeFileSync43, copyFileSync as copyFileSync13 } from "node:fs";
93920
+ import { existsSync as existsSync93, mkdirSync as mkdirSync52, writeFileSync as writeFileSync43, copyFileSync as copyFileSync14 } from "node:fs";
93752
93921
  import { homedir as homedir54 } from "node:os";
93753
93922
  import { join as join90 } from "node:path";
93754
93923
  import { spawnSync as spawnSync21 } from "node:child_process";
@@ -93846,7 +94015,7 @@ function backupExistingCompose2() {
93846
94015
  return null;
93847
94016
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
93848
94017
  const bak = `${p}.bak-${ts}`;
93849
- copyFileSync13(p, bak);
94018
+ copyFileSync14(p, bak);
93850
94019
  return bak;
93851
94020
  }
93852
94021
  function runDocker2(args) {