switchroom 0.18.30 → 0.18.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/agent-scheduler/index.js +4 -2
  2. package/dist/auth-broker/index.js +4 -2
  3. package/dist/cli/notion-write-pretool.mjs +4 -2
  4. package/dist/cli/switchroom.js +708 -255
  5. package/dist/host-control/main.js +5 -3
  6. package/dist/vault/approvals/kernel-server.js +4 -2
  7. package/dist/vault/broker/server.js +4 -2
  8. package/package.json +3 -2
  9. package/profiles/_base/start.sh.hbs +142 -7
  10. package/telegram-plugin/dist/gateway/gateway.js +25870 -25007
  11. package/telegram-plugin/gateway/backstop-delivery.ts +223 -23
  12. package/telegram-plugin/gateway/captured-answer-resume.ts +259 -0
  13. package/telegram-plugin/gateway/disconnect-flush.ts +6 -44
  14. package/telegram-plugin/gateway/gateway-import-clean.test.ts +188 -0
  15. package/telegram-plugin/gateway/gateway.ts +5479 -7069
  16. package/telegram-plugin/gateway/inbound-delivery-machine-dispatch.ts +7 -15
  17. package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +35 -68
  18. package/telegram-plugin/gateway/obligation-ledger.ts +42 -0
  19. package/telegram-plugin/gateway/obligation-store.ts +37 -1
  20. package/telegram-plugin/gateway/outbound-send-path.ts +2012 -0
  21. package/telegram-plugin/gateway/turn-flush-suppression.ts +82 -0
  22. package/telegram-plugin/pending-user-notice.ts +59 -13
  23. package/telegram-plugin/subagent-watcher.ts +111 -28
  24. package/telegram-plugin/tests/backstop-delivery.test.ts +167 -0
  25. package/telegram-plugin/tests/backstop-readback-probe.test.ts +144 -0
  26. package/telegram-plugin/tests/buffer-gate-broadened.test.ts +16 -6
  27. package/telegram-plugin/tests/button-tap-turn-gated.test.ts +3 -3
  28. package/telegram-plugin/tests/captured-answer-resume.test.ts +358 -0
  29. package/telegram-plugin/tests/emission-authority-facade.test.ts +29 -19
  30. package/telegram-plugin/tests/emission-authority-ping-gate.test.ts +4 -1
  31. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +18 -7
  32. package/telegram-plugin/tests/gateway-boot-side-effect-gating.test.ts +249 -0
  33. package/telegram-plugin/tests/gateway-bot-construction-deferral.test.ts +251 -0
  34. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +5 -128
  35. package/telegram-plugin/tests/gateway-handler-registration-wiring.test.ts +303 -0
  36. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +10 -3
  37. package/telegram-plugin/tests/inbound-delivery-cutover-flip.test.ts +54 -150
  38. package/telegram-plugin/tests/inbound-delivery-cutover-gate.test.ts +10 -14
  39. package/telegram-plugin/tests/inbound-delivery-dispatch-equivalence.test.ts +6 -7
  40. package/telegram-plugin/tests/inbound-delivery-machine-dispatch.test.ts +0 -16
  41. package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +4 -4
  42. package/telegram-plugin/tests/litellm-proxy-auth-misconfig.test.ts +69 -14
  43. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +9 -3
  44. package/telegram-plugin/tests/obligation-ledger.test.ts +40 -0
  45. package/telegram-plugin/tests/obligation-store.test.ts +43 -0
  46. package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +2 -1
  47. package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +9 -6
  48. package/telegram-plugin/tests/photo-reroute-wiring.test.ts +5 -2
  49. package/telegram-plugin/tests/reply-terminal-reaction.test.ts +6 -2
  50. package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +3 -2
  51. package/telegram-plugin/tests/send-reply-golden.test.ts +571 -0
  52. package/telegram-plugin/tests/subagent-watcher-resume-reregister.test.ts +291 -0
  53. package/telegram-plugin/tests/subagent-watcher-resurrection.test.ts +32 -0
  54. package/telegram-plugin/tests/turn-end-gate-backstop.test.ts +8 -12
  55. package/telegram-plugin/tests/turn-flush-safety.test.ts +8 -6
  56. package/telegram-plugin/tests/turn-flush-suppression-wiring.test.ts +112 -0
  57. package/telegram-plugin/tests/turn-flush-suppression.test.ts +90 -0
  58. package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +2 -2
  59. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +126 -0
  60. package/telegram-plugin/tool-activity-summary.ts +27 -3
  61. package/telegram-plugin/worker-activity-feed.ts +24 -0
  62. package/telegram-plugin/gateway/busy-key-reaper.ts +0 -113
  63. package/telegram-plugin/gateway/gate-parity-probe.ts +0 -102
  64. package/telegram-plugin/tests/busy-key-reaper.test.ts +0 -192
  65. package/telegram-plugin/tests/fixtures/cutover-killswitch-probe.ts +0 -75
  66. package/telegram-plugin/tests/gate-parity-probe.test.ts +0 -171
  67. package/telegram-plugin/tests/parallel-turns-deadlock-fix.test.ts +0 -217
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.18.30", COMMIT_SHA = "7729c674";
2123
+ var VERSION = "0.18.32", COMMIT_SHA = "de41b271";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -14247,7 +14247,8 @@ var init_schema = __esm(() => {
14247
14247
  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."),
14248
14248
  api_key: exports_external.string().optional().describe("API key or vault reference for embedding provider"),
14249
14249
  docker_service: exports_external.boolean().default(true).describe("Whether to include Hindsight in docker-compose"),
14250
- 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/.")
14250
+ 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/."),
14251
+ mcp_transport: exports_external.enum(["shim", "http"]).optional().describe("How agents reach Hindsight's MCP surface. Default (unset or " + "'shim'): the lazy-connect stdio proxy `switchroom " + "hindsight-mcp-shim`, which always registers at session start " + "even when the hindsight container is down and reconnects " + "per-call. 'http' is the escape hatch that restores the legacy " + "direct Streamable-HTTP entry (session-fatal if the backend is " + "down when the agent session boots).")
14251
14252
  }).optional()
14252
14253
  });
14253
14254
  VaultConfigSchema = exports_external.object({
@@ -14290,7 +14291,8 @@ var init_schema = __esm(() => {
14290
14291
  auto_release_check: AutoReleaseCheckSchema.default({}).describe("Pull-based release-triggered fleet restart (#1743). hostd polls " + "the remote release tag on a fixed interval and applies + " + "restarts the fleet (graceful) when a new release is detected. " + "Opt-in: default enabled=false.")
14291
14292
  });
14292
14293
  WebServiceConfigSchema = exports_external.object({
14293
- managed: exports_external.boolean().default(false).describe("Whether `switchroom update` refreshes the web-service container " + "(dashboard + GitHub-webhook receiver) via `switchroom webd " + "install`. Default: false \u2014 existing installs run the web server " + "as the legacy `switchroom-web.service` systemd unit and must not " + "be surprised by a container takeover of host loopback 127.0.0.1:" + "8080 mid-update. Set true ONLY after cutting over to the " + "container (stop+disable the systemd unit, `switchroom webd " + "install`). The container runs in its own compose project " + "(`switchroom-web`), separate from the agent fleet, with " + "network_mode: host so it keeps owning loopback:8080 for the " + "cloudflared tunnel + tailscale serve consumers.")
14294
+ managed: exports_external.boolean().default(false).describe("Whether `switchroom update` refreshes the web-service container " + "(dashboard + GitHub-webhook receiver) via `switchroom webd " + "install`. Default: false \u2014 existing installs run the web server " + "as the legacy `switchroom-web.service` systemd unit and must not " + "be surprised by a container takeover of host loopback 127.0.0.1:" + "8080 mid-update. Set true ONLY after cutting over to the " + "container (stop+disable the systemd unit, `switchroom webd " + "install`). The container runs in its own compose project " + "(`switchroom-web`), separate from the agent fleet, with " + "network_mode: host so it keeps owning loopback:8080 for the " + "cloudflared tunnel + tailscale serve consumers."),
14295
+ port: exports_external.number().int().min(1).max(65535).default(8080).describe("Host loopback port the web server listens on (127.0.0.1:<port>). " + "Default: 8080. Set this when another service owns 8080 on the " + "host (e.g. UniFi OS Server) \u2014 `switchroom webd install` and the " + "`switchroom update` refresh-web step both honor it, so a custom " + "port survives updates instead of being silently reverted to " + "8080 by the regenerated compose file. Remember to point the " + "host-side consumers (cloudflared tunnel, `tailscale serve`) at " + "the same port.")
14294
14296
  });
14295
14297
  FleetHealthConfigSchema = exports_external.object({
14296
14298
  owner_agent: exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,50}$/, {
@@ -15959,11 +15961,24 @@ var init_hindsight = __esm(() => {
15959
15961
  // src/memory/hindsight.ts
15960
15962
  function generateHindsightMcpConfig(collection, memoryConfig) {
15961
15963
  const url = memoryConfig.config?.url ?? HINDSIGHT_DEFAULT_MCP_URL;
15964
+ if (memoryConfig.config?.mcp_transport === "http") {
15965
+ return {
15966
+ type: "http",
15967
+ url,
15968
+ headers: {
15969
+ "X-Bank-Id": collection
15970
+ }
15971
+ };
15972
+ }
15962
15973
  return {
15963
- type: "http",
15964
- url,
15965
- headers: {
15966
- "X-Bank-Id": collection
15974
+ type: "stdio",
15975
+ command: HINDSIGHT_SHIM_CLI_PATH,
15976
+ args: ["hindsight-mcp-shim"],
15977
+ env: {
15978
+ HINDSIGHT_MCP_URL: url,
15979
+ HINDSIGHT_BANK_ID: collection,
15980
+ HINDSIGHT_SHIM_CACHE_DIR: `${HINDSIGHT_SHIM_AGENT_HOME}/.hindsight-shim`,
15981
+ HOME: HINDSIGHT_SHIM_AGENT_HOME
15967
15982
  }
15968
15983
  };
15969
15984
  }
@@ -16490,7 +16505,7 @@ async function addMemoryTag(apiUrl, bankId, memoryId, tag, opts) {
16490
16505
  return { ok: false, reason: String(err) };
16491
16506
  }
16492
16507
  }
16493
- var DEFAULT_RETAIN_MISSION, PROFILE_MEMORY_DEFAULTS, USER_PROFILE_SOURCE_QUERY = "What are the key facts, preferences, context, and communication style about the user I talk to? Summarize what matters for making the agent feel like it knows them.", DEMOTE_FROM_RECALL_TAG = "[demote-from-recall]";
16508
+ var HINDSIGHT_SHIM_CLI_PATH = "/usr/local/bin/switchroom", HINDSIGHT_SHIM_AGENT_HOME = "/state/agent/home", DEFAULT_RETAIN_MISSION, PROFILE_MEMORY_DEFAULTS, USER_PROFILE_SOURCE_QUERY = "What are the key facts, preferences, context, and communication style about the user I talk to? Summarize what matters for making the agent feel like it knows them.", DEMOTE_FROM_RECALL_TAG = "[demote-from-recall]";
16494
16509
  var init_hindsight2 = __esm(() => {
16495
16510
  init_users();
16496
16511
  init_hindsight();
@@ -26179,7 +26194,10 @@ function webkiteDenyForAgent(agentConfig) {
26179
26194
  function resolveMainModel(model) {
26180
26195
  if (model === undefined || model === "default")
26181
26196
  return SWITCHROOM_DEFAULT_MAIN_MODEL;
26182
- return model;
26197
+ return normalizeModelAlias(model);
26198
+ }
26199
+ function normalizeModelAlias(model) {
26200
+ return model === "claude-fable-5" ? "fable" : model;
26183
26201
  }
26184
26202
  function dedupe2(items) {
26185
26203
  const seen = new Set;
@@ -47786,8 +47804,8 @@ var init_fleet_defaults = __esm(() => {
47786
47804
  });
47787
47805
 
47788
47806
  // src/agents/connection-health.ts
47789
- import { mkdirSync as mkdirSync45, writeFileSync as writeFileSync27 } from "node:fs";
47790
- import { join as join81 } from "node:path";
47807
+ import { mkdirSync as mkdirSync46, writeFileSync as writeFileSync28 } from "node:fs";
47808
+ import { join as join82 } from "node:path";
47791
47809
  async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
47792
47810
  const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
47793
47811
  if (reqs.length === 0)
@@ -47844,10 +47862,10 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
47844
47862
  return issues;
47845
47863
  }
47846
47864
  function writeConnectionHealthFile(agentDir, health, deps) {
47847
- const dir = join81(agentDir, ".claude");
47848
- const path8 = join81(dir, CONNECTION_HEALTH_FILENAME);
47849
- (deps?.mkdir ?? ((p, o) => mkdirSync45(p, o)))(dir, { recursive: true });
47850
- (deps?.writeFile ?? ((p, d) => writeFileSync27(p, d)))(path8, JSON.stringify(health, null, 2) + `
47865
+ const dir = join82(agentDir, ".claude");
47866
+ const path8 = join82(dir, CONNECTION_HEALTH_FILENAME);
47867
+ (deps?.mkdir ?? ((p, o) => mkdirSync46(p, o)))(dir, { recursive: true });
47868
+ (deps?.writeFile ?? ((p, d) => writeFileSync28(p, d)))(path8, JSON.stringify(health, null, 2) + `
47851
47869
  `);
47852
47870
  }
47853
47871
  async function refreshAgentConnectionHealth(config, agentName, agentDir, deps) {
@@ -47868,10 +47886,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
47868
47886
  var init_connection_health = () => {};
47869
47887
 
47870
47888
  // src/cli/update-prompt-hook.ts
47871
- import { existsSync as existsSync83, readFileSync as readFileSync71, writeFileSync as writeFileSync28, chmodSync as chmodSync12, mkdirSync as mkdirSync46 } from "node:fs";
47872
- import { join as join82 } from "node:path";
47889
+ import { existsSync as existsSync83, readFileSync as readFileSync72, writeFileSync as writeFileSync29, chmodSync as chmodSync12, mkdirSync as mkdirSync47 } from "node:fs";
47890
+ import { join as join83 } from "node:path";
47873
47891
  function containerHookCommand() {
47874
- return join82(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
47892
+ return join83(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
47875
47893
  }
47876
47894
  function updatePromptHookScript() {
47877
47895
  return `#!/bin/bash
@@ -47937,14 +47955,14 @@ exit 0
47937
47955
  `;
47938
47956
  }
47939
47957
  function installUpdatePromptHook(agentDir) {
47940
- const hooksDir = join82(agentDir, ".claude", "hooks");
47941
- mkdirSync46(hooksDir, { recursive: true });
47942
- const scriptPath = join82(hooksDir, HOOK_FILENAME);
47958
+ const hooksDir = join83(agentDir, ".claude", "hooks");
47959
+ mkdirSync47(hooksDir, { recursive: true });
47960
+ const scriptPath = join83(hooksDir, HOOK_FILENAME);
47943
47961
  const desired = updatePromptHookScript();
47944
47962
  let installed = false;
47945
- const existing = existsSync83(scriptPath) ? readFileSync71(scriptPath, "utf-8") : "";
47963
+ const existing = existsSync83(scriptPath) ? readFileSync72(scriptPath, "utf-8") : "";
47946
47964
  if (existing !== desired) {
47947
- writeFileSync28(scriptPath, desired, { mode: 493 });
47965
+ writeFileSync29(scriptPath, desired, { mode: 493 });
47948
47966
  chmodSync12(scriptPath, 493);
47949
47967
  installed = true;
47950
47968
  } else {
@@ -47952,11 +47970,11 @@ function installUpdatePromptHook(agentDir) {
47952
47970
  chmodSync12(scriptPath, 493);
47953
47971
  } catch {}
47954
47972
  }
47955
- const settingsPath = join82(agentDir, ".claude", "settings.json");
47973
+ const settingsPath = join83(agentDir, ".claude", "settings.json");
47956
47974
  if (!existsSync83(settingsPath)) {
47957
47975
  return { scriptPath, settingsPath, installed };
47958
47976
  }
47959
- const raw = readFileSync71(settingsPath, "utf-8");
47977
+ const raw = readFileSync72(settingsPath, "utf-8");
47960
47978
  let parsed;
47961
47979
  try {
47962
47980
  parsed = JSON.parse(raw);
@@ -47992,7 +48010,7 @@ function installUpdatePromptHook(agentDir) {
47992
48010
  if (mutated) {
47993
48011
  hooks.UserPromptSubmit = list2;
47994
48012
  parsed.hooks = hooks;
47995
- writeFileSync28(settingsPath, JSON.stringify(parsed, null, 2) + `
48013
+ writeFileSync29(settingsPath, JSON.stringify(parsed, null, 2) + `
47996
48014
  `, { mode: 384 });
47997
48015
  installed = true;
47998
48016
  } else if (!alreadyCorrect) {
@@ -48001,7 +48019,7 @@ function installUpdatePromptHook(agentDir) {
48001
48019
  });
48002
48020
  hooks.UserPromptSubmit = list2;
48003
48021
  parsed.hooks = hooks;
48004
- writeFileSync28(settingsPath, JSON.stringify(parsed, null, 2) + `
48022
+ writeFileSync29(settingsPath, JSON.stringify(parsed, null, 2) + `
48005
48023
  `, { mode: 384 });
48006
48024
  installed = true;
48007
48025
  }
@@ -48381,7 +48399,7 @@ __export(exports_voice_sidecar_token, {
48381
48399
  VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
48382
48400
  });
48383
48401
  import { randomBytes as randomBytes14 } from "node:crypto";
48384
- import { chmodSync as chmodSync13, chownSync as chownSync7, existsSync as existsSync85, mkdirSync as mkdirSync47, readFileSync as readFileSync72, rmSync as rmSync17, writeFileSync as writeFileSync29 } from "node:fs";
48402
+ import { chmodSync as chmodSync13, chownSync as chownSync7, existsSync as existsSync85, mkdirSync as mkdirSync48, readFileSync as readFileSync73, rmSync as rmSync17, writeFileSync as writeFileSync30 } from "node:fs";
48385
48403
  import { dirname as dirname29 } from "node:path";
48386
48404
  async function defaultResolveOrSeedToken(home2, writeErr) {
48387
48405
  const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
@@ -48417,7 +48435,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
48417
48435
  if (engine !== "local") {
48418
48436
  try {
48419
48437
  if (existsSync85(envPath)) {
48420
- const body = readFileSync72(envPath, "utf-8");
48438
+ const body = readFileSync73(envPath, "utf-8");
48421
48439
  if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
48422
48440
  rmSync17(envPath);
48423
48441
  }
@@ -48436,11 +48454,11 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
48436
48454
  if (!token)
48437
48455
  return;
48438
48456
  try {
48439
- mkdirSync47(dirname29(envPath), { recursive: true });
48457
+ mkdirSync48(dirname29(envPath), { recursive: true });
48440
48458
  let body = "";
48441
48459
  try {
48442
48460
  if (existsSync85(envPath))
48443
- body = readFileSync72(envPath, "utf-8");
48461
+ body = readFileSync73(envPath, "utf-8");
48444
48462
  } catch {}
48445
48463
  const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
48446
48464
  const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
@@ -48448,7 +48466,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
48448
48466
  `) ? body + `
48449
48467
  ` : body) + line + `
48450
48468
  `;
48451
- writeFileSync29(envPath, next, {
48469
+ writeFileSync30(envPath, next, {
48452
48470
  encoding: "utf-8",
48453
48471
  mode: 384
48454
48472
  });
@@ -48499,11 +48517,11 @@ __export(exports_apply, {
48499
48517
  DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
48500
48518
  COMPOSE_PROJECT: () => COMPOSE_PROJECT2
48501
48519
  });
48502
- import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync86, mkdirSync as mkdirSync48, readFileSync as readFileSync73, readdirSync as readdirSync29, renameSync as renameSync19, statSync as statSync47, writeFileSync as writeFileSync30 } from "node:fs";
48520
+ import { accessSync as accessSync3, chownSync as chownSync8, constants as fsConstants6, copyFileSync as copyFileSync12, existsSync as existsSync86, mkdirSync as mkdirSync49, readFileSync as readFileSync74, readdirSync as readdirSync29, renameSync as renameSync20, statSync as statSync47, writeFileSync as writeFileSync31 } from "node:fs";
48503
48521
  import { mkdir as mkdir2 } from "node:fs/promises";
48504
48522
  import { spawnSync as childSpawnSync } from "node:child_process";
48505
48523
  import readline from "node:readline";
48506
- import { dirname as dirname30, join as join84, resolve as resolve52 } from "node:path";
48524
+ import { dirname as dirname30, join as join85, resolve as resolve52 } from "node:path";
48507
48525
  import { homedir as homedir49 } from "node:os";
48508
48526
  import { execFileSync as execFileSync27 } from "node:child_process";
48509
48527
  function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
@@ -48515,7 +48533,7 @@ async function resolveOperatorVaultPassphrase(home2) {
48515
48533
  return envPass;
48516
48534
  try {
48517
48535
  const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
48518
- const blobPath = join84(home2, ".switchroom", "vault-auto-unlock");
48536
+ const blobPath = join85(home2, ".switchroom", "vault-auto-unlock");
48519
48537
  const pass = readAutoUnlockFile2(blobPath);
48520
48538
  return pass && pass.length > 0 ? pass : null;
48521
48539
  } catch {
@@ -48605,7 +48623,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
48605
48623
  let configText = null;
48606
48624
  if (switchroomConfigPath && existsSync86(switchroomConfigPath)) {
48607
48625
  try {
48608
- configText = readFileSync73(switchroomConfigPath, "utf-8");
48626
+ configText = readFileSync74(switchroomConfigPath, "utf-8");
48609
48627
  } catch (err) {
48610
48628
  ctx.writeErr(source_default.yellow(` ! litellm: could not read config for ACL grants (${err.message}); keys will be provisioned but agents may lack read-ACL.
48611
48629
  `));
@@ -48828,7 +48846,7 @@ function resolveVaultBindMountDir(homeDir, ctx) {
48828
48846
  if (isCustomPath && ctx.customVaultPath) {
48829
48847
  return dirname30(ctx.customVaultPath);
48830
48848
  }
48831
- return join84(homeDir, ".switchroom", "vault");
48849
+ return join85(homeDir, ".switchroom", "vault");
48832
48850
  }
48833
48851
  function inspectVaultBindMountDir(vaultDir) {
48834
48852
  if (!existsSync86(vaultDir))
@@ -48859,60 +48877,60 @@ function hasVaultRefs(value) {
48859
48877
  async function ensureHostMountSources(config) {
48860
48878
  const home2 = resolveHostHomeForCompose();
48861
48879
  const dirs = [
48862
- join84(home2, ".switchroom", "approvals"),
48863
- join84(home2, ".switchroom", "scheduler"),
48864
- join84(home2, ".switchroom", "logs"),
48865
- join84(home2, ".switchroom", "compose"),
48866
- join84(home2, ".switchroom", "broker-operator")
48880
+ join85(home2, ".switchroom", "approvals"),
48881
+ join85(home2, ".switchroom", "scheduler"),
48882
+ join85(home2, ".switchroom", "logs"),
48883
+ join85(home2, ".switchroom", "compose"),
48884
+ join85(home2, ".switchroom", "broker-operator")
48867
48885
  ];
48868
48886
  for (const name of Object.keys(config.agents)) {
48869
- dirs.push(join84(home2, ".switchroom", "agents", name));
48870
- dirs.push(join84(home2, ".switchroom", "logs", name));
48871
- dirs.push(join84(home2, ".claude", "projects", name));
48872
- dirs.push(join84(home2, ".switchroom", "audit", name));
48873
- if (existsSync86(join84(home2, ".switchroom-config"))) {
48874
- dirs.push(join84(home2, ".switchroom-config", "agents", name, "personal-skills"));
48887
+ dirs.push(join85(home2, ".switchroom", "agents", name));
48888
+ dirs.push(join85(home2, ".switchroom", "logs", name));
48889
+ dirs.push(join85(home2, ".claude", "projects", name));
48890
+ dirs.push(join85(home2, ".switchroom", "audit", name));
48891
+ if (existsSync86(join85(home2, ".switchroom-config"))) {
48892
+ dirs.push(join85(home2, ".switchroom-config", "agents", name, "personal-skills"));
48875
48893
  }
48876
48894
  }
48877
48895
  for (const dir of dirs) {
48878
48896
  await mkdir2(dir, { recursive: true });
48879
48897
  }
48880
- const autoUnlockPath = join84(home2, ".switchroom", "vault-auto-unlock");
48898
+ const autoUnlockPath = join85(home2, ".switchroom", "vault-auto-unlock");
48881
48899
  if (!existsSync86(autoUnlockPath)) {
48882
- writeFileSync30(autoUnlockPath, "", { mode: 384 });
48900
+ writeFileSync31(autoUnlockPath, "", { mode: 384 });
48883
48901
  }
48884
- const auditLogPath = join84(home2, ".switchroom", "vault-audit.log");
48902
+ const auditLogPath = join85(home2, ".switchroom", "vault-audit.log");
48885
48903
  if (!existsSync86(auditLogPath)) {
48886
- writeFileSync30(auditLogPath, "", { mode: 420 });
48904
+ writeFileSync31(auditLogPath, "", { mode: 420 });
48887
48905
  }
48888
48906
  const grantsDbDir = getGrantsDbDir(home2);
48889
- mkdirSync48(grantsDbDir, { recursive: true, mode: 448 });
48907
+ mkdirSync49(grantsDbDir, { recursive: true, mode: 448 });
48890
48908
  migrateLegacyGrantsDbLocation(getGrantsDbPath(home2));
48891
- const hostdAuditLogPath = join84(home2, ".switchroom", "host-control-audit.log");
48909
+ const hostdAuditLogPath = join85(home2, ".switchroom", "host-control-audit.log");
48892
48910
  if (!existsSync86(hostdAuditLogPath)) {
48893
- writeFileSync30(hostdAuditLogPath, "", { mode: 420 });
48911
+ writeFileSync31(hostdAuditLogPath, "", { mode: 420 });
48894
48912
  }
48895
48913
  for (const name of Object.keys(config.agents)) {
48896
- const tokenPath = join84(home2, ".switchroom", "agents", name, ".vault-token");
48914
+ const tokenPath = join85(home2, ".switchroom", "agents", name, ".vault-token");
48897
48915
  if (!existsSync86(tokenPath)) {
48898
- writeFileSync30(tokenPath, "", { mode: 384 });
48916
+ writeFileSync31(tokenPath, "", { mode: 384 });
48899
48917
  }
48900
48918
  try {
48901
48919
  const uid = allocateAgentUid(name);
48902
48920
  chownSync8(tokenPath, uid, uid);
48903
48921
  } catch {}
48904
48922
  }
48905
- const fleetDir = join84(home2, ".switchroom", "fleet");
48923
+ const fleetDir = join85(home2, ".switchroom", "fleet");
48906
48924
  await mkdir2(fleetDir, { recursive: true });
48907
- const invariantsPath = join84(fleetDir, "switchroom-invariants.md");
48925
+ const invariantsPath = join85(fleetDir, "switchroom-invariants.md");
48908
48926
  const invariantsCanonical = renderFleetInvariants();
48909
- const invariantsCurrent = existsSync86(invariantsPath) ? readFileSync73(invariantsPath, "utf-8") : null;
48927
+ const invariantsCurrent = existsSync86(invariantsPath) ? readFileSync74(invariantsPath, "utf-8") : null;
48910
48928
  if (invariantsCurrent !== invariantsCanonical) {
48911
- writeFileSync30(invariantsPath, invariantsCanonical, { mode: 420 });
48929
+ writeFileSync31(invariantsPath, invariantsCanonical, { mode: 420 });
48912
48930
  }
48913
- const fleetClaudePath = join84(fleetDir, "CLAUDE.md");
48931
+ const fleetClaudePath = join85(fleetDir, "CLAUDE.md");
48914
48932
  if (!existsSync86(fleetClaudePath)) {
48915
- writeFileSync30(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
48933
+ writeFileSync31(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
48916
48934
  mode: 420
48917
48935
  });
48918
48936
  }
@@ -48996,17 +49014,17 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
48996
49014
  }
48997
49015
  function writeInstallTypeCache(homeDir = homedir49()) {
48998
49016
  const ctx = detectInstallType();
48999
- const dir = join84(homeDir, ".switchroom");
49000
- const out = join84(dir, "install-type.json");
49017
+ const dir = join85(homeDir, ".switchroom");
49018
+ const out = join85(dir, "install-type.json");
49001
49019
  const tmp = `${out}.tmp`;
49002
- mkdirSync48(dir, { recursive: true });
49020
+ mkdirSync49(dir, { recursive: true });
49003
49021
  const payload = {
49004
49022
  install_type: ctx.install_type,
49005
49023
  detected_at: new Date().toISOString(),
49006
49024
  source_paths: ctx.source_paths
49007
49025
  };
49008
- writeFileSync30(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
49009
- renameSync19(tmp, out);
49026
+ writeFileSync31(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
49027
+ renameSync20(tmp, out);
49010
49028
  return out;
49011
49029
  }
49012
49030
  async function runApply(config, options, deps = {}, switchroomConfigPath) {
@@ -49064,17 +49082,17 @@ Applying switchroom config...
49064
49082
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
49065
49083
  `));
49066
49084
  try {
49067
- installUpdatePromptHook(join84(agentsDir, name));
49085
+ installUpdatePromptHook(join85(agentsDir, name));
49068
49086
  } catch (hookErr) {
49069
49087
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
49070
49088
  `));
49071
49089
  }
49072
- await refreshAgentConnectionHealth(config, name, join84(agentsDir, name), {
49090
+ await refreshAgentConnectionHealth(config, name, join85(agentsDir, name), {
49073
49091
  vaultAclReader: connHealthVaultAclReader
49074
49092
  });
49075
49093
  try {
49076
49094
  const uid = allocateAgentUid(name);
49077
- alignAgentUid(name, join84(agentsDir, name), uid, {
49095
+ alignAgentUid(name, join85(agentsDir, name), uid, {
49078
49096
  confirm: !options.nonInteractive,
49079
49097
  writeOut
49080
49098
  });
@@ -49119,7 +49137,7 @@ Applying switchroom config...
49119
49137
  for (const name of agentNames) {
49120
49138
  try {
49121
49139
  const uid = allocateAgentUid(name);
49122
- alignAgentUid(name, join84(agentsDir, name), uid, {
49140
+ alignAgentUid(name, join85(agentsDir, name), uid, {
49123
49141
  confirm: !options.nonInteractive,
49124
49142
  writeOut
49125
49143
  });
@@ -49463,7 +49481,7 @@ function copyExampleConfig2(name) {
49463
49481
  }
49464
49482
  const embedded = EMBEDDED_EXAMPLES[name];
49465
49483
  if (embedded !== undefined) {
49466
- writeFileSync30(dest, embedded, { encoding: "utf8" });
49484
+ writeFileSync31(dest, embedded, { encoding: "utf8" });
49467
49485
  console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
49468
49486
  return;
49469
49487
  }
@@ -49479,7 +49497,7 @@ function findUnwritableAgentDirs(config, opts) {
49479
49497
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
49480
49498
  const unwritable = [];
49481
49499
  for (const name of targets) {
49482
- const startSh = join84(agentsDir, name, "start.sh");
49500
+ const startSh = join85(agentsDir, name, "start.sh");
49483
49501
  if (!existsSync86(startSh))
49484
49502
  continue;
49485
49503
  try {
@@ -49684,7 +49702,7 @@ var init_apply = __esm(() => {
49684
49702
  switchroom: switchroom_default,
49685
49703
  minimal: minimal_default
49686
49704
  };
49687
- DEFAULT_COMPOSE_PATH2 = join84(homedir49(), ".switchroom", "compose", "docker-compose.yml");
49705
+ DEFAULT_COMPOSE_PATH2 = join85(homedir49(), ".switchroom", "compose", "docker-compose.yml");
49688
49706
  IN_AGENT_CONTAINER_APPLY_MSG = "`switchroom apply`'s full per-agent scaffold cannot run from inside an " + "agent container \u2014 this is a host/hostd operation by construction " + "(no vault at the container HOME, and no `docker compose` v2 plugin here).\nTo roll the fleet to a new version, drive the hostd rollout (`mcp__hostd__rollout`): it runs a `--compose-only` apply plus a per-agent restart-reconcile, and each agent refreshes its own templates " + `on restart \u2014 the roll completes without any agent running a full apply.
49689
49707
  ` + "A full host-side `sudo switchroom apply` is only needed for structural changes (compose regeneration / new-agent scaffolding), and is run by the operator on the host, never from inside an agent.";
49690
49708
  SELF_ELEVATE_PRESERVED_ENV = [
@@ -63133,7 +63151,7 @@ __export(exports_server2, {
63133
63151
  TOOLS: () => TOOLS2
63134
63152
  });
63135
63153
  import { randomBytes as randomBytes16 } from "node:crypto";
63136
- import { existsSync as existsSync95, readFileSync as readFileSync82 } from "node:fs";
63154
+ import { existsSync as existsSync95, readFileSync as readFileSync83 } from "node:fs";
63137
63155
  function selfSocketPath() {
63138
63156
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
63139
63157
  }
@@ -63422,7 +63440,7 @@ function getLastUpdateApplyStatus() {
63422
63440
  }
63423
63441
  let raw;
63424
63442
  try {
63425
- raw = readFileSync82(path9, "utf-8");
63443
+ raw = readFileSync83(path9, "utf-8");
63426
63444
  } catch (err2) {
63427
63445
  return errorText2(`get_status: failed to read audit log at ${path9}: ${err2.message}`);
63428
63446
  }
@@ -64112,11 +64130,11 @@ __export(exports_scan, {
64112
64130
  ledgerPathForBase: () => ledgerPathForBase
64113
64131
  });
64114
64132
  import {
64115
- readFileSync as readFileSync84,
64133
+ readFileSync as readFileSync85,
64116
64134
  readdirSync as readdirSync37,
64117
64135
  existsSync as existsSync98,
64118
- mkdirSync as mkdirSync55,
64119
- writeFileSync as writeFileSync36
64136
+ mkdirSync as mkdirSync56,
64137
+ writeFileSync as writeFileSync37
64120
64138
  } from "node:fs";
64121
64139
  import { resolve as resolve57, dirname as dirname33 } from "node:path";
64122
64140
  import { homedir as homedir58 } from "node:os";
@@ -64155,7 +64173,7 @@ function runScan(opts = {}) {
64155
64173
  let sawArtifact = false;
64156
64174
  try {
64157
64175
  if (existsSync98(turnsPath)) {
64158
- turnsText = readFileSync84(turnsPath, "utf-8");
64176
+ turnsText = readFileSync85(turnsPath, "utf-8");
64159
64177
  sawArtifact = true;
64160
64178
  }
64161
64179
  } catch (e) {
@@ -64163,7 +64181,7 @@ function runScan(opts = {}) {
64163
64181
  }
64164
64182
  try {
64165
64183
  if (existsSync98(gwPath)) {
64166
- gwText = readFileSync84(gwPath, "utf-8");
64184
+ gwText = readFileSync85(gwPath, "utf-8");
64167
64185
  sawArtifact = true;
64168
64186
  }
64169
64187
  } catch (e) {
@@ -64205,7 +64223,7 @@ function readLedgerIfPresent(base) {
64205
64223
  try {
64206
64224
  if (!existsSync98(path9))
64207
64225
  return null;
64208
- return JSON.parse(readFileSync84(path9, "utf-8"));
64226
+ return JSON.parse(readFileSync85(path9, "utf-8"));
64209
64227
  } catch {
64210
64228
  return null;
64211
64229
  }
@@ -64215,8 +64233,8 @@ function ledgerPathForBase(base) {
64215
64233
  }
64216
64234
  function writeLedger(base, ledger) {
64217
64235
  const path9 = ledgerPathForBase(base);
64218
- mkdirSync55(dirname33(path9), { recursive: true });
64219
- writeFileSync36(path9, JSON.stringify(ledger, null, 2) + `
64236
+ mkdirSync56(dirname33(path9), { recursive: true });
64237
+ writeFileSync37(path9, JSON.stringify(ledger, null, 2) + `
64220
64238
  `, "utf-8");
64221
64239
  return path9;
64222
64240
  }
@@ -66965,7 +66983,7 @@ function registerAgentCommand(program3) {
66965
66983
  name,
66966
66984
  status: status?.active ?? "unknown",
66967
66985
  uptime: formatUptime2(status?.uptime ?? null),
66968
- model: resolved.model ?? SWITCHROOM_DEFAULT_MAIN_MODEL,
66986
+ model: resolveMainModel(resolved.model),
66969
66987
  thinking_effort: resolved.thinking_effort ?? SWITCHROOM_DEFAULT_THINKING_EFFORT,
66970
66988
  extends: agentConfig.extends ?? "default",
66971
66989
  topic_name: agentConfig.topic_name,
@@ -66999,6 +67017,16 @@ function registerAgentCommand(program3) {
66999
67017
  printTable(headers, rows, widths);
67000
67018
  console.log();
67001
67019
  }));
67020
+ agent.command("effective-model <name>").description("Print the cascade-resolved model the agent's launcher boots with").action(withConfigError(async (name) => {
67021
+ const config = getConfig(program3);
67022
+ const agentConfig = config.agents[name];
67023
+ if (!agentConfig) {
67024
+ console.error(source_default.red(`Agent "${name}" is not defined in switchroom.yaml`));
67025
+ process.exit(1);
67026
+ }
67027
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
67028
+ console.log(resolveMainModel(resolved.model));
67029
+ }));
67002
67030
  agent.command("status <name>").description("Show health status for a single agent (PID, polling, Hindsight, last messages)").option("--json", "Output as JSON").action(withConfigError(async (name, opts) => {
67003
67031
  const config = getConfig(program3);
67004
67032
  const agentsDir = resolveAgentsDir(config);
@@ -89646,9 +89674,425 @@ function registerNotionMcpLauncherCommand(program3) {
89646
89674
  });
89647
89675
  }
89648
89676
 
89677
+ // src/cli/hindsight-mcp-shim.ts
89678
+ init_hindsight();
89679
+ import { mkdirSync as mkdirSync45, readFileSync as readFileSync70, renameSync as renameSync19, writeFileSync as writeFileSync27 } from "node:fs";
89680
+ import { tmpdir as tmpdir5 } from "node:os";
89681
+ import { join as join81 } from "node:path";
89682
+ import { createInterface as createInterface6 } from "node:readline";
89683
+ var SHIM_SUPPORTED_PROTOCOL_VERSIONS = [
89684
+ "2025-06-18",
89685
+ "2025-03-26",
89686
+ "2024-11-05"
89687
+ ];
89688
+ var TOOLS_LIST_TIMEOUT_MS = 3000;
89689
+ var TOOLS_CALL_TIMEOUT_MS = 120000;
89690
+ var UPSTREAM_CONNECT_TIMEOUT_MS = 3000;
89691
+ var TOOLS_CACHE_FILENAME = "hindsight-tools-list.json";
89692
+ var FALLBACK_TOOL_TABLE = {
89693
+ cancel_operation: [["operation_id"], ["bank_id", "operation_id"]],
89694
+ clear_memories: [[], ["bank_id", "type"]],
89695
+ create_bank: [["bank_id"], ["bank_id", "mission", "name"]],
89696
+ create_directive: [["content", "name"], ["bank_id", "content", "is_active", "name", "priority", "tags"]],
89697
+ create_mental_model: [["name", "source_query"], ["bank_id", "max_tokens", "mental_model_id", "name", "source_query", "tags", "trigger_refresh_after_consolidation"]],
89698
+ delete_bank: [[], ["bank_id"]],
89699
+ delete_directive: [["directive_id"], ["bank_id", "directive_id"]],
89700
+ delete_document: [["document_id"], ["bank_id", "document_id"]],
89701
+ delete_mental_model: [["mental_model_id"], ["bank_id", "mental_model_id"]],
89702
+ get_bank: [[], ["bank_id"]],
89703
+ get_bank_stats: [[], ["bank_id"]],
89704
+ get_document: [["document_id"], ["bank_id", "document_id"]],
89705
+ get_memory: [["memory_id"], ["bank_id", "memory_id"]],
89706
+ get_mental_model: [["mental_model_id"], ["bank_id", "detail", "mental_model_id"]],
89707
+ get_operation: [["operation_id"], ["bank_id", "operation_id"]],
89708
+ list_banks: [[], []],
89709
+ list_directives: [[], ["active_only", "bank_id", "tags"]],
89710
+ list_documents: [[], ["bank_id", "limit", "q"]],
89711
+ list_memories: [[], ["bank_id", "limit", "offset", "q", "type"]],
89712
+ list_mental_models: [[], ["bank_id", "detail", "tags"]],
89713
+ list_operations: [[], ["bank_id", "limit", "status"]],
89714
+ list_tags: [[], ["bank_id", "limit", "q"]],
89715
+ recall: [["query"], ["bank_id", "budget", "max_tokens", "query", "query_timestamp", "tag_groups", "tags", "tags_match", "types"]],
89716
+ reflect: [["query"], ["bank_id", "budget", "context", "max_tokens", "query", "response_schema", "tags", "tags_match"]],
89717
+ refresh_mental_model: [["mental_model_id"], ["bank_id", "mental_model_id"]],
89718
+ retain: [["content"], ["bank_id", "content", "context", "document_id", "metadata", "strategy", "tags", "timestamp", "update_mode"]],
89719
+ sync_retain: [["content"], ["bank_id", "content", "context", "document_id", "metadata", "strategy", "tags", "timestamp"]],
89720
+ update_bank: [[], ["bank_id", "config_updates", "mission", "name"]],
89721
+ update_mental_model: [["mental_model_id"], ["bank_id", "max_tokens", "mental_model_id", "name", "source_query", "tags", "trigger_refresh_after_consolidation"]]
89722
+ };
89723
+ function buildFallbackToolsList() {
89724
+ const tools = Object.entries(FALLBACK_TOOL_TABLE).map(([name, [required, props]]) => ({
89725
+ name,
89726
+ description: "Hindsight memory tool (served from the shim's static fallback " + "manifest because the backend has not been reachable yet; the " + "schema is permissive and will be replaced by the live one).",
89727
+ inputSchema: {
89728
+ type: "object",
89729
+ properties: Object.fromEntries(props.map((p) => [p, {}])),
89730
+ required
89731
+ }
89732
+ }));
89733
+ return { tools };
89734
+ }
89735
+
89736
+ class UpstreamClient {
89737
+ opts;
89738
+ sessionId = null;
89739
+ protocolVersion = null;
89740
+ nextId = 1;
89741
+ constructor(opts) {
89742
+ this.opts = opts;
89743
+ }
89744
+ get fetchImpl() {
89745
+ return this.opts.fetchImpl ?? fetch;
89746
+ }
89747
+ reset() {
89748
+ this.sessionId = null;
89749
+ this.protocolVersion = null;
89750
+ }
89751
+ get connected() {
89752
+ return this.sessionId !== null || this.protocolVersion !== null;
89753
+ }
89754
+ headers() {
89755
+ const h = {
89756
+ "content-type": "application/json",
89757
+ accept: "application/json, text/event-stream"
89758
+ };
89759
+ if (this.opts.bankId)
89760
+ h["X-Bank-Id"] = this.opts.bankId;
89761
+ if (this.sessionId)
89762
+ h["mcp-session-id"] = this.sessionId;
89763
+ if (this.protocolVersion)
89764
+ h["mcp-protocol-version"] = this.protocolVersion;
89765
+ return h;
89766
+ }
89767
+ async post(body, timeoutMs) {
89768
+ const ctl = new AbortController;
89769
+ const timer = setTimeout(() => ctl.abort(), timeoutMs);
89770
+ try {
89771
+ return await this.fetchImpl(this.opts.url, {
89772
+ method: "POST",
89773
+ headers: this.headers(),
89774
+ body: JSON.stringify(body),
89775
+ signal: ctl.signal
89776
+ });
89777
+ } finally {
89778
+ clearTimeout(timer);
89779
+ }
89780
+ }
89781
+ async parseResponse(res, id) {
89782
+ const ctype = res.headers.get("content-type") ?? "";
89783
+ const text = await res.text();
89784
+ if (ctype.includes("text/event-stream")) {
89785
+ for (const chunk2 of text.split(/\n\n/)) {
89786
+ const data = chunk2.split(/\n/).filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim()).join(`
89787
+ `);
89788
+ if (!data)
89789
+ continue;
89790
+ try {
89791
+ const msg = JSON.parse(data);
89792
+ if (msg.id === id)
89793
+ return msg;
89794
+ } catch {}
89795
+ }
89796
+ throw new Error("no matching JSON-RPC response in SSE stream");
89797
+ }
89798
+ return JSON.parse(text);
89799
+ }
89800
+ async ensureSession() {
89801
+ if (this.protocolVersion)
89802
+ return;
89803
+ const timeoutMs = this.opts.connectTimeoutMs ?? UPSTREAM_CONNECT_TIMEOUT_MS;
89804
+ const id = this.nextId++;
89805
+ const res = await this.post({
89806
+ jsonrpc: "2.0",
89807
+ id,
89808
+ method: "initialize",
89809
+ params: {
89810
+ protocolVersion: SHIM_SUPPORTED_PROTOCOL_VERSIONS[0],
89811
+ capabilities: {},
89812
+ clientInfo: { name: "switchroom-hindsight-shim", version: "1.0.0" }
89813
+ }
89814
+ }, timeoutMs);
89815
+ if (!res.ok) {
89816
+ throw new Error(`upstream initialize failed: HTTP ${res.status}`);
89817
+ }
89818
+ this.sessionId = res.headers.get("mcp-session-id");
89819
+ const msg = await this.parseResponse(res, id);
89820
+ if (msg.error) {
89821
+ throw new Error(`upstream initialize error: ${msg.error.message}`);
89822
+ }
89823
+ const negotiated = msg.result?.protocolVersion;
89824
+ this.protocolVersion = negotiated ?? SHIM_SUPPORTED_PROTOCOL_VERSIONS[0];
89825
+ const notifRes = await this.post({ jsonrpc: "2.0", method: "notifications/initialized" }, timeoutMs);
89826
+ if (!notifRes.ok && notifRes.status !== 405) {
89827
+ throw new Error(`upstream initialized notification failed: HTTP ${notifRes.status}`);
89828
+ }
89829
+ await notifRes.text().catch(() => {
89830
+ return;
89831
+ });
89832
+ }
89833
+ async request(method, params, timeoutMs, retry = true) {
89834
+ try {
89835
+ await this.ensureSession();
89836
+ } catch (err) {
89837
+ this.reset();
89838
+ if (retry)
89839
+ return this.request(method, params, timeoutMs, false);
89840
+ throw err;
89841
+ }
89842
+ const id = this.nextId++;
89843
+ let res;
89844
+ try {
89845
+ res = await this.post({ jsonrpc: "2.0", id, method, params }, timeoutMs);
89846
+ } catch (err) {
89847
+ this.reset();
89848
+ if (retry && isPreDeliveryError(err)) {
89849
+ return this.request(method, params, timeoutMs, false);
89850
+ }
89851
+ throw err;
89852
+ }
89853
+ if (res.status === 404) {
89854
+ this.reset();
89855
+ await res.text().catch(() => {
89856
+ return;
89857
+ });
89858
+ if (retry)
89859
+ return this.request(method, params, timeoutMs, false);
89860
+ throw new Error("upstream session expired (404)");
89861
+ }
89862
+ try {
89863
+ if (!res.ok)
89864
+ throw new Error(`upstream HTTP ${res.status}`);
89865
+ return await this.parseResponse(res, id);
89866
+ } catch (err) {
89867
+ this.reset();
89868
+ throw err;
89869
+ }
89870
+ }
89871
+ }
89872
+ function isPreDeliveryError(err) {
89873
+ const PRE_DELIVERY_CODES = new Set([
89874
+ "ECONNREFUSED",
89875
+ "ENOTFOUND",
89876
+ "EAI_AGAIN",
89877
+ "UND_ERR_CONNECT_TIMEOUT"
89878
+ ]);
89879
+ let cur = err;
89880
+ for (let depth = 0;cur && depth < 5; depth++) {
89881
+ const code = cur.code;
89882
+ if (typeof code === "string" && PRE_DELIVERY_CODES.has(code))
89883
+ return true;
89884
+ const errors2 = cur.errors;
89885
+ if (Array.isArray(errors2) && errors2.length > 0) {
89886
+ return errors2.every((e) => isPreDeliveryError(e));
89887
+ }
89888
+ cur = cur.cause;
89889
+ }
89890
+ return false;
89891
+ }
89892
+
89893
+ class HindsightShim {
89894
+ opts;
89895
+ upstream;
89896
+ log;
89897
+ constructor(opts) {
89898
+ this.opts = opts;
89899
+ this.upstream = new UpstreamClient(opts);
89900
+ this.log = opts.logger ?? ((l) => process.stderr.write(l + `
89901
+ `));
89902
+ }
89903
+ get cachePath() {
89904
+ return join81(this.opts.cacheDir, TOOLS_CACHE_FILENAME);
89905
+ }
89906
+ writeCache(result) {
89907
+ try {
89908
+ mkdirSync45(this.opts.cacheDir, { recursive: true });
89909
+ const tmp = join81(this.opts.cacheDir, `.${TOOLS_CACHE_FILENAME}.${process.pid}.tmp`);
89910
+ writeFileSync27(tmp, JSON.stringify(result, null, 2) + `
89911
+ `);
89912
+ renameSync19(tmp, this.cachePath);
89913
+ } catch (err) {
89914
+ this.log(`[hindsight-shim] cache write failed: ${String(err)}`);
89915
+ }
89916
+ }
89917
+ readCache() {
89918
+ try {
89919
+ const parsed = JSON.parse(readFileSync70(this.cachePath, "utf-8"));
89920
+ if (Array.isArray(parsed.tools))
89921
+ return parsed;
89922
+ return null;
89923
+ } catch {
89924
+ return null;
89925
+ }
89926
+ }
89927
+ async handle(msg) {
89928
+ const { id, method } = msg;
89929
+ if (method === undefined)
89930
+ return null;
89931
+ if (id === undefined || id === null) {
89932
+ if (method !== "notifications/initialized" && this.upstream.connected) {
89933
+ this.upstream.request(method, msg.params, this.opts.connectTimeoutMs ?? UPSTREAM_CONNECT_TIMEOUT_MS, false).catch(() => {
89934
+ return;
89935
+ });
89936
+ }
89937
+ return null;
89938
+ }
89939
+ switch (method) {
89940
+ case "initialize":
89941
+ return { jsonrpc: "2.0", id, result: this.initializeResult(msg.params) };
89942
+ case "tools/list":
89943
+ return { jsonrpc: "2.0", id, result: await this.toolsList(msg.params) };
89944
+ case "tools/call":
89945
+ return { jsonrpc: "2.0", id, result: await this.toolsCall(msg.params) };
89946
+ case "ping":
89947
+ return { jsonrpc: "2.0", id, result: {} };
89948
+ default:
89949
+ return this.forward(msg);
89950
+ }
89951
+ }
89952
+ initializeResult(params) {
89953
+ const requested = params?.protocolVersion;
89954
+ const version2 = requested && SHIM_SUPPORTED_PROTOCOL_VERSIONS.includes(requested) ? requested : SHIM_SUPPORTED_PROTOCOL_VERSIONS[0];
89955
+ return {
89956
+ protocolVersion: version2,
89957
+ capabilities: { tools: { listChanged: true } },
89958
+ serverInfo: { name: "switchroom-hindsight-shim", version: "1.0.0" }
89959
+ };
89960
+ }
89961
+ staleManifestServed = false;
89962
+ notificationSink = () => {
89963
+ return;
89964
+ };
89965
+ notifyIfRecovered() {
89966
+ if (!this.staleManifestServed)
89967
+ return;
89968
+ this.staleManifestServed = false;
89969
+ this.notificationSink({
89970
+ jsonrpc: "2.0",
89971
+ method: "notifications/tools/list_changed"
89972
+ });
89973
+ }
89974
+ async toolsList(params) {
89975
+ try {
89976
+ const res = await this.upstream.request("tools/list", params ?? {}, this.opts.toolsListTimeoutMs ?? TOOLS_LIST_TIMEOUT_MS);
89977
+ if (res.error)
89978
+ throw new Error(res.error.message);
89979
+ const result = res.result;
89980
+ if (Array.isArray(result?.tools)) {
89981
+ this.writeCache(result);
89982
+ this.staleManifestServed = false;
89983
+ return result;
89984
+ }
89985
+ throw new Error("upstream tools/list returned no tools array");
89986
+ } catch (err) {
89987
+ this.log(`[hindsight-shim] live tools/list failed (${String(err)}); serving ` + "cached/fallback manifest");
89988
+ this.staleManifestServed = true;
89989
+ return this.readCache() ?? buildFallbackToolsList();
89990
+ }
89991
+ }
89992
+ async toolsCall(params) {
89993
+ try {
89994
+ const res = await this.upstream.request("tools/call", params, this.opts.toolsCallTimeoutMs ?? TOOLS_CALL_TIMEOUT_MS);
89995
+ this.notifyIfRecovered();
89996
+ if (res.error) {
89997
+ return {
89998
+ content: [
89999
+ { type: "text", text: `Hindsight returned an error: ${res.error.message}` }
90000
+ ],
90001
+ isError: true
90002
+ };
90003
+ }
90004
+ return res.result;
90005
+ } catch (err) {
90006
+ const name = params?.name ?? "unknown";
90007
+ this.log(`[hindsight-shim] tools/call ${name} failed after retry: ${String(err)}`);
90008
+ return {
90009
+ content: [
90010
+ {
90011
+ type: "text",
90012
+ text: "Hindsight memory is temporarily unavailable (backend at " + `${this.opts.url} is not reachable: ${String(err)}). ` + "This is transient - the shim reconnects automatically, so " + "simply retry this tool call shortly. Do not treat memory " + "as permanently lost."
90013
+ }
90014
+ ],
90015
+ isError: true
90016
+ };
90017
+ }
90018
+ }
90019
+ async forward(msg) {
90020
+ try {
90021
+ const res = await this.upstream.request(msg.method, msg.params, this.opts.toolsListTimeoutMs ?? TOOLS_LIST_TIMEOUT_MS);
90022
+ return { jsonrpc: "2.0", id: msg.id, result: res.result, ...res.error ? { error: res.error } : {} };
90023
+ } catch (err) {
90024
+ return {
90025
+ jsonrpc: "2.0",
90026
+ id: msg.id,
90027
+ error: {
90028
+ code: -32001,
90029
+ message: `hindsight backend unavailable: ${String(err)}`
90030
+ }
90031
+ };
90032
+ }
90033
+ }
90034
+ async run(input, output) {
90035
+ this.notificationSink = (msg) => output.write(JSON.stringify(msg) + `
90036
+ `);
90037
+ const rl = createInterface6({ input, crlfDelay: Infinity });
90038
+ const inflight = new Set;
90039
+ rl.on("line", (line) => {
90040
+ const trimmed = line.trim();
90041
+ if (!trimmed)
90042
+ return;
90043
+ let msg;
90044
+ try {
90045
+ msg = JSON.parse(trimmed);
90046
+ } catch {
90047
+ this.log(`[hindsight-shim] dropping non-JSON stdin line`);
90048
+ return;
90049
+ }
90050
+ const task = (async () => {
90051
+ try {
90052
+ const res = await this.handle(msg);
90053
+ if (res)
90054
+ output.write(JSON.stringify(res) + `
90055
+ `);
90056
+ } catch (err) {
90057
+ this.log(`[hindsight-shim] handler threw: ${String(err)}`);
90058
+ if (msg.id !== undefined && msg.id !== null) {
90059
+ output.write(JSON.stringify({
90060
+ jsonrpc: "2.0",
90061
+ id: msg.id,
90062
+ error: { code: -32603, message: `shim internal error: ${String(err)}` }
90063
+ }) + `
90064
+ `);
90065
+ }
90066
+ }
90067
+ })();
90068
+ inflight.add(task);
90069
+ task.finally(() => inflight.delete(task));
90070
+ });
90071
+ await new Promise((resolve51) => rl.on("close", resolve51));
90072
+ while (inflight.size > 0) {
90073
+ await Promise.allSettled([...inflight]);
90074
+ }
90075
+ }
90076
+ }
90077
+ function resolveShimOptionsFromEnv(env2) {
90078
+ const home2 = env2.HOME && env2.HOME !== "/" ? env2.HOME : tmpdir5();
90079
+ return {
90080
+ url: env2.HINDSIGHT_MCP_URL || HINDSIGHT_DEFAULT_MCP_URL,
90081
+ bankId: env2.HINDSIGHT_BANK_ID || "",
90082
+ cacheDir: env2.HINDSIGHT_SHIM_CACHE_DIR || join81(home2, ".hindsight-shim")
90083
+ };
90084
+ }
90085
+ function registerHindsightMcpShimCommand(program3) {
90086
+ program3.command("hindsight-mcp-shim", { hidden: true }).description("Internal: lazy-connect stdio MCP proxy for the Hindsight memory " + "backend. Spawned as the `hindsight` MCP command inside agents.").action(async () => {
90087
+ const shim = new HindsightShim(resolveShimOptionsFromEnv(process.env));
90088
+ await shim.run(process.stdin, process.stdout);
90089
+ process.exit(0);
90090
+ });
90091
+ }
90092
+
89649
90093
  // src/cli/deliver-file.ts
89650
90094
  init_client2();
89651
- import { readFileSync as readFileSync70, statSync as statSync46 } from "node:fs";
90095
+ import { readFileSync as readFileSync71, statSync as statSync46 } from "node:fs";
89652
90096
  import { basename as basename11 } from "node:path";
89653
90097
 
89654
90098
  // src/delivery/onedrive.ts
@@ -89988,7 +90432,7 @@ async function defaultResolveProvider() {
89988
90432
  async function runDeliverFile(localPath, deps = {}) {
89989
90433
  const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
89990
90434
  const sizeOf = deps.fileSize ?? ((p) => statSync46(p).size);
89991
- const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync70(p)));
90435
+ const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync71(p)));
89992
90436
  const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
89993
90437
  let size;
89994
90438
  try {
@@ -90309,8 +90753,8 @@ function runRedactStdin() {
90309
90753
  }
90310
90754
 
90311
90755
  // src/cli/status-ask.ts
90312
- import { readFileSync as readFileSync74, existsSync as existsSync87, readdirSync as readdirSync30 } from "node:fs";
90313
- import { join as join85 } from "node:path";
90756
+ import { readFileSync as readFileSync75, existsSync as existsSync87, readdirSync as readdirSync30 } from "node:fs";
90757
+ import { join as join86 } from "node:path";
90314
90758
  import { homedir as homedir50 } from "node:os";
90315
90759
 
90316
90760
  // src/status-ask/report.ts
@@ -90585,7 +91029,7 @@ function runReport(opts) {
90585
91029
  for (const src of sources) {
90586
91030
  let content;
90587
91031
  try {
90588
- content = readFileSync74(src.path, "utf-8");
91032
+ content = readFileSync75(src.path, "utf-8");
90589
91033
  } catch (err) {
90590
91034
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
90591
91035
  `);
@@ -90646,7 +91090,7 @@ function resolveSources(explicitPath) {
90646
91090
  const config = loadConfig();
90647
91091
  agentsDir = resolveAgentsDir(config);
90648
91092
  } catch {
90649
- agentsDir = join85(homedir50(), ".switchroom", "agents");
91093
+ agentsDir = join86(homedir50(), ".switchroom", "agents");
90650
91094
  }
90651
91095
  if (!existsSync87(agentsDir))
90652
91096
  return [];
@@ -90658,7 +91102,7 @@ function resolveSources(explicitPath) {
90658
91102
  return [];
90659
91103
  }
90660
91104
  for (const name of entries) {
90661
- const path9 = join85(agentsDir, name, "runtime-metrics.jsonl");
91105
+ const path9 = join86(agentsDir, name, "runtime-metrics.jsonl");
90662
91106
  if (existsSync87(path9)) {
90663
91107
  sources.push({ path: path9, agent: name });
90664
91108
  }
@@ -90691,43 +91135,43 @@ import {
90691
91135
  closeSync as closeSync14,
90692
91136
  existsSync as existsSync88,
90693
91137
  fsyncSync as fsyncSync8,
90694
- mkdirSync as mkdirSync49,
91138
+ mkdirSync as mkdirSync50,
90695
91139
  openSync as openSync14,
90696
91140
  readdirSync as readdirSync31,
90697
- readFileSync as readFileSync75,
90698
- renameSync as renameSync20,
91141
+ readFileSync as readFileSync76,
91142
+ renameSync as renameSync21,
90699
91143
  statSync as statSync48,
90700
91144
  unlinkSync as unlinkSync18,
90701
91145
  writeSync as writeSync10
90702
91146
  } from "node:fs";
90703
- import { join as join86, resolve as resolve53 } from "node:path";
91147
+ import { join as join87, resolve as resolve53 } from "node:path";
90704
91148
  var STAGING_SUBDIR = ".staging";
90705
91149
  function overlayPathsFor(agent, opts = {}) {
90706
91150
  const base = opts.root ? resolve53(opts.root, agent) : resolve53(resolveDualPath(`~/.switchroom/agents/${agent}`));
90707
- const scheduleDir = join86(base, "schedule.d");
90708
- const scheduleStagingDir = join86(scheduleDir, STAGING_SUBDIR);
90709
- const skillsDir = join86(base, "skills.d");
90710
- const skillsStagingDir = join86(skillsDir, STAGING_SUBDIR);
91151
+ const scheduleDir = join87(base, "schedule.d");
91152
+ const scheduleStagingDir = join87(scheduleDir, STAGING_SUBDIR);
91153
+ const skillsDir = join87(base, "skills.d");
91154
+ const skillsStagingDir = join87(skillsDir, STAGING_SUBDIR);
90711
91155
  return {
90712
91156
  agentRoot: base,
90713
91157
  scheduleDir,
90714
91158
  scheduleStagingDir,
90715
91159
  skillsDir,
90716
91160
  skillsStagingDir,
90717
- lockPath: join86(base, ".lock"),
91161
+ lockPath: join87(base, ".lock"),
90718
91162
  stagingDir: scheduleStagingDir
90719
91163
  };
90720
91164
  }
90721
91165
  function ensureDirs(paths) {
90722
- mkdirSync49(paths.scheduleDir, { recursive: true });
90723
- mkdirSync49(paths.scheduleStagingDir, { recursive: true });
91166
+ mkdirSync50(paths.scheduleDir, { recursive: true });
91167
+ mkdirSync50(paths.scheduleStagingDir, { recursive: true });
90724
91168
  }
90725
91169
  function ensureSkillsDirs(paths) {
90726
- mkdirSync49(paths.skillsDir, { recursive: true });
90727
- mkdirSync49(paths.skillsStagingDir, { recursive: true });
91170
+ mkdirSync50(paths.skillsDir, { recursive: true });
91171
+ mkdirSync50(paths.skillsStagingDir, { recursive: true });
90728
91172
  }
90729
91173
  function withAgentLock(paths, fn) {
90730
- mkdirSync49(paths.agentRoot, { recursive: true });
91174
+ mkdirSync50(paths.agentRoot, { recursive: true });
90731
91175
  const start = Date.now();
90732
91176
  const TIMEOUT_MS = 5000;
90733
91177
  let fd = null;
@@ -90768,8 +91212,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
90768
91212
  const paths = overlayPathsFor(agent, opts);
90769
91213
  return withAgentLock(paths, () => {
90770
91214
  ensureDirs(paths);
90771
- const stagingPath = join86(paths.scheduleStagingDir, `${slug}.yaml`);
90772
- const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
91215
+ const stagingPath = join87(paths.scheduleStagingDir, `${slug}.yaml`);
91216
+ const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
90773
91217
  const fd = openSync14(stagingPath, "w", 384);
90774
91218
  try {
90775
91219
  writeSync10(fd, yamlText);
@@ -90777,7 +91221,7 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
90777
91221
  } finally {
90778
91222
  closeSync14(fd);
90779
91223
  }
90780
- renameSync20(stagingPath, finalPath);
91224
+ renameSync21(stagingPath, finalPath);
90781
91225
  return finalPath;
90782
91226
  });
90783
91227
  }
@@ -90785,8 +91229,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90785
91229
  const paths = overlayPathsFor(agent, opts);
90786
91230
  return withAgentLock(paths, () => {
90787
91231
  ensureSkillsDirs(paths);
90788
- const stagingPath = join86(paths.skillsStagingDir, `${slug}.yaml`);
90789
- const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
91232
+ const stagingPath = join87(paths.skillsStagingDir, `${slug}.yaml`);
91233
+ const finalPath = join87(paths.skillsDir, `${slug}.yaml`);
90790
91234
  const fd = openSync14(stagingPath, "w", 384);
90791
91235
  try {
90792
91236
  writeSync10(fd, yamlText);
@@ -90794,14 +91238,14 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90794
91238
  } finally {
90795
91239
  closeSync14(fd);
90796
91240
  }
90797
- renameSync20(stagingPath, finalPath);
91241
+ renameSync21(stagingPath, finalPath);
90798
91242
  return finalPath;
90799
91243
  });
90800
91244
  }
90801
91245
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
90802
91246
  const paths = overlayPathsFor(agent, opts);
90803
91247
  return withAgentLock(paths, () => {
90804
- const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
91248
+ const finalPath = join87(paths.skillsDir, `${slug}.yaml`);
90805
91249
  if (!existsSync88(finalPath))
90806
91250
  return false;
90807
91251
  unlinkSync18(finalPath);
@@ -90816,9 +91260,9 @@ function listSkillsOverlayEntries(agent, opts = {}) {
90816
91260
  for (const name of readdirSync31(paths.skillsDir)) {
90817
91261
  if (!/\.ya?ml$/i.test(name))
90818
91262
  continue;
90819
- const full = join86(paths.skillsDir, name);
91263
+ const full = join87(paths.skillsDir, name);
90820
91264
  try {
90821
- const raw = readFileSync75(full, "utf-8");
91265
+ const raw = readFileSync76(full, "utf-8");
90822
91266
  const slug = name.replace(/\.ya?ml$/i, "");
90823
91267
  out.push({ slug, path: full, raw });
90824
91268
  } catch {}
@@ -90828,7 +91272,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
90828
91272
  function deleteOverlayEntry(agent, slug, opts = {}) {
90829
91273
  const paths = overlayPathsFor(agent, opts);
90830
91274
  return withAgentLock(paths, () => {
90831
- const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
91275
+ const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
90832
91276
  if (!existsSync88(finalPath))
90833
91277
  return false;
90834
91278
  unlinkSync18(finalPath);
@@ -90843,9 +91287,9 @@ function listOverlayEntries(agent, opts = {}) {
90843
91287
  for (const name of readdirSync31(paths.scheduleDir)) {
90844
91288
  if (!/\.ya?ml$/i.test(name))
90845
91289
  continue;
90846
- const full = join86(paths.scheduleDir, name);
91290
+ const full = join87(paths.scheduleDir, name);
90847
91291
  try {
90848
- const raw = readFileSync75(full, "utf-8");
91292
+ const raw = readFileSync76(full, "utf-8");
90849
91293
  const slug = name.replace(/\.ya?ml$/i, "");
90850
91294
  out.push({ slug, path: full, raw });
90851
91295
  } catch {}
@@ -91067,25 +91511,25 @@ import {
91067
91511
  closeSync as closeSync15,
91068
91512
  existsSync as existsSync89,
91069
91513
  fsyncSync as fsyncSync9,
91070
- mkdirSync as mkdirSync50,
91514
+ mkdirSync as mkdirSync51,
91071
91515
  openSync as openSync15,
91072
91516
  readdirSync as readdirSync32,
91073
- readFileSync as readFileSync76,
91074
- renameSync as renameSync21,
91517
+ readFileSync as readFileSync77,
91518
+ renameSync as renameSync22,
91075
91519
  unlinkSync as unlinkSync19,
91076
- writeFileSync as writeFileSync31,
91520
+ writeFileSync as writeFileSync32,
91077
91521
  writeSync as writeSync11
91078
91522
  } from "node:fs";
91079
- import { join as join87 } from "node:path";
91523
+ import { join as join88 } from "node:path";
91080
91524
  import { randomBytes as randomBytes15 } from "node:crypto";
91081
91525
  var STAGE_ID_PREFIX = "cap_";
91082
91526
  function pendingDir(agent, opts = {}) {
91083
91527
  const paths = overlayPathsFor(agent, opts);
91084
- return join87(paths.scheduleDir, ".pending");
91528
+ return join88(paths.scheduleDir, ".pending");
91085
91529
  }
91086
91530
  function ensurePendingDir(agent, opts = {}) {
91087
91531
  const dir = pendingDir(agent, opts);
91088
- mkdirSync50(dir, { recursive: true });
91532
+ mkdirSync51(dir, { recursive: true });
91089
91533
  return dir;
91090
91534
  }
91091
91535
  function newStageId() {
@@ -91094,8 +91538,8 @@ function newStageId() {
91094
91538
  function stagePendingScheduleEntry(opts) {
91095
91539
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
91096
91540
  const stageId = opts.stageId ?? newStageId();
91097
- const yamlPath = join87(dir, `${stageId}.yaml`);
91098
- const metaPath = join87(dir, `${stageId}.meta.json`);
91541
+ const yamlPath = join88(dir, `${stageId}.yaml`);
91542
+ const metaPath = join88(dir, `${stageId}.meta.json`);
91099
91543
  const meta = {
91100
91544
  v: 1,
91101
91545
  stage_id: stageId,
@@ -91114,9 +91558,9 @@ function stagePendingScheduleEntry(opts) {
91114
91558
  } finally {
91115
91559
  closeSync15(fd);
91116
91560
  }
91117
- renameSync21(yamlTmp, yamlPath);
91561
+ renameSync22(yamlTmp, yamlPath);
91118
91562
  }
91119
- writeFileSync31(metaPath, JSON.stringify(meta, null, 2) + `
91563
+ writeFileSync32(metaPath, JSON.stringify(meta, null, 2) + `
91120
91564
  `, { mode: 384 });
91121
91565
  return { stageId, yamlPath, metaPath };
91122
91566
  }
@@ -91129,12 +91573,12 @@ function listPendingScheduleEntries(agent, opts = {}) {
91129
91573
  if (!name.endsWith(".meta.json"))
91130
91574
  continue;
91131
91575
  const stageId = name.slice(0, -".meta.json".length);
91132
- const metaPath = join87(dir, name);
91133
- const yamlPath = join87(dir, `${stageId}.yaml`);
91576
+ const metaPath = join88(dir, name);
91577
+ const yamlPath = join88(dir, `${stageId}.yaml`);
91134
91578
  if (!existsSync89(yamlPath))
91135
91579
  continue;
91136
91580
  try {
91137
- const meta = JSON.parse(readFileSync76(metaPath, "utf-8"));
91581
+ const meta = JSON.parse(readFileSync77(metaPath, "utf-8"));
91138
91582
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
91139
91583
  continue;
91140
91584
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -91149,11 +91593,11 @@ function commitPendingScheduleEntry(opts) {
91149
91593
  return { committed: false, reason: "not_found" };
91150
91594
  const slug = match.meta.entry.name ?? match.stageId;
91151
91595
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
91152
- const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
91596
+ const finalPath = join88(paths.scheduleDir, `${slug}.yaml`);
91153
91597
  if (existsSync89(finalPath)) {
91154
91598
  return { committed: false, reason: "slug_collision" };
91155
91599
  }
91156
- renameSync21(match.yamlPath, finalPath);
91600
+ renameSync22(match.yamlPath, finalPath);
91157
91601
  unlinkSync19(match.metaPath);
91158
91602
  return { committed: true, path: finalPath, slug };
91159
91603
  }
@@ -91172,7 +91616,7 @@ function denyPendingScheduleEntry(opts) {
91172
91616
  }
91173
91617
 
91174
91618
  // src/cli/agent-config-write.ts
91175
- import { existsSync as existsSync90, readFileSync as readFileSync77 } from "node:fs";
91619
+ import { existsSync as existsSync90, readFileSync as readFileSync78 } from "node:fs";
91176
91620
  import { execFileSync as execFileSync28 } from "node:child_process";
91177
91621
 
91178
91622
  // src/scheduler/schedule-report.ts
@@ -91563,7 +92007,7 @@ function scheduleRemove(opts) {
91563
92007
  let priorContent = null;
91564
92008
  try {
91565
92009
  if (existsSync90(match.path))
91566
- priorContent = readFileSync77(match.path, "utf-8");
92010
+ priorContent = readFileSync78(match.path, "utf-8");
91567
92011
  } catch {}
91568
92012
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
91569
92013
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -91766,7 +92210,7 @@ function registerAgentConfigWriteCommands(program3) {
91766
92210
  }
91767
92211
  let blob;
91768
92212
  if (opts.jsonl) {
91769
- blob = existsSync90(opts.jsonl) ? readFileSync77(opts.jsonl, "utf-8") : "";
92213
+ blob = existsSync90(opts.jsonl) ? readFileSync78(opts.jsonl, "utf-8") : "";
91770
92214
  } else {
91771
92215
  try {
91772
92216
  blob = execFileSync28("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
@@ -91802,7 +92246,7 @@ import { existsSync as existsSync91 } from "node:fs";
91802
92246
  init_reconcile_default_skills();
91803
92247
  init_agent_config();
91804
92248
  var import_yaml23 = __toESM(require_dist(), 1);
91805
- import { join as join88 } from "node:path";
92249
+ import { join as join89 } from "node:path";
91806
92250
  var MAX_SKILLS_PER_AGENT = 20;
91807
92251
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
91808
92252
  function exitCodeFor2(code) {
@@ -91877,7 +92321,7 @@ function skillInstall(opts) {
91877
92321
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
91878
92322
  }
91879
92323
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
91880
- const skillPath = join88(poolDir, skillName);
92324
+ const skillPath = join89(poolDir, skillName);
91881
92325
  if (!existsSync91(skillPath)) {
91882
92326
  return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
91883
92327
  }
@@ -92044,19 +92488,19 @@ import {
92044
92488
  closeSync as closeSync16,
92045
92489
  existsSync as existsSync92,
92046
92490
  lstatSync as lstatSync11,
92047
- mkdirSync as mkdirSync51,
92491
+ mkdirSync as mkdirSync52,
92048
92492
  mkdtempSync as mkdtempSync5,
92049
92493
  openSync as openSync16,
92050
- readFileSync as readFileSync78,
92494
+ readFileSync as readFileSync79,
92051
92495
  readdirSync as readdirSync33,
92052
92496
  realpathSync as realpathSync7,
92053
- renameSync as renameSync22,
92497
+ renameSync as renameSync23,
92054
92498
  rmSync as rmSync18,
92055
92499
  statSync as statSync49,
92056
- writeFileSync as writeFileSync32
92500
+ writeFileSync as writeFileSync33
92057
92501
  } from "node:fs";
92058
- import { tmpdir as tmpdir5, homedir as homedir51 } from "node:os";
92059
- import { dirname as dirname31, join as join89, relative as relative2, resolve as resolve54 } from "node:path";
92502
+ import { tmpdir as tmpdir6, homedir as homedir51 } from "node:os";
92503
+ import { dirname as dirname31, join as join90, relative as relative2, resolve as resolve54 } from "node:path";
92060
92504
  import { spawnSync as spawnSync16 } from "node:child_process";
92061
92505
 
92062
92506
  // src/cli/skill-common.ts
@@ -92290,7 +92734,7 @@ function scanForClaudeP2(content) {
92290
92734
  function resolveSkillsPoolDir2(override) {
92291
92735
  const raw = override ?? "~/.switchroom/skills";
92292
92736
  if (raw.startsWith("~/")) {
92293
- return join89(homedir51(), raw.slice(2));
92737
+ return join90(homedir51(), raw.slice(2));
92294
92738
  }
92295
92739
  if (raw === "~")
92296
92740
  return homedir51();
@@ -92329,7 +92773,7 @@ function loadFromDir(dir) {
92329
92773
  const walk2 = (sub) => {
92330
92774
  const entries = readdirSync33(sub, { withFileTypes: true });
92331
92775
  for (const ent of entries) {
92332
- const full = join89(sub, ent.name);
92776
+ const full = join90(sub, ent.name);
92333
92777
  const rel = relative2(abs, full);
92334
92778
  if (ent.isSymbolicLink()) {
92335
92779
  fail3(`refusing to read symlink inside --from dir: ${rel}`);
@@ -92339,7 +92783,7 @@ function loadFromDir(dir) {
92339
92783
  continue;
92340
92784
  }
92341
92785
  if (ent.isFile()) {
92342
- const buf = readFileSync78(full);
92786
+ const buf = readFileSync79(full);
92343
92787
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
92344
92788
  }
92345
92789
  }
@@ -92364,7 +92808,7 @@ function loadFromTarball(tarPath) {
92364
92808
  fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
92365
92809
  }
92366
92810
  }
92367
- const staging = mkdtempSync5(join89(tmpdir5(), "skill-apply-extract-"));
92811
+ const staging = mkdtempSync5(join90(tmpdir6(), "skill-apply-extract-"));
92368
92812
  try {
92369
92813
  const flags = isGz ? ["-xzf"] : ["-xf"];
92370
92814
  const r = spawnSync16("tar", [
@@ -92388,7 +92832,7 @@ function loadFromTarball(tarPath) {
92388
92832
  }
92389
92833
  }
92390
92834
  function loadSingleFile(filePath) {
92391
- const content = readFileSync78(filePath, "utf-8");
92835
+ const content = readFileSync79(filePath, "utf-8");
92392
92836
  return { "SKILL.md": content };
92393
92837
  }
92394
92838
  function loadFromStdin() {
@@ -92450,10 +92894,10 @@ function validatePayload(name, files) {
92450
92894
  errors2.push(`${path9} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
92451
92895
  }
92452
92896
  } else if (PY_SCRIPT_RE2.test(path9)) {
92453
- const tmp = mkdtempSync5(join89(tmpdir5(), "skill-apply-py-"));
92454
- const tmpPy = join89(tmp, "check.py");
92897
+ const tmp = mkdtempSync5(join90(tmpdir6(), "skill-apply-py-"));
92898
+ const tmpPy = join90(tmp, "check.py");
92455
92899
  try {
92456
- writeFileSync32(tmpPy, content);
92900
+ writeFileSync33(tmpPy, content);
92457
92901
  const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
92458
92902
  encoding: "utf-8"
92459
92903
  });
@@ -92474,12 +92918,12 @@ function diffSummary(currentDir, files) {
92474
92918
  if (existsSync92(currentDir)) {
92475
92919
  const walk2 = (sub) => {
92476
92920
  for (const ent of readdirSync33(sub, { withFileTypes: true })) {
92477
- const full = join89(sub, ent.name);
92921
+ const full = join90(sub, ent.name);
92478
92922
  const rel = relative2(currentDir, full);
92479
92923
  if (ent.isDirectory()) {
92480
92924
  walk2(full);
92481
92925
  } else if (ent.isFile()) {
92482
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync78(full, "utf-8");
92926
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync79(full, "utf-8");
92483
92927
  }
92484
92928
  }
92485
92929
  };
@@ -92508,9 +92952,9 @@ function diffSummary(currentDir, files) {
92508
92952
  }
92509
92953
  function writePayload(poolDir, name, files) {
92510
92954
  if (!existsSync92(poolDir)) {
92511
- mkdirSync51(poolDir, { recursive: true, mode: 493 });
92955
+ mkdirSync52(poolDir, { recursive: true, mode: 493 });
92512
92956
  }
92513
- const target = join89(poolDir, name);
92957
+ const target = join90(poolDir, name);
92514
92958
  let targetIsSymlink = false;
92515
92959
  try {
92516
92960
  const st = lstatSync11(target);
@@ -92521,15 +92965,15 @@ function writePayload(poolDir, name, files) {
92521
92965
  if (targetIsSymlink) {
92522
92966
  fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
92523
92967
  }
92524
- const staging = mkdtempSync5(join89(poolDir, `.skill-apply-stage-${name}-`));
92968
+ const staging = mkdtempSync5(join90(poolDir, `.skill-apply-stage-${name}-`));
92525
92969
  let oldRename = null;
92526
92970
  try {
92527
92971
  for (const [path9, content] of Object.entries(files)) {
92528
- const full = join89(staging, path9);
92529
- mkdirSync51(dirname31(full), { recursive: true, mode: 493 });
92972
+ const full = join90(staging, path9);
92973
+ mkdirSync52(dirname31(full), { recursive: true, mode: 493 });
92530
92974
  const fd = openSync16(full, "wx");
92531
92975
  try {
92532
- writeFileSync32(fd, content);
92976
+ writeFileSync33(fd, content);
92533
92977
  } finally {
92534
92978
  closeSync16(fd);
92535
92979
  }
@@ -92545,9 +92989,9 @@ function writePayload(poolDir, name, files) {
92545
92989
  } catch {}
92546
92990
  if (targetExists) {
92547
92991
  oldRename = `${target}.skill-apply-old-${Date.now()}`;
92548
- renameSync22(target, oldRename);
92992
+ renameSync23(target, oldRename);
92549
92993
  }
92550
- renameSync22(staging, target);
92994
+ renameSync23(staging, target);
92551
92995
  if (oldRename) {
92552
92996
  rmSync18(oldRename, { recursive: true, force: true });
92553
92997
  oldRename = null;
@@ -92561,7 +93005,7 @@ function writePayload(poolDir, name, files) {
92561
93005
  if (existsSync92(target)) {
92562
93006
  rmSync18(target, { recursive: true, force: true });
92563
93007
  }
92564
- renameSync22(oldRename, target);
93008
+ renameSync23(oldRename, target);
92565
93009
  } catch {}
92566
93010
  }
92567
93011
  throw err2;
@@ -92603,7 +93047,7 @@ function registerSkillCommand(program3) {
92603
93047
  }
92604
93048
  const config = loadConfig();
92605
93049
  const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
92606
- const currentDir = join89(poolDir, name);
93050
+ const currentDir = join90(poolDir, name);
92607
93051
  console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
92608
93052
  console.log(source_default.bold("Diff vs current pool content:"));
92609
93053
  console.log(diffSummary(currentDir, files));
@@ -92637,19 +93081,19 @@ import {
92637
93081
  closeSync as closeSync17,
92638
93082
  existsSync as existsSync93,
92639
93083
  lstatSync as lstatSync12,
92640
- mkdirSync as mkdirSync52,
93084
+ mkdirSync as mkdirSync53,
92641
93085
  mkdtempSync as mkdtempSync6,
92642
93086
  openSync as openSync17,
92643
- readFileSync as readFileSync79,
93087
+ readFileSync as readFileSync80,
92644
93088
  readdirSync as readdirSync34,
92645
- renameSync as renameSync23,
93089
+ renameSync as renameSync24,
92646
93090
  rmSync as rmSync19,
92647
93091
  statSync as statSync50,
92648
93092
  utimesSync,
92649
- writeFileSync as writeFileSync33
93093
+ writeFileSync as writeFileSync34
92650
93094
  } from "node:fs";
92651
- import { dirname as dirname32, join as join90, relative as relative3, resolve as resolve55 } from "node:path";
92652
- import { homedir as homedir52, tmpdir as tmpdir6 } from "node:os";
93095
+ import { dirname as dirname32, join as join91, relative as relative3, resolve as resolve55 } from "node:path";
93096
+ import { homedir as homedir52, tmpdir as tmpdir7 } from "node:os";
92653
93097
  import { spawnSync as spawnSync17 } from "node:child_process";
92654
93098
  init_helpers();
92655
93099
  init_agent_config();
@@ -92660,10 +93104,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
92660
93104
  var PERSONAL_SKILLS_SUBPATH = "personal-skills";
92661
93105
  function resolveConfigSkillsDir(agent) {
92662
93106
  const override = process.env.SWITCHROOM_CONFIG_DIR;
92663
- const candidate = override ? resolve55(override) : join90(homedir52(), ".switchroom-config");
93107
+ const candidate = override ? resolve55(override) : join91(homedir52(), ".switchroom-config");
92664
93108
  if (!existsSync93(candidate))
92665
93109
  return null;
92666
- return join90(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
93110
+ return join91(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
92667
93111
  }
92668
93112
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
92669
93113
  function sweepMirrorPriors(configSkillsRoot) {
@@ -92681,7 +93125,7 @@ function sweepMirrorPriors(configSkillsRoot) {
92681
93125
  if (now - ts < MIRROR_PRIOR_TTL_MS)
92682
93126
  continue;
92683
93127
  try {
92684
- rmSync19(join90(configSkillsRoot, ent), { recursive: true, force: true });
93128
+ rmSync19(join91(configSkillsRoot, ent), { recursive: true, force: true });
92685
93129
  } catch {}
92686
93130
  }
92687
93131
  } catch {}
@@ -92690,7 +93134,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
92690
93134
  const configSkillsRoot = resolveConfigSkillsDir(agent);
92691
93135
  if (!configSkillsRoot)
92692
93136
  return;
92693
- const dest = join90(configSkillsRoot, name);
93137
+ const dest = join91(configSkillsRoot, name);
92694
93138
  try {
92695
93139
  if (liveSkillDir !== null) {
92696
93140
  try {
@@ -92705,34 +93149,34 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
92705
93149
  if (liveSkillDir === null) {
92706
93150
  sweepMirrorPriors(configSkillsRoot);
92707
93151
  if (existsSync93(dest)) {
92708
- const trash = join90(configSkillsRoot, `.${name}-trash-${Date.now()}`);
92709
- renameSync23(dest, trash);
93152
+ const trash = join91(configSkillsRoot, `.${name}-trash-${Date.now()}`);
93153
+ renameSync24(dest, trash);
92710
93154
  }
92711
93155
  return;
92712
93156
  }
92713
- mkdirSync52(configSkillsRoot, { recursive: true, mode: 493 });
93157
+ mkdirSync53(configSkillsRoot, { recursive: true, mode: 493 });
92714
93158
  sweepMirrorPriors(configSkillsRoot);
92715
- const staging = mkdtempSync6(join90(configSkillsRoot, `.${name}-staging-`));
93159
+ const staging = mkdtempSync6(join91(configSkillsRoot, `.${name}-staging-`));
92716
93160
  const walk2 = (src, dst) => {
92717
- mkdirSync52(dst, { recursive: true, mode: 493 });
93161
+ mkdirSync53(dst, { recursive: true, mode: 493 });
92718
93162
  for (const ent of readdirSync34(src, { withFileTypes: true })) {
92719
- const s = join90(src, ent.name);
92720
- const d = join90(dst, ent.name);
93163
+ const s = join91(src, ent.name);
93164
+ const d = join91(dst, ent.name);
92721
93165
  if (ent.isSymbolicLink())
92722
93166
  continue;
92723
93167
  if (ent.isDirectory())
92724
93168
  walk2(s, d);
92725
93169
  else if (ent.isFile()) {
92726
- writeFileSync33(d, readFileSync79(s));
93170
+ writeFileSync34(d, readFileSync80(s));
92727
93171
  }
92728
93172
  }
92729
93173
  };
92730
93174
  walk2(liveSkillDir, staging);
92731
93175
  if (existsSync93(dest)) {
92732
- const prior = join90(configSkillsRoot, `.${name}-prior-${Date.now()}`);
92733
- renameSync23(dest, prior);
93176
+ const prior = join91(configSkillsRoot, `.${name}-prior-${Date.now()}`);
93177
+ renameSync24(dest, prior);
92734
93178
  }
92735
- renameSync23(staging, dest);
93179
+ renameSync24(staging, dest);
92736
93180
  } catch (err2) {
92737
93181
  process.stderr.write(source_default.yellow(`warning: mirror to ${dest} failed (${err2.message ?? err2}); ` + `live copy still works, but this skill is not version-controlled until next successful sync.
92738
93182
  `));
@@ -92759,16 +93203,16 @@ function resolveAgent(opts) {
92759
93203
  function resolveAgentsRoot(opts) {
92760
93204
  if (opts.root)
92761
93205
  return resolve55(opts.root);
92762
- return join90(homedir52(), ".switchroom", "agents");
93206
+ return join91(homedir52(), ".switchroom", "agents");
92763
93207
  }
92764
93208
  function personalSkillDir(agentsRoot, agent, name) {
92765
- return join90(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
93209
+ return join91(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
92766
93210
  }
92767
93211
  function trashDir(agentsRoot, agent) {
92768
- return join90(agentsRoot, agent, ".claude", TRASH_DIRNAME);
93212
+ return join91(agentsRoot, agent, ".claude", TRASH_DIRNAME);
92769
93213
  }
92770
93214
  function countPersonalSkills(agentsRoot, agent) {
92771
- const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
93215
+ const skillsDir = join91(agentsRoot, agent, ".claude", "skills");
92772
93216
  if (!existsSync93(skillsDir))
92773
93217
  return 0;
92774
93218
  let n = 0;
@@ -92806,7 +93250,7 @@ function loadFromDir2(dir) {
92806
93250
  const files = {};
92807
93251
  const walk2 = (sub) => {
92808
93252
  for (const ent of readdirSync34(sub, { withFileTypes: true })) {
92809
- const full = join90(sub, ent.name);
93253
+ const full = join91(sub, ent.name);
92810
93254
  if (ent.isSymbolicLink()) {
92811
93255
  fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
92812
93256
  }
@@ -92816,7 +93260,7 @@ function loadFromDir2(dir) {
92816
93260
  }
92817
93261
  if (ent.isFile()) {
92818
93262
  const rel = relative3(abs, full).replace(/\\/g, "/");
92819
- files[rel] = readFileSync79(full, "utf-8");
93263
+ files[rel] = readFileSync80(full, "utf-8");
92820
93264
  }
92821
93265
  }
92822
93266
  };
@@ -92859,10 +93303,10 @@ function behavioralValidate(files) {
92859
93303
  errors2.push(`${path9} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
92860
93304
  }
92861
93305
  } else if (PY_SCRIPT_RE.test(path9)) {
92862
- const tmp = mkdtempSync6(join90(tmpdir6(), "skill-personal-py-"));
92863
- const tmpPy = join90(tmp, "check.py");
93306
+ const tmp = mkdtempSync6(join91(tmpdir7(), "skill-personal-py-"));
93307
+ const tmpPy = join91(tmp, "check.py");
92864
93308
  try {
92865
- writeFileSync33(tmpPy, content);
93309
+ writeFileSync34(tmpPy, content);
92866
93310
  const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
92867
93311
  encoding: "utf-8"
92868
93312
  });
@@ -92884,7 +93328,7 @@ function sweepTrash(agentsRoot, agent) {
92884
93328
  for (const ent of readdirSync34(trash, { withFileTypes: true })) {
92885
93329
  if (!ent.isDirectory())
92886
93330
  continue;
92887
- const entPath = join90(trash, ent.name);
93331
+ const entPath = join91(trash, ent.name);
92888
93332
  try {
92889
93333
  const st = statSync50(entPath);
92890
93334
  if (now - st.mtimeMs > TRASH_TTL_MS) {
@@ -92904,16 +93348,16 @@ function writePersonalSkill(targetDir, files) {
92904
93348
  if (targetIsSymlink) {
92905
93349
  fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
92906
93350
  }
92907
- mkdirSync52(dirname32(targetDir), { recursive: true, mode: 493 });
92908
- const staging = mkdtempSync6(join90(dirname32(targetDir), `.skill-personal-stage-`));
93351
+ mkdirSync53(dirname32(targetDir), { recursive: true, mode: 493 });
93352
+ const staging = mkdtempSync6(join91(dirname32(targetDir), `.skill-personal-stage-`));
92909
93353
  let oldRename = null;
92910
93354
  try {
92911
93355
  for (const [path9, content] of Object.entries(files)) {
92912
- const full = join90(staging, path9);
92913
- mkdirSync52(dirname32(full), { recursive: true, mode: 493 });
93356
+ const full = join91(staging, path9);
93357
+ mkdirSync53(dirname32(full), { recursive: true, mode: 493 });
92914
93358
  const fd = openSync17(full, "wx");
92915
93359
  try {
92916
- writeFileSync33(fd, content);
93360
+ writeFileSync34(fd, content);
92917
93361
  } finally {
92918
93362
  closeSync17(fd);
92919
93363
  }
@@ -92929,9 +93373,9 @@ function writePersonalSkill(targetDir, files) {
92929
93373
  } catch {}
92930
93374
  if (targetExists) {
92931
93375
  oldRename = `${targetDir}.personal-old-${Date.now()}`;
92932
- renameSync23(targetDir, oldRename);
93376
+ renameSync24(targetDir, oldRename);
92933
93377
  }
92934
- renameSync23(staging, targetDir);
93378
+ renameSync24(staging, targetDir);
92935
93379
  if (oldRename) {
92936
93380
  rmSync19(oldRename, { recursive: true, force: true });
92937
93381
  oldRename = null;
@@ -92945,7 +93389,7 @@ function writePersonalSkill(targetDir, files) {
92945
93389
  if (existsSync93(targetDir)) {
92946
93390
  rmSync19(targetDir, { recursive: true, force: true });
92947
93391
  }
92948
- renameSync23(oldRename, targetDir);
93392
+ renameSync24(oldRename, targetDir);
92949
93393
  } catch {}
92950
93394
  }
92951
93395
  throw err2;
@@ -93016,7 +93460,7 @@ function loadFiles(opts) {
93016
93460
  return loadFromDir2(p);
93017
93461
  }
93018
93462
  if (p.endsWith(".md")) {
93019
- return { "SKILL.md": readFileSync79(p, "utf-8") };
93463
+ return { "SKILL.md": readFileSync80(p, "utf-8") };
93020
93464
  }
93021
93465
  fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
93022
93466
  }
@@ -93056,10 +93500,10 @@ function editPersonalAction(name, opts) {
93056
93500
  }
93057
93501
  var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
93058
93502
  function defaultSharedRoot() {
93059
- return join90(homedir52(), ".switchroom", "skills");
93503
+ return join91(homedir52(), ".switchroom", "skills");
93060
93504
  }
93061
93505
  function defaultBundledRoot() {
93062
- return join90(homedir52(), ".switchroom", "skills", "_bundled");
93506
+ return join91(homedir52(), ".switchroom", "skills", "_bundled");
93063
93507
  }
93064
93508
  function resolveCloneSource(source, opts) {
93065
93509
  const m = CLONE_SOURCE_RE.exec(source);
@@ -93069,7 +93513,7 @@ function resolveCloneSource(source, opts) {
93069
93513
  const tier = m[1];
93070
93514
  const slug = m[2];
93071
93515
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
93072
- const dir = join90(root, slug);
93516
+ const dir = join91(root, slug);
93073
93517
  if (!existsSync93(dir)) {
93074
93518
  fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
93075
93519
  }
@@ -93085,7 +93529,7 @@ function readSourceFiles(dir) {
93085
93529
  const skipped = [];
93086
93530
  const walk2 = (sub) => {
93087
93531
  for (const ent of readdirSync34(sub, { withFileTypes: true })) {
93088
- const full = join90(sub, ent.name);
93532
+ const full = join91(sub, ent.name);
93089
93533
  if (ent.isSymbolicLink()) {
93090
93534
  continue;
93091
93535
  }
@@ -93105,7 +93549,7 @@ function readSourceFiles(dir) {
93105
93549
  fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
93106
93550
  }
93107
93551
  } catch {}
93108
- files[rel] = readFileSync79(full, "utf-8");
93552
+ files[rel] = readFileSync80(full, "utf-8");
93109
93553
  }
93110
93554
  }
93111
93555
  };
@@ -93194,10 +93638,10 @@ function removePersonalAction(name, opts) {
93194
93638
  throw err2;
93195
93639
  }
93196
93640
  const trashRoot2 = trashDir(agentsRoot, agent);
93197
- mkdirSync52(trashRoot2, { recursive: true, mode: 493 });
93641
+ mkdirSync53(trashRoot2, { recursive: true, mode: 493 });
93198
93642
  const ts = Date.now();
93199
- const trashTarget = join90(trashRoot2, `${name}-${ts}`);
93200
- renameSync23(target, trashTarget);
93643
+ const trashTarget = join91(trashRoot2, `${name}-${ts}`);
93644
+ renameSync24(target, trashTarget);
93201
93645
  const now = new Date(ts);
93202
93646
  utimesSync(trashTarget, now, now);
93203
93647
  mirrorToConfigRepo(agent, name, null);
@@ -93215,7 +93659,7 @@ function listPersonalAction(opts) {
93215
93659
  const agent = resolveAgent(opts);
93216
93660
  const agentsRoot = resolveAgentsRoot(opts);
93217
93661
  sweepTrash(agentsRoot, agent);
93218
- const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
93662
+ const skillsDir = join91(agentsRoot, agent, ".claude", "skills");
93219
93663
  const personal = [];
93220
93664
  if (existsSync93(skillsDir)) {
93221
93665
  for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
@@ -93224,7 +93668,7 @@ function listPersonalAction(opts) {
93224
93668
  if (!ent.name.startsWith(PERSONAL_PREFIX))
93225
93669
  continue;
93226
93670
  const skillName = ent.name.slice(PERSONAL_PREFIX.length);
93227
- const skillPath = join90(skillsDir, ent.name);
93671
+ const skillPath = join91(skillsDir, ent.name);
93228
93672
  let fileCount = 0;
93229
93673
  let totalBytes = 0;
93230
93674
  const walk2 = (sub) => {
@@ -93232,10 +93676,10 @@ function listPersonalAction(opts) {
93232
93676
  if (e.isFile()) {
93233
93677
  fileCount += 1;
93234
93678
  try {
93235
- totalBytes += statSync50(join90(sub, e.name)).size;
93679
+ totalBytes += statSync50(join91(sub, e.name)).size;
93236
93680
  } catch {}
93237
93681
  } else if (e.isDirectory()) {
93238
- walk2(join90(sub, e.name));
93682
+ walk2(join91(sub, e.name));
93239
93683
  }
93240
93684
  }
93241
93685
  };
@@ -93274,11 +93718,11 @@ function registerSkillPersonalCommands(program3) {
93274
93718
  // src/cli/self-improve-propose-skill.ts
93275
93719
  import { createConnection as createConnection4 } from "node:net";
93276
93720
  import { homedir as homedir53 } from "node:os";
93277
- import { join as join91 } from "node:path";
93278
- import { readFileSync as readFileSync80 } from "node:fs";
93721
+ import { join as join92 } from "node:path";
93722
+ import { readFileSync as readFileSync81 } from "node:fs";
93279
93723
  var IPC_CONNECT_TIMEOUT_MS = 5000;
93280
93724
  function gatewaySocketPath() {
93281
- return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join91(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join91(homedir53(), ".claude", "channels", "telegram", "gateway.sock"));
93725
+ return process.env.SWITCHROOM_GATEWAY_SOCKET ?? (process.env.TELEGRAM_STATE_DIR ? join92(process.env.TELEGRAM_STATE_DIR, "gateway.sock") : join92(homedir53(), ".claude", "channels", "telegram", "gateway.sock"));
93282
93726
  }
93283
93727
  function fail5(msg, code = 1) {
93284
93728
  console.error(msg);
@@ -93313,7 +93757,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
93313
93757
  fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
93314
93758
  let draft;
93315
93759
  try {
93316
- draft = JSON.parse(readFileSync80(opts.draft, "utf-8"));
93760
+ draft = JSON.parse(readFileSync81(opts.draft, "utf-8"));
93317
93761
  } catch (e) {
93318
93762
  fail5(`failed to read/parse --draft: ${e.message}`);
93319
93763
  }
@@ -93350,9 +93794,9 @@ function registerSelfImproveProposeSkillCommand(program3) {
93350
93794
  init_esm();
93351
93795
  init_helpers();
93352
93796
  var import_yaml25 = __toESM(require_dist(), 1);
93353
- import { existsSync as existsSync94, readdirSync as readdirSync35, readFileSync as readFileSync81, statSync as statSync51 } from "node:fs";
93797
+ import { existsSync as existsSync94, readdirSync as readdirSync35, readFileSync as readFileSync82, statSync as statSync51 } from "node:fs";
93354
93798
  import { homedir as homedir54 } from "node:os";
93355
- import { join as join92, resolve as resolve56 } from "node:path";
93799
+ import { join as join93, resolve as resolve56 } from "node:path";
93356
93800
  var PERSONAL_PREFIX2 = "personal-";
93357
93801
  var BUNDLED_SUBDIR = "_bundled";
93358
93802
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
@@ -93366,12 +93810,12 @@ function defaultBundledRoot2() {
93366
93810
  return resolve56(homedir54(), ".switchroom/skills/_bundled");
93367
93811
  }
93368
93812
  function readSkillFrontmatter(skillDir) {
93369
- const mdPath = join92(skillDir, "SKILL.md");
93813
+ const mdPath = join93(skillDir, "SKILL.md");
93370
93814
  if (!existsSync94(mdPath))
93371
93815
  return null;
93372
93816
  let content;
93373
93817
  try {
93374
- content = readFileSync81(mdPath, "utf-8");
93818
+ content = readFileSync82(mdPath, "utf-8");
93375
93819
  } catch {
93376
93820
  return null;
93377
93821
  }
@@ -93399,7 +93843,7 @@ function readSkillFrontmatter(skillDir) {
93399
93843
  return { fm: parsed };
93400
93844
  }
93401
93845
  function statSkillMd(skillDir) {
93402
- const mdPath = join92(skillDir, "SKILL.md");
93846
+ const mdPath = join93(skillDir, "SKILL.md");
93403
93847
  try {
93404
93848
  const st = statSync51(mdPath);
93405
93849
  return { size: st.size, mtime: st.mtime.toISOString() };
@@ -93410,7 +93854,7 @@ function statSkillMd(skillDir) {
93410
93854
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
93411
93855
  if (!AGENT_NAME_RE3.test(agent))
93412
93856
  return [];
93413
- const skillsDir = join92(agentsRoot, agent, ".claude/skills");
93857
+ const skillsDir = join93(agentsRoot, agent, ".claude/skills");
93414
93858
  if (!existsSync94(skillsDir))
93415
93859
  return [];
93416
93860
  const out = [];
@@ -93423,7 +93867,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
93423
93867
  for (const ent of entries) {
93424
93868
  if (!ent.startsWith(PERSONAL_PREFIX2))
93425
93869
  continue;
93426
- const dirPath = join92(skillsDir, ent);
93870
+ const dirPath = join93(skillsDir, ent);
93427
93871
  try {
93428
93872
  if (!statSync51(dirPath).isDirectory())
93429
93873
  continue;
@@ -93463,7 +93907,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
93463
93907
  continue;
93464
93908
  if (ent.startsWith("."))
93465
93909
  continue;
93466
- const dirPath = join92(sharedRoot, ent);
93910
+ const dirPath = join93(sharedRoot, ent);
93467
93911
  try {
93468
93912
  if (!statSync51(dirPath).isDirectory())
93469
93913
  continue;
@@ -93499,7 +93943,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
93499
93943
  for (const ent of entries) {
93500
93944
  if (ent.startsWith("."))
93501
93945
  continue;
93502
- const dirPath = join92(bundledRoot, ent);
93946
+ const dirPath = join93(bundledRoot, ent);
93503
93947
  try {
93504
93948
  if (!statSync51(dirPath).isDirectory())
93505
93949
  continue;
@@ -93646,17 +94090,17 @@ init_helpers();
93646
94090
  init_operator_uid();
93647
94091
  import {
93648
94092
  existsSync as existsSync96,
93649
- mkdirSync as mkdirSync53,
94093
+ mkdirSync as mkdirSync54,
93650
94094
  readdirSync as readdirSync36,
93651
- readFileSync as readFileSync83,
93652
- writeFileSync as writeFileSync34,
94095
+ readFileSync as readFileSync84,
94096
+ writeFileSync as writeFileSync35,
93653
94097
  statSync as statSync52,
93654
94098
  lstatSync as lstatSync13,
93655
94099
  realpathSync as realpathSync8,
93656
94100
  copyFileSync as copyFileSync13
93657
94101
  } from "node:fs";
93658
94102
  import { homedir as homedir55 } from "node:os";
93659
- import { join as join93 } from "node:path";
94103
+ import { join as join94 } from "node:path";
93660
94104
  import { spawnSync as spawnSync20 } from "node:child_process";
93661
94105
 
93662
94106
  // src/cli/singleton-stale-cleanup.ts
@@ -94005,7 +94449,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
94005
94449
  return resolved;
94006
94450
  }
94007
94451
  function resolveHostdSkillsTarget(hostHome) {
94008
- const skillsPath = join93(hostHome, ".switchroom", "skills");
94452
+ const skillsPath = join94(hostHome, ".switchroom", "skills");
94009
94453
  let st;
94010
94454
  try {
94011
94455
  st = lstatSync13(skillsPath);
@@ -94029,10 +94473,10 @@ function resolveHostdSkillsTarget(hostHome) {
94029
94473
  return target;
94030
94474
  }
94031
94475
  function hostdDir() {
94032
- return join93(homedir55(), ".switchroom", "hostd");
94476
+ return join94(homedir55(), ".switchroom", "hostd");
94033
94477
  }
94034
94478
  function hostdComposePath() {
94035
- return join93(hostdDir(), "docker-compose.yml");
94479
+ return join94(hostdDir(), "docker-compose.yml");
94036
94480
  }
94037
94481
  function backupExistingCompose() {
94038
94482
  const p = hostdComposePath();
@@ -94069,7 +94513,7 @@ async function doInstall(opts, program3) {
94069
94513
  }
94070
94514
  const dir = hostdDir();
94071
94515
  const composePath = hostdComposePath();
94072
- mkdirSync53(dir, { recursive: true });
94516
+ mkdirSync54(dir, { recursive: true });
94073
94517
  const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
94074
94518
  const guard = checkDowngrade({
94075
94519
  container: "switchroom-hostd",
@@ -94102,7 +94546,7 @@ async function doInstall(opts, program3) {
94102
94546
  const bak = backupExistingCompose();
94103
94547
  if (bak)
94104
94548
  console.log(source_default.dim(` Backed up existing compose to ${bak}`));
94105
- writeFileSync34(composePath, yaml, "utf8");
94549
+ writeFileSync35(composePath, yaml, "utf8");
94106
94550
  console.log(source_default.green(` \u2713 Wrote ${composePath}`));
94107
94551
  const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
94108
94552
  console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
@@ -94161,7 +94605,7 @@ function doStatus() {
94161
94605
  for (const name of readdirSync36(dir)) {
94162
94606
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
94163
94607
  continue;
94164
- const sockPath = join93(dir, name, "sock");
94608
+ const sockPath = join94(dir, name, "sock");
94165
94609
  if (existsSync96(sockPath)) {
94166
94610
  const st = statSync52(sockPath);
94167
94611
  if ((st.mode & 61440) === 49152) {
@@ -94210,7 +94654,7 @@ function registerHostdCommand(program3) {
94210
94654
  The log is created when hostd handles its first privileged-verb request.`));
94211
94655
  return;
94212
94656
  }
94213
- const raw = readFileSync83(logPath, "utf-8");
94657
+ const raw = readFileSync84(logPath, "utf-8");
94214
94658
  const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
94215
94659
  const filters = {
94216
94660
  agent: opts.agent,
@@ -94253,9 +94697,9 @@ The log is created when hostd handles its first privileged-verb request.`));
94253
94697
  init_source();
94254
94698
  init_helpers();
94255
94699
  init_operator_uid();
94256
- import { chownSync as chownSync9, existsSync as existsSync97, mkdirSync as mkdirSync54, writeFileSync as writeFileSync35, copyFileSync as copyFileSync14 } from "node:fs";
94700
+ import { chownSync as chownSync9, existsSync as existsSync97, mkdirSync as mkdirSync55, writeFileSync as writeFileSync36, copyFileSync as copyFileSync14 } from "node:fs";
94257
94701
  import { homedir as homedir56 } from "node:os";
94258
- import { join as join94 } from "node:path";
94702
+ import { join as join95 } from "node:path";
94259
94703
  import { spawnSync as spawnSync21 } from "node:child_process";
94260
94704
  function resolveWebImageTag(explicitTag, release) {
94261
94705
  if (explicitTag)
@@ -94274,7 +94718,7 @@ function resolveWebHostHome(env2 = process.env, home2 = homedir56()) {
94274
94718
  }
94275
94719
  var WEB_COMPOSE_PROJECT = "switchroom-web";
94276
94720
  function renderWebComposeFile(opts) {
94277
- const { hostHome, imageTag, operatorUid } = opts;
94721
+ const { hostHome, imageTag, operatorUid, port } = opts;
94278
94722
  return `# AUTO-GENERATED by \`switchroom webd install\` \u2014 do not hand-edit.
94279
94723
  # Edits land at \`switchroom webd install\` time; backed up to
94280
94724
  # docker-compose.yml.bak-<ts> on overwrite.
@@ -94289,13 +94733,18 @@ services:
94289
94733
  image: ghcr.io/switchroom/switchroom-web:${imageTag}
94290
94734
  container_name: switchroom-web
94291
94735
  restart: always
94292
- # The server MUST own host loopback 127.0.0.1:8080 \u2014 the cloudflared
94736
+ # The server MUST own host loopback 127.0.0.1:${port} \u2014 the cloudflared
94293
94737
  # tunnel (GitHub webhooks) and \`tailscale serve\` (dashboard) both
94294
94738
  # reach it there, and the dashboard CSRF gate trusts the
94295
94739
  # Tailscale-User-Login header only when the request arrives on
94296
94740
  # loopback (PR #1380). Host networking makes the container's
94297
94741
  # 127.0.0.1 the host loopback, so both keep working unchanged.
94298
94742
  network_mode: host
94743
+ # Listen port comes from \`web_service.port\` in switchroom.yaml
94744
+ # (default 8080) \u2014 overriding the image CMD here is what lets a
94745
+ # custom port survive \`switchroom update\`'s compose regeneration.
94746
+ # Point cloudflared / tailscale serve at the same port if changed.
94747
+ command: ["switchroom", "web", "--port", "${port}", "--bind", "127.0.0.1"]
94299
94748
  # The webhook.sock forward is peercred-gated by each agent's gateway
94300
94749
  # to {agent uid, SWITCHROOM_WEBHOOK_RECEIVER_UID = operator uid}.
94301
94750
  # Run as any other uid \u2192 every forward is 503'd. Operator uid also
@@ -94350,10 +94799,10 @@ services:
94350
94799
  `;
94351
94800
  }
94352
94801
  function webdDir() {
94353
- return join94(homedir56(), ".switchroom", "web");
94802
+ return join95(homedir56(), ".switchroom", "web");
94354
94803
  }
94355
94804
  function webdComposePath() {
94356
- return join94(webdDir(), "docker-compose.yml");
94805
+ return join95(webdDir(), "docker-compose.yml");
94357
94806
  }
94358
94807
  function backupExistingCompose2() {
94359
94808
  const p = webdComposePath();
@@ -94382,9 +94831,10 @@ async function doInstall2(opts, program3) {
94382
94831
  }
94383
94832
  const dir = webdDir();
94384
94833
  const composePath = webdComposePath();
94385
- mkdirSync54(dir, { recursive: true });
94834
+ mkdirSync55(dir, { recursive: true });
94386
94835
  const cfg = getConfig(program3);
94387
94836
  const imageTag = resolveWebImageTag(opts.tag, cfg.release);
94837
+ const port = cfg.web_service?.port ?? 8080;
94388
94838
  const guard = checkDowngrade({
94389
94839
  container: "switchroom-web",
94390
94840
  targetTag: imageTag,
@@ -94404,7 +94854,8 @@ async function doInstall2(opts, program3) {
94404
94854
  const yaml = renderWebComposeFile({
94405
94855
  hostHome,
94406
94856
  imageTag,
94407
- operatorUid
94857
+ operatorUid,
94858
+ port
94408
94859
  });
94409
94860
  if (opts.dryRun) {
94410
94861
  console.log(source_default.dim(`# Would write: ${composePath}`));
@@ -94415,7 +94866,7 @@ async function doInstall2(opts, program3) {
94415
94866
  const bak = backupExistingCompose2();
94416
94867
  if (bak)
94417
94868
  console.log(source_default.dim(` Backed up existing compose to ${bak}`));
94418
- writeFileSync35(composePath, yaml, "utf8");
94869
+ writeFileSync36(composePath, yaml, "utf8");
94419
94870
  try {
94420
94871
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
94421
94872
  chownSync9(dir, operatorUid, operatorUid);
@@ -94425,7 +94876,7 @@ async function doInstall2(opts, program3) {
94425
94876
  }
94426
94877
  } catch {}
94427
94878
  console.log(source_default.green(` \u2713 Wrote ${composePath}`));
94428
- console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host`));
94879
+ console.log(source_default.dim(` running as uid ${operatorUid} (operator), network_mode: host, listening on 127.0.0.1:${port}`));
94429
94880
  console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${imageTag}\u2026`));
94430
94881
  const pull = runDocker2(["compose", "-p", WEB_COMPOSE_PROJECT, "-f", composePath, "pull"]);
94431
94882
  if (!pull.ok) {
@@ -94448,7 +94899,8 @@ ${up.stderr}`));
94448
94899
  console.log(source_default.dim(` Logs: docker logs switchroom-web --tail 50`));
94449
94900
  console.log(source_default.dim(` Verify: switchroom webd status`));
94450
94901
  console.log(source_default.yellow(` NOTE: the switchroom-web.service systemd unit (if still installed) also
94451
- ` + ` binds 127.0.0.1:8080. Stop it before this container can claim the port:
94902
+ ` + ` binds 127.0.0.1:8080. Stop it before this container can claim the port
94903
+ ` + ` (the container listens on 127.0.0.1:${port}, from web_service.port):
94452
94904
  ` + ` systemctl --user stop switchroom-web.service
94453
94905
  ` + ` systemctl --user disable switchroom-web.service`));
94454
94906
  }
@@ -94510,9 +94962,9 @@ function registerWebdCommand(program3) {
94510
94962
  // src/cli/host-repair.ts
94511
94963
  init_source();
94512
94964
  import { homedir as homedir57 } from "node:os";
94513
- import { join as join95 } from "node:path";
94965
+ import { join as join96 } from "node:path";
94514
94966
  var ARTIFACT_ALLOWLIST = {
94515
- dockerComposePluginDir: (home2) => join95(home2, ".docker", "cli-plugins", "docker-compose"),
94967
+ dockerComposePluginDir: (home2) => join96(home2, ".docker", "cli-plugins", "docker-compose"),
94516
94968
  stateSentinel: "/state"
94517
94969
  };
94518
94970
  function isStateBogusAutoDir(probe2) {
@@ -95003,6 +95455,7 @@ registerDriveCommand(program3);
95003
95455
  registerDriveMcpLauncherCommand(program3);
95004
95456
  registerM365McpLauncherCommand(program3);
95005
95457
  registerNotionMcpLauncherCommand(program3);
95458
+ registerHindsightMcpShimCommand(program3);
95006
95459
  registerDeliverFileCommand(program3);
95007
95460
  registerNotionCommand(program3);
95008
95461
  registerApplyCommand(program3);