switchroom 0.18.31 → 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 (65) 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 +693 -253
  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 +43 -5
  10. package/telegram-plugin/dist/gateway/gateway.js +25868 -25038
  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 +7195 -8837
  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/pending-user-notice.ts +59 -13
  22. package/telegram-plugin/subagent-watcher.ts +111 -28
  23. package/telegram-plugin/tests/backstop-delivery.test.ts +167 -0
  24. package/telegram-plugin/tests/backstop-readback-probe.test.ts +144 -0
  25. package/telegram-plugin/tests/buffer-gate-broadened.test.ts +16 -6
  26. package/telegram-plugin/tests/button-tap-turn-gated.test.ts +3 -3
  27. package/telegram-plugin/tests/captured-answer-resume.test.ts +358 -0
  28. package/telegram-plugin/tests/emission-authority-facade.test.ts +29 -19
  29. package/telegram-plugin/tests/emission-authority-ping-gate.test.ts +4 -1
  30. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +18 -7
  31. package/telegram-plugin/tests/gateway-boot-side-effect-gating.test.ts +249 -0
  32. package/telegram-plugin/tests/gateway-bot-construction-deferral.test.ts +251 -0
  33. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +5 -128
  34. package/telegram-plugin/tests/gateway-handler-registration-wiring.test.ts +303 -0
  35. package/telegram-plugin/tests/gateway-outbound-redact.test.ts +10 -3
  36. package/telegram-plugin/tests/inbound-delivery-cutover-flip.test.ts +54 -150
  37. package/telegram-plugin/tests/inbound-delivery-cutover-gate.test.ts +10 -14
  38. package/telegram-plugin/tests/inbound-delivery-dispatch-equivalence.test.ts +6 -7
  39. package/telegram-plugin/tests/inbound-delivery-machine-dispatch.test.ts +0 -16
  40. package/telegram-plugin/tests/inbound-emit-after-intercepts.test.ts +4 -4
  41. package/telegram-plugin/tests/litellm-proxy-auth-misconfig.test.ts +69 -14
  42. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +9 -3
  43. package/telegram-plugin/tests/obligation-ledger.test.ts +40 -0
  44. package/telegram-plugin/tests/obligation-store.test.ts +43 -0
  45. package/telegram-plugin/tests/pending-card-durability-wiring.test.ts +2 -1
  46. package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +9 -6
  47. package/telegram-plugin/tests/photo-reroute-wiring.test.ts +5 -2
  48. package/telegram-plugin/tests/reply-terminal-reaction.test.ts +6 -2
  49. package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +3 -2
  50. package/telegram-plugin/tests/send-reply-golden.test.ts +571 -0
  51. package/telegram-plugin/tests/subagent-watcher-resume-reregister.test.ts +291 -0
  52. package/telegram-plugin/tests/subagent-watcher-resurrection.test.ts +32 -0
  53. package/telegram-plugin/tests/turn-end-gate-backstop.test.ts +8 -12
  54. package/telegram-plugin/tests/turn-flush-safety.test.ts +8 -6
  55. package/telegram-plugin/tests/turn-flush-suppression-wiring.test.ts +112 -0
  56. package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +2 -2
  57. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +126 -0
  58. package/telegram-plugin/tool-activity-summary.ts +27 -3
  59. package/telegram-plugin/worker-activity-feed.ts +24 -0
  60. package/telegram-plugin/gateway/busy-key-reaper.ts +0 -113
  61. package/telegram-plugin/gateway/gate-parity-probe.ts +0 -102
  62. package/telegram-plugin/tests/busy-key-reaper.test.ts +0 -192
  63. package/telegram-plugin/tests/fixtures/cutover-killswitch-probe.ts +0 -75
  64. package/telegram-plugin/tests/gate-parity-probe.test.ts +0 -171
  65. 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.31", COMMIT_SHA = "486b55f9";
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();
@@ -47789,8 +47804,8 @@ var init_fleet_defaults = __esm(() => {
47789
47804
  });
47790
47805
 
47791
47806
  // src/agents/connection-health.ts
47792
- import { mkdirSync as mkdirSync45, writeFileSync as writeFileSync27 } from "node:fs";
47793
- 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";
47794
47809
  async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
47795
47810
  const reqs = computeMcpSecretRequirements(config).filter((r) => r.agent === agentName);
47796
47811
  if (reqs.length === 0)
@@ -47847,10 +47862,10 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
47847
47862
  return issues;
47848
47863
  }
47849
47864
  function writeConnectionHealthFile(agentDir, health, deps) {
47850
- const dir = join81(agentDir, ".claude");
47851
- const path8 = join81(dir, CONNECTION_HEALTH_FILENAME);
47852
- (deps?.mkdir ?? ((p, o) => mkdirSync45(p, o)))(dir, { recursive: true });
47853
- (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) + `
47854
47869
  `);
47855
47870
  }
47856
47871
  async function refreshAgentConnectionHealth(config, agentName, agentDir, deps) {
@@ -47871,10 +47886,10 @@ var CONNECTION_HEALTH_FILENAME = "connection-health.json";
47871
47886
  var init_connection_health = () => {};
47872
47887
 
47873
47888
  // src/cli/update-prompt-hook.ts
47874
- import { existsSync as existsSync83, readFileSync as readFileSync71, writeFileSync as writeFileSync28, chmodSync as chmodSync12, mkdirSync as mkdirSync46 } from "node:fs";
47875
- 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";
47876
47891
  function containerHookCommand() {
47877
- return join82(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
47892
+ return join83(CONTAINER_AGENT_DIR, ".claude", "hooks", HOOK_FILENAME);
47878
47893
  }
47879
47894
  function updatePromptHookScript() {
47880
47895
  return `#!/bin/bash
@@ -47940,14 +47955,14 @@ exit 0
47940
47955
  `;
47941
47956
  }
47942
47957
  function installUpdatePromptHook(agentDir) {
47943
- const hooksDir = join82(agentDir, ".claude", "hooks");
47944
- mkdirSync46(hooksDir, { recursive: true });
47945
- 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);
47946
47961
  const desired = updatePromptHookScript();
47947
47962
  let installed = false;
47948
- const existing = existsSync83(scriptPath) ? readFileSync71(scriptPath, "utf-8") : "";
47963
+ const existing = existsSync83(scriptPath) ? readFileSync72(scriptPath, "utf-8") : "";
47949
47964
  if (existing !== desired) {
47950
- writeFileSync28(scriptPath, desired, { mode: 493 });
47965
+ writeFileSync29(scriptPath, desired, { mode: 493 });
47951
47966
  chmodSync12(scriptPath, 493);
47952
47967
  installed = true;
47953
47968
  } else {
@@ -47955,11 +47970,11 @@ function installUpdatePromptHook(agentDir) {
47955
47970
  chmodSync12(scriptPath, 493);
47956
47971
  } catch {}
47957
47972
  }
47958
- const settingsPath = join82(agentDir, ".claude", "settings.json");
47973
+ const settingsPath = join83(agentDir, ".claude", "settings.json");
47959
47974
  if (!existsSync83(settingsPath)) {
47960
47975
  return { scriptPath, settingsPath, installed };
47961
47976
  }
47962
- const raw = readFileSync71(settingsPath, "utf-8");
47977
+ const raw = readFileSync72(settingsPath, "utf-8");
47963
47978
  let parsed;
47964
47979
  try {
47965
47980
  parsed = JSON.parse(raw);
@@ -47995,7 +48010,7 @@ function installUpdatePromptHook(agentDir) {
47995
48010
  if (mutated) {
47996
48011
  hooks.UserPromptSubmit = list2;
47997
48012
  parsed.hooks = hooks;
47998
- writeFileSync28(settingsPath, JSON.stringify(parsed, null, 2) + `
48013
+ writeFileSync29(settingsPath, JSON.stringify(parsed, null, 2) + `
47999
48014
  `, { mode: 384 });
48000
48015
  installed = true;
48001
48016
  } else if (!alreadyCorrect) {
@@ -48004,7 +48019,7 @@ function installUpdatePromptHook(agentDir) {
48004
48019
  });
48005
48020
  hooks.UserPromptSubmit = list2;
48006
48021
  parsed.hooks = hooks;
48007
- writeFileSync28(settingsPath, JSON.stringify(parsed, null, 2) + `
48022
+ writeFileSync29(settingsPath, JSON.stringify(parsed, null, 2) + `
48008
48023
  `, { mode: 384 });
48009
48024
  installed = true;
48010
48025
  }
@@ -48384,7 +48399,7 @@ __export(exports_voice_sidecar_token, {
48384
48399
  VOICE_SIDECAR_TOKEN_ENV: () => VOICE_SIDECAR_TOKEN_ENV
48385
48400
  });
48386
48401
  import { randomBytes as randomBytes14 } from "node:crypto";
48387
- 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";
48388
48403
  import { dirname as dirname29 } from "node:path";
48389
48404
  async function defaultResolveOrSeedToken(home2, writeErr) {
48390
48405
  const [{ getViaBrokerStructured: getViaBrokerStructured2, putViaBroker: putViaBroker2 }, { resolveOperatorVaultPassphrase }] = await Promise.all([
@@ -48420,7 +48435,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
48420
48435
  if (engine !== "local") {
48421
48436
  try {
48422
48437
  if (existsSync85(envPath)) {
48423
- const body = readFileSync72(envPath, "utf-8");
48438
+ const body = readFileSync73(envPath, "utf-8");
48424
48439
  if (body.includes(`${VOICE_SIDECAR_TOKEN_ENV}=`))
48425
48440
  rmSync17(envPath);
48426
48441
  }
@@ -48439,11 +48454,11 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
48439
48454
  if (!token)
48440
48455
  return;
48441
48456
  try {
48442
- mkdirSync47(dirname29(envPath), { recursive: true });
48457
+ mkdirSync48(dirname29(envPath), { recursive: true });
48443
48458
  let body = "";
48444
48459
  try {
48445
48460
  if (existsSync85(envPath))
48446
- body = readFileSync72(envPath, "utf-8");
48461
+ body = readFileSync73(envPath, "utf-8");
48447
48462
  } catch {}
48448
48463
  const line = `${VOICE_SIDECAR_TOKEN_ENV}=${token}`;
48449
48464
  const keyRe = new RegExp(`^${VOICE_SIDECAR_TOKEN_ENV}=.*$`, "m");
@@ -48451,7 +48466,7 @@ async function provisionVoiceSidecarToken(composePath, home2, ctx) {
48451
48466
  `) ? body + `
48452
48467
  ` : body) + line + `
48453
48468
  `;
48454
- writeFileSync29(envPath, next, {
48469
+ writeFileSync30(envPath, next, {
48455
48470
  encoding: "utf-8",
48456
48471
  mode: 384
48457
48472
  });
@@ -48502,11 +48517,11 @@ __export(exports_apply, {
48502
48517
  DEFAULT_COMPOSE_PATH: () => DEFAULT_COMPOSE_PATH2,
48503
48518
  COMPOSE_PROJECT: () => COMPOSE_PROJECT2
48504
48519
  });
48505
- 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";
48506
48521
  import { mkdir as mkdir2 } from "node:fs/promises";
48507
48522
  import { spawnSync as childSpawnSync } from "node:child_process";
48508
48523
  import readline from "node:readline";
48509
- 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";
48510
48525
  import { homedir as homedir49 } from "node:os";
48511
48526
  import { execFileSync as execFileSync27 } from "node:child_process";
48512
48527
  function effectiveLiteLLMEnabled(config, agentResolvedLitellm) {
@@ -48518,7 +48533,7 @@ async function resolveOperatorVaultPassphrase(home2) {
48518
48533
  return envPass;
48519
48534
  try {
48520
48535
  const { readAutoUnlockFile: readAutoUnlockFile2 } = await Promise.resolve().then(() => (init_auto_unlock(), exports_auto_unlock));
48521
- const blobPath = join84(home2, ".switchroom", "vault-auto-unlock");
48536
+ const blobPath = join85(home2, ".switchroom", "vault-auto-unlock");
48522
48537
  const pass = readAutoUnlockFile2(blobPath);
48523
48538
  return pass && pass.length > 0 ? pass : null;
48524
48539
  } catch {
@@ -48608,7 +48623,7 @@ async function provisionLiteLLMKeys(config, agentNames, switchroomConfigPath, ct
48608
48623
  let configText = null;
48609
48624
  if (switchroomConfigPath && existsSync86(switchroomConfigPath)) {
48610
48625
  try {
48611
- configText = readFileSync73(switchroomConfigPath, "utf-8");
48626
+ configText = readFileSync74(switchroomConfigPath, "utf-8");
48612
48627
  } catch (err) {
48613
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.
48614
48629
  `));
@@ -48831,7 +48846,7 @@ function resolveVaultBindMountDir(homeDir, ctx) {
48831
48846
  if (isCustomPath && ctx.customVaultPath) {
48832
48847
  return dirname30(ctx.customVaultPath);
48833
48848
  }
48834
- return join84(homeDir, ".switchroom", "vault");
48849
+ return join85(homeDir, ".switchroom", "vault");
48835
48850
  }
48836
48851
  function inspectVaultBindMountDir(vaultDir) {
48837
48852
  if (!existsSync86(vaultDir))
@@ -48862,60 +48877,60 @@ function hasVaultRefs(value) {
48862
48877
  async function ensureHostMountSources(config) {
48863
48878
  const home2 = resolveHostHomeForCompose();
48864
48879
  const dirs = [
48865
- join84(home2, ".switchroom", "approvals"),
48866
- join84(home2, ".switchroom", "scheduler"),
48867
- join84(home2, ".switchroom", "logs"),
48868
- join84(home2, ".switchroom", "compose"),
48869
- 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")
48870
48885
  ];
48871
48886
  for (const name of Object.keys(config.agents)) {
48872
- dirs.push(join84(home2, ".switchroom", "agents", name));
48873
- dirs.push(join84(home2, ".switchroom", "logs", name));
48874
- dirs.push(join84(home2, ".claude", "projects", name));
48875
- dirs.push(join84(home2, ".switchroom", "audit", name));
48876
- if (existsSync86(join84(home2, ".switchroom-config"))) {
48877
- 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"));
48878
48893
  }
48879
48894
  }
48880
48895
  for (const dir of dirs) {
48881
48896
  await mkdir2(dir, { recursive: true });
48882
48897
  }
48883
- const autoUnlockPath = join84(home2, ".switchroom", "vault-auto-unlock");
48898
+ const autoUnlockPath = join85(home2, ".switchroom", "vault-auto-unlock");
48884
48899
  if (!existsSync86(autoUnlockPath)) {
48885
- writeFileSync30(autoUnlockPath, "", { mode: 384 });
48900
+ writeFileSync31(autoUnlockPath, "", { mode: 384 });
48886
48901
  }
48887
- const auditLogPath = join84(home2, ".switchroom", "vault-audit.log");
48902
+ const auditLogPath = join85(home2, ".switchroom", "vault-audit.log");
48888
48903
  if (!existsSync86(auditLogPath)) {
48889
- writeFileSync30(auditLogPath, "", { mode: 420 });
48904
+ writeFileSync31(auditLogPath, "", { mode: 420 });
48890
48905
  }
48891
48906
  const grantsDbDir = getGrantsDbDir(home2);
48892
- mkdirSync48(grantsDbDir, { recursive: true, mode: 448 });
48907
+ mkdirSync49(grantsDbDir, { recursive: true, mode: 448 });
48893
48908
  migrateLegacyGrantsDbLocation(getGrantsDbPath(home2));
48894
- const hostdAuditLogPath = join84(home2, ".switchroom", "host-control-audit.log");
48909
+ const hostdAuditLogPath = join85(home2, ".switchroom", "host-control-audit.log");
48895
48910
  if (!existsSync86(hostdAuditLogPath)) {
48896
- writeFileSync30(hostdAuditLogPath, "", { mode: 420 });
48911
+ writeFileSync31(hostdAuditLogPath, "", { mode: 420 });
48897
48912
  }
48898
48913
  for (const name of Object.keys(config.agents)) {
48899
- const tokenPath = join84(home2, ".switchroom", "agents", name, ".vault-token");
48914
+ const tokenPath = join85(home2, ".switchroom", "agents", name, ".vault-token");
48900
48915
  if (!existsSync86(tokenPath)) {
48901
- writeFileSync30(tokenPath, "", { mode: 384 });
48916
+ writeFileSync31(tokenPath, "", { mode: 384 });
48902
48917
  }
48903
48918
  try {
48904
48919
  const uid = allocateAgentUid(name);
48905
48920
  chownSync8(tokenPath, uid, uid);
48906
48921
  } catch {}
48907
48922
  }
48908
- const fleetDir = join84(home2, ".switchroom", "fleet");
48923
+ const fleetDir = join85(home2, ".switchroom", "fleet");
48909
48924
  await mkdir2(fleetDir, { recursive: true });
48910
- const invariantsPath = join84(fleetDir, "switchroom-invariants.md");
48925
+ const invariantsPath = join85(fleetDir, "switchroom-invariants.md");
48911
48926
  const invariantsCanonical = renderFleetInvariants();
48912
- const invariantsCurrent = existsSync86(invariantsPath) ? readFileSync73(invariantsPath, "utf-8") : null;
48927
+ const invariantsCurrent = existsSync86(invariantsPath) ? readFileSync74(invariantsPath, "utf-8") : null;
48913
48928
  if (invariantsCurrent !== invariantsCanonical) {
48914
- writeFileSync30(invariantsPath, invariantsCanonical, { mode: 420 });
48929
+ writeFileSync31(invariantsPath, invariantsCanonical, { mode: 420 });
48915
48930
  }
48916
- const fleetClaudePath = join84(fleetDir, "CLAUDE.md");
48931
+ const fleetClaudePath = join85(fleetDir, "CLAUDE.md");
48917
48932
  if (!existsSync86(fleetClaudePath)) {
48918
- writeFileSync30(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
48933
+ writeFileSync31(fleetClaudePath, renderFleetDefaultsClaudeMd(), {
48919
48934
  mode: 420
48920
48935
  });
48921
48936
  }
@@ -48999,17 +49014,17 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
48999
49014
  }
49000
49015
  function writeInstallTypeCache(homeDir = homedir49()) {
49001
49016
  const ctx = detectInstallType();
49002
- const dir = join84(homeDir, ".switchroom");
49003
- const out = join84(dir, "install-type.json");
49017
+ const dir = join85(homeDir, ".switchroom");
49018
+ const out = join85(dir, "install-type.json");
49004
49019
  const tmp = `${out}.tmp`;
49005
- mkdirSync48(dir, { recursive: true });
49020
+ mkdirSync49(dir, { recursive: true });
49006
49021
  const payload = {
49007
49022
  install_type: ctx.install_type,
49008
49023
  detected_at: new Date().toISOString(),
49009
49024
  source_paths: ctx.source_paths
49010
49025
  };
49011
- writeFileSync30(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
49012
- renameSync19(tmp, out);
49026
+ writeFileSync31(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
49027
+ renameSync20(tmp, out);
49013
49028
  return out;
49014
49029
  }
49015
49030
  async function runApply(config, options, deps = {}, switchroomConfigPath) {
@@ -49067,17 +49082,17 @@ Applying switchroom config...
49067
49082
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
49068
49083
  `));
49069
49084
  try {
49070
- installUpdatePromptHook(join84(agentsDir, name));
49085
+ installUpdatePromptHook(join85(agentsDir, name));
49071
49086
  } catch (hookErr) {
49072
49087
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
49073
49088
  `));
49074
49089
  }
49075
- await refreshAgentConnectionHealth(config, name, join84(agentsDir, name), {
49090
+ await refreshAgentConnectionHealth(config, name, join85(agentsDir, name), {
49076
49091
  vaultAclReader: connHealthVaultAclReader
49077
49092
  });
49078
49093
  try {
49079
49094
  const uid = allocateAgentUid(name);
49080
- alignAgentUid(name, join84(agentsDir, name), uid, {
49095
+ alignAgentUid(name, join85(agentsDir, name), uid, {
49081
49096
  confirm: !options.nonInteractive,
49082
49097
  writeOut
49083
49098
  });
@@ -49122,7 +49137,7 @@ Applying switchroom config...
49122
49137
  for (const name of agentNames) {
49123
49138
  try {
49124
49139
  const uid = allocateAgentUid(name);
49125
- alignAgentUid(name, join84(agentsDir, name), uid, {
49140
+ alignAgentUid(name, join85(agentsDir, name), uid, {
49126
49141
  confirm: !options.nonInteractive,
49127
49142
  writeOut
49128
49143
  });
@@ -49466,7 +49481,7 @@ function copyExampleConfig2(name) {
49466
49481
  }
49467
49482
  const embedded = EMBEDDED_EXAMPLES[name];
49468
49483
  if (embedded !== undefined) {
49469
- writeFileSync30(dest, embedded, { encoding: "utf8" });
49484
+ writeFileSync31(dest, embedded, { encoding: "utf8" });
49470
49485
  console.log(source_default.green(`Copied ${name}.yaml -> switchroom.yaml`));
49471
49486
  return;
49472
49487
  }
@@ -49482,7 +49497,7 @@ function findUnwritableAgentDirs(config, opts) {
49482
49497
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
49483
49498
  const unwritable = [];
49484
49499
  for (const name of targets) {
49485
- const startSh = join84(agentsDir, name, "start.sh");
49500
+ const startSh = join85(agentsDir, name, "start.sh");
49486
49501
  if (!existsSync86(startSh))
49487
49502
  continue;
49488
49503
  try {
@@ -49687,7 +49702,7 @@ var init_apply = __esm(() => {
49687
49702
  switchroom: switchroom_default,
49688
49703
  minimal: minimal_default
49689
49704
  };
49690
- DEFAULT_COMPOSE_PATH2 = join84(homedir49(), ".switchroom", "compose", "docker-compose.yml");
49705
+ DEFAULT_COMPOSE_PATH2 = join85(homedir49(), ".switchroom", "compose", "docker-compose.yml");
49691
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.
49692
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.";
49693
49708
  SELF_ELEVATE_PRESERVED_ENV = [
@@ -63136,7 +63151,7 @@ __export(exports_server2, {
63136
63151
  TOOLS: () => TOOLS2
63137
63152
  });
63138
63153
  import { randomBytes as randomBytes16 } from "node:crypto";
63139
- import { existsSync as existsSync95, readFileSync as readFileSync82 } from "node:fs";
63154
+ import { existsSync as existsSync95, readFileSync as readFileSync83 } from "node:fs";
63140
63155
  function selfSocketPath() {
63141
63156
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
63142
63157
  }
@@ -63425,7 +63440,7 @@ function getLastUpdateApplyStatus() {
63425
63440
  }
63426
63441
  let raw;
63427
63442
  try {
63428
- raw = readFileSync82(path9, "utf-8");
63443
+ raw = readFileSync83(path9, "utf-8");
63429
63444
  } catch (err2) {
63430
63445
  return errorText2(`get_status: failed to read audit log at ${path9}: ${err2.message}`);
63431
63446
  }
@@ -64115,11 +64130,11 @@ __export(exports_scan, {
64115
64130
  ledgerPathForBase: () => ledgerPathForBase
64116
64131
  });
64117
64132
  import {
64118
- readFileSync as readFileSync84,
64133
+ readFileSync as readFileSync85,
64119
64134
  readdirSync as readdirSync37,
64120
64135
  existsSync as existsSync98,
64121
- mkdirSync as mkdirSync55,
64122
- writeFileSync as writeFileSync36
64136
+ mkdirSync as mkdirSync56,
64137
+ writeFileSync as writeFileSync37
64123
64138
  } from "node:fs";
64124
64139
  import { resolve as resolve57, dirname as dirname33 } from "node:path";
64125
64140
  import { homedir as homedir58 } from "node:os";
@@ -64158,7 +64173,7 @@ function runScan(opts = {}) {
64158
64173
  let sawArtifact = false;
64159
64174
  try {
64160
64175
  if (existsSync98(turnsPath)) {
64161
- turnsText = readFileSync84(turnsPath, "utf-8");
64176
+ turnsText = readFileSync85(turnsPath, "utf-8");
64162
64177
  sawArtifact = true;
64163
64178
  }
64164
64179
  } catch (e) {
@@ -64166,7 +64181,7 @@ function runScan(opts = {}) {
64166
64181
  }
64167
64182
  try {
64168
64183
  if (existsSync98(gwPath)) {
64169
- gwText = readFileSync84(gwPath, "utf-8");
64184
+ gwText = readFileSync85(gwPath, "utf-8");
64170
64185
  sawArtifact = true;
64171
64186
  }
64172
64187
  } catch (e) {
@@ -64208,7 +64223,7 @@ function readLedgerIfPresent(base) {
64208
64223
  try {
64209
64224
  if (!existsSync98(path9))
64210
64225
  return null;
64211
- return JSON.parse(readFileSync84(path9, "utf-8"));
64226
+ return JSON.parse(readFileSync85(path9, "utf-8"));
64212
64227
  } catch {
64213
64228
  return null;
64214
64229
  }
@@ -64218,8 +64233,8 @@ function ledgerPathForBase(base) {
64218
64233
  }
64219
64234
  function writeLedger(base, ledger) {
64220
64235
  const path9 = ledgerPathForBase(base);
64221
- mkdirSync55(dirname33(path9), { recursive: true });
64222
- writeFileSync36(path9, JSON.stringify(ledger, null, 2) + `
64236
+ mkdirSync56(dirname33(path9), { recursive: true });
64237
+ writeFileSync37(path9, JSON.stringify(ledger, null, 2) + `
64223
64238
  `, "utf-8");
64224
64239
  return path9;
64225
64240
  }
@@ -89659,9 +89674,425 @@ function registerNotionMcpLauncherCommand(program3) {
89659
89674
  });
89660
89675
  }
89661
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
+
89662
90093
  // src/cli/deliver-file.ts
89663
90094
  init_client2();
89664
- import { readFileSync as readFileSync70, statSync as statSync46 } from "node:fs";
90095
+ import { readFileSync as readFileSync71, statSync as statSync46 } from "node:fs";
89665
90096
  import { basename as basename11 } from "node:path";
89666
90097
 
89667
90098
  // src/delivery/onedrive.ts
@@ -90001,7 +90432,7 @@ async function defaultResolveProvider() {
90001
90432
  async function runDeliverFile(localPath, deps = {}) {
90002
90433
  const agentName = safeAgentName(deps.agentName ?? process.env.SWITCHROOM_AGENT_NAME);
90003
90434
  const sizeOf = deps.fileSize ?? ((p) => statSync46(p).size);
90004
- const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync70(p)));
90435
+ const read = deps.readFile ?? ((p) => new Uint8Array(readFileSync71(p)));
90005
90436
  const resolveProvider = deps.resolveProvider ?? defaultResolveProvider;
90006
90437
  let size;
90007
90438
  try {
@@ -90322,8 +90753,8 @@ function runRedactStdin() {
90322
90753
  }
90323
90754
 
90324
90755
  // src/cli/status-ask.ts
90325
- import { readFileSync as readFileSync74, existsSync as existsSync87, readdirSync as readdirSync30 } from "node:fs";
90326
- 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";
90327
90758
  import { homedir as homedir50 } from "node:os";
90328
90759
 
90329
90760
  // src/status-ask/report.ts
@@ -90598,7 +91029,7 @@ function runReport(opts) {
90598
91029
  for (const src of sources) {
90599
91030
  let content;
90600
91031
  try {
90601
- content = readFileSync74(src.path, "utf-8");
91032
+ content = readFileSync75(src.path, "utf-8");
90602
91033
  } catch (err) {
90603
91034
  process.stderr.write(`status-ask report: cannot read ${src.path}: ${err instanceof Error ? err.message : String(err)}
90604
91035
  `);
@@ -90659,7 +91090,7 @@ function resolveSources(explicitPath) {
90659
91090
  const config = loadConfig();
90660
91091
  agentsDir = resolveAgentsDir(config);
90661
91092
  } catch {
90662
- agentsDir = join85(homedir50(), ".switchroom", "agents");
91093
+ agentsDir = join86(homedir50(), ".switchroom", "agents");
90663
91094
  }
90664
91095
  if (!existsSync87(agentsDir))
90665
91096
  return [];
@@ -90671,7 +91102,7 @@ function resolveSources(explicitPath) {
90671
91102
  return [];
90672
91103
  }
90673
91104
  for (const name of entries) {
90674
- const path9 = join85(agentsDir, name, "runtime-metrics.jsonl");
91105
+ const path9 = join86(agentsDir, name, "runtime-metrics.jsonl");
90675
91106
  if (existsSync87(path9)) {
90676
91107
  sources.push({ path: path9, agent: name });
90677
91108
  }
@@ -90704,43 +91135,43 @@ import {
90704
91135
  closeSync as closeSync14,
90705
91136
  existsSync as existsSync88,
90706
91137
  fsyncSync as fsyncSync8,
90707
- mkdirSync as mkdirSync49,
91138
+ mkdirSync as mkdirSync50,
90708
91139
  openSync as openSync14,
90709
91140
  readdirSync as readdirSync31,
90710
- readFileSync as readFileSync75,
90711
- renameSync as renameSync20,
91141
+ readFileSync as readFileSync76,
91142
+ renameSync as renameSync21,
90712
91143
  statSync as statSync48,
90713
91144
  unlinkSync as unlinkSync18,
90714
91145
  writeSync as writeSync10
90715
91146
  } from "node:fs";
90716
- import { join as join86, resolve as resolve53 } from "node:path";
91147
+ import { join as join87, resolve as resolve53 } from "node:path";
90717
91148
  var STAGING_SUBDIR = ".staging";
90718
91149
  function overlayPathsFor(agent, opts = {}) {
90719
91150
  const base = opts.root ? resolve53(opts.root, agent) : resolve53(resolveDualPath(`~/.switchroom/agents/${agent}`));
90720
- const scheduleDir = join86(base, "schedule.d");
90721
- const scheduleStagingDir = join86(scheduleDir, STAGING_SUBDIR);
90722
- const skillsDir = join86(base, "skills.d");
90723
- 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);
90724
91155
  return {
90725
91156
  agentRoot: base,
90726
91157
  scheduleDir,
90727
91158
  scheduleStagingDir,
90728
91159
  skillsDir,
90729
91160
  skillsStagingDir,
90730
- lockPath: join86(base, ".lock"),
91161
+ lockPath: join87(base, ".lock"),
90731
91162
  stagingDir: scheduleStagingDir
90732
91163
  };
90733
91164
  }
90734
91165
  function ensureDirs(paths) {
90735
- mkdirSync49(paths.scheduleDir, { recursive: true });
90736
- mkdirSync49(paths.scheduleStagingDir, { recursive: true });
91166
+ mkdirSync50(paths.scheduleDir, { recursive: true });
91167
+ mkdirSync50(paths.scheduleStagingDir, { recursive: true });
90737
91168
  }
90738
91169
  function ensureSkillsDirs(paths) {
90739
- mkdirSync49(paths.skillsDir, { recursive: true });
90740
- mkdirSync49(paths.skillsStagingDir, { recursive: true });
91170
+ mkdirSync50(paths.skillsDir, { recursive: true });
91171
+ mkdirSync50(paths.skillsStagingDir, { recursive: true });
90741
91172
  }
90742
91173
  function withAgentLock(paths, fn) {
90743
- mkdirSync49(paths.agentRoot, { recursive: true });
91174
+ mkdirSync50(paths.agentRoot, { recursive: true });
90744
91175
  const start = Date.now();
90745
91176
  const TIMEOUT_MS = 5000;
90746
91177
  let fd = null;
@@ -90781,8 +91212,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
90781
91212
  const paths = overlayPathsFor(agent, opts);
90782
91213
  return withAgentLock(paths, () => {
90783
91214
  ensureDirs(paths);
90784
- const stagingPath = join86(paths.scheduleStagingDir, `${slug}.yaml`);
90785
- const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
91215
+ const stagingPath = join87(paths.scheduleStagingDir, `${slug}.yaml`);
91216
+ const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
90786
91217
  const fd = openSync14(stagingPath, "w", 384);
90787
91218
  try {
90788
91219
  writeSync10(fd, yamlText);
@@ -90790,7 +91221,7 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
90790
91221
  } finally {
90791
91222
  closeSync14(fd);
90792
91223
  }
90793
- renameSync20(stagingPath, finalPath);
91224
+ renameSync21(stagingPath, finalPath);
90794
91225
  return finalPath;
90795
91226
  });
90796
91227
  }
@@ -90798,8 +91229,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90798
91229
  const paths = overlayPathsFor(agent, opts);
90799
91230
  return withAgentLock(paths, () => {
90800
91231
  ensureSkillsDirs(paths);
90801
- const stagingPath = join86(paths.skillsStagingDir, `${slug}.yaml`);
90802
- const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
91232
+ const stagingPath = join87(paths.skillsStagingDir, `${slug}.yaml`);
91233
+ const finalPath = join87(paths.skillsDir, `${slug}.yaml`);
90803
91234
  const fd = openSync14(stagingPath, "w", 384);
90804
91235
  try {
90805
91236
  writeSync10(fd, yamlText);
@@ -90807,14 +91238,14 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
90807
91238
  } finally {
90808
91239
  closeSync14(fd);
90809
91240
  }
90810
- renameSync20(stagingPath, finalPath);
91241
+ renameSync21(stagingPath, finalPath);
90811
91242
  return finalPath;
90812
91243
  });
90813
91244
  }
90814
91245
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
90815
91246
  const paths = overlayPathsFor(agent, opts);
90816
91247
  return withAgentLock(paths, () => {
90817
- const finalPath = join86(paths.skillsDir, `${slug}.yaml`);
91248
+ const finalPath = join87(paths.skillsDir, `${slug}.yaml`);
90818
91249
  if (!existsSync88(finalPath))
90819
91250
  return false;
90820
91251
  unlinkSync18(finalPath);
@@ -90829,9 +91260,9 @@ function listSkillsOverlayEntries(agent, opts = {}) {
90829
91260
  for (const name of readdirSync31(paths.skillsDir)) {
90830
91261
  if (!/\.ya?ml$/i.test(name))
90831
91262
  continue;
90832
- const full = join86(paths.skillsDir, name);
91263
+ const full = join87(paths.skillsDir, name);
90833
91264
  try {
90834
- const raw = readFileSync75(full, "utf-8");
91265
+ const raw = readFileSync76(full, "utf-8");
90835
91266
  const slug = name.replace(/\.ya?ml$/i, "");
90836
91267
  out.push({ slug, path: full, raw });
90837
91268
  } catch {}
@@ -90841,7 +91272,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
90841
91272
  function deleteOverlayEntry(agent, slug, opts = {}) {
90842
91273
  const paths = overlayPathsFor(agent, opts);
90843
91274
  return withAgentLock(paths, () => {
90844
- const finalPath = join86(paths.scheduleDir, `${slug}.yaml`);
91275
+ const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
90845
91276
  if (!existsSync88(finalPath))
90846
91277
  return false;
90847
91278
  unlinkSync18(finalPath);
@@ -90856,9 +91287,9 @@ function listOverlayEntries(agent, opts = {}) {
90856
91287
  for (const name of readdirSync31(paths.scheduleDir)) {
90857
91288
  if (!/\.ya?ml$/i.test(name))
90858
91289
  continue;
90859
- const full = join86(paths.scheduleDir, name);
91290
+ const full = join87(paths.scheduleDir, name);
90860
91291
  try {
90861
- const raw = readFileSync75(full, "utf-8");
91292
+ const raw = readFileSync76(full, "utf-8");
90862
91293
  const slug = name.replace(/\.ya?ml$/i, "");
90863
91294
  out.push({ slug, path: full, raw });
90864
91295
  } catch {}
@@ -91080,25 +91511,25 @@ import {
91080
91511
  closeSync as closeSync15,
91081
91512
  existsSync as existsSync89,
91082
91513
  fsyncSync as fsyncSync9,
91083
- mkdirSync as mkdirSync50,
91514
+ mkdirSync as mkdirSync51,
91084
91515
  openSync as openSync15,
91085
91516
  readdirSync as readdirSync32,
91086
- readFileSync as readFileSync76,
91087
- renameSync as renameSync21,
91517
+ readFileSync as readFileSync77,
91518
+ renameSync as renameSync22,
91088
91519
  unlinkSync as unlinkSync19,
91089
- writeFileSync as writeFileSync31,
91520
+ writeFileSync as writeFileSync32,
91090
91521
  writeSync as writeSync11
91091
91522
  } from "node:fs";
91092
- import { join as join87 } from "node:path";
91523
+ import { join as join88 } from "node:path";
91093
91524
  import { randomBytes as randomBytes15 } from "node:crypto";
91094
91525
  var STAGE_ID_PREFIX = "cap_";
91095
91526
  function pendingDir(agent, opts = {}) {
91096
91527
  const paths = overlayPathsFor(agent, opts);
91097
- return join87(paths.scheduleDir, ".pending");
91528
+ return join88(paths.scheduleDir, ".pending");
91098
91529
  }
91099
91530
  function ensurePendingDir(agent, opts = {}) {
91100
91531
  const dir = pendingDir(agent, opts);
91101
- mkdirSync50(dir, { recursive: true });
91532
+ mkdirSync51(dir, { recursive: true });
91102
91533
  return dir;
91103
91534
  }
91104
91535
  function newStageId() {
@@ -91107,8 +91538,8 @@ function newStageId() {
91107
91538
  function stagePendingScheduleEntry(opts) {
91108
91539
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
91109
91540
  const stageId = opts.stageId ?? newStageId();
91110
- const yamlPath = join87(dir, `${stageId}.yaml`);
91111
- const metaPath = join87(dir, `${stageId}.meta.json`);
91541
+ const yamlPath = join88(dir, `${stageId}.yaml`);
91542
+ const metaPath = join88(dir, `${stageId}.meta.json`);
91112
91543
  const meta = {
91113
91544
  v: 1,
91114
91545
  stage_id: stageId,
@@ -91127,9 +91558,9 @@ function stagePendingScheduleEntry(opts) {
91127
91558
  } finally {
91128
91559
  closeSync15(fd);
91129
91560
  }
91130
- renameSync21(yamlTmp, yamlPath);
91561
+ renameSync22(yamlTmp, yamlPath);
91131
91562
  }
91132
- writeFileSync31(metaPath, JSON.stringify(meta, null, 2) + `
91563
+ writeFileSync32(metaPath, JSON.stringify(meta, null, 2) + `
91133
91564
  `, { mode: 384 });
91134
91565
  return { stageId, yamlPath, metaPath };
91135
91566
  }
@@ -91142,12 +91573,12 @@ function listPendingScheduleEntries(agent, opts = {}) {
91142
91573
  if (!name.endsWith(".meta.json"))
91143
91574
  continue;
91144
91575
  const stageId = name.slice(0, -".meta.json".length);
91145
- const metaPath = join87(dir, name);
91146
- const yamlPath = join87(dir, `${stageId}.yaml`);
91576
+ const metaPath = join88(dir, name);
91577
+ const yamlPath = join88(dir, `${stageId}.yaml`);
91147
91578
  if (!existsSync89(yamlPath))
91148
91579
  continue;
91149
91580
  try {
91150
- const meta = JSON.parse(readFileSync76(metaPath, "utf-8"));
91581
+ const meta = JSON.parse(readFileSync77(metaPath, "utf-8"));
91151
91582
  if (meta?.v !== 1 || typeof meta.stage_id !== "string")
91152
91583
  continue;
91153
91584
  out.push({ stageId: meta.stage_id, agent: meta.agent, yamlPath, metaPath, meta });
@@ -91162,11 +91593,11 @@ function commitPendingScheduleEntry(opts) {
91162
91593
  return { committed: false, reason: "not_found" };
91163
91594
  const slug = match.meta.entry.name ?? match.stageId;
91164
91595
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
91165
- const finalPath = join87(paths.scheduleDir, `${slug}.yaml`);
91596
+ const finalPath = join88(paths.scheduleDir, `${slug}.yaml`);
91166
91597
  if (existsSync89(finalPath)) {
91167
91598
  return { committed: false, reason: "slug_collision" };
91168
91599
  }
91169
- renameSync21(match.yamlPath, finalPath);
91600
+ renameSync22(match.yamlPath, finalPath);
91170
91601
  unlinkSync19(match.metaPath);
91171
91602
  return { committed: true, path: finalPath, slug };
91172
91603
  }
@@ -91185,7 +91616,7 @@ function denyPendingScheduleEntry(opts) {
91185
91616
  }
91186
91617
 
91187
91618
  // src/cli/agent-config-write.ts
91188
- import { existsSync as existsSync90, readFileSync as readFileSync77 } from "node:fs";
91619
+ import { existsSync as existsSync90, readFileSync as readFileSync78 } from "node:fs";
91189
91620
  import { execFileSync as execFileSync28 } from "node:child_process";
91190
91621
 
91191
91622
  // src/scheduler/schedule-report.ts
@@ -91576,7 +92007,7 @@ function scheduleRemove(opts) {
91576
92007
  let priorContent = null;
91577
92008
  try {
91578
92009
  if (existsSync90(match.path))
91579
- priorContent = readFileSync77(match.path, "utf-8");
92010
+ priorContent = readFileSync78(match.path, "utf-8");
91580
92011
  } catch {}
91581
92012
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
91582
92013
  const reconcileFn = opts.reconcile === undefined ? opts.root ? null : reconcileAgentCronOnly : opts.reconcile;
@@ -91779,7 +92210,7 @@ function registerAgentConfigWriteCommands(program3) {
91779
92210
  }
91780
92211
  let blob;
91781
92212
  if (opts.jsonl) {
91782
- blob = existsSync90(opts.jsonl) ? readFileSync77(opts.jsonl, "utf-8") : "";
92213
+ blob = existsSync90(opts.jsonl) ? readFileSync78(opts.jsonl, "utf-8") : "";
91783
92214
  } else {
91784
92215
  try {
91785
92216
  blob = execFileSync28("docker", ["exec", `switchroom-${agent}`, "cat", "/state/agent/scheduler.jsonl"], {
@@ -91815,7 +92246,7 @@ import { existsSync as existsSync91 } from "node:fs";
91815
92246
  init_reconcile_default_skills();
91816
92247
  init_agent_config();
91817
92248
  var import_yaml23 = __toESM(require_dist(), 1);
91818
- import { join as join88 } from "node:path";
92249
+ import { join as join89 } from "node:path";
91819
92250
  var MAX_SKILLS_PER_AGENT = 20;
91820
92251
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
91821
92252
  function exitCodeFor2(code) {
@@ -91890,7 +92321,7 @@ function skillInstall(opts) {
91890
92321
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
91891
92322
  }
91892
92323
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
91893
- const skillPath = join88(poolDir, skillName);
92324
+ const skillPath = join89(poolDir, skillName);
91894
92325
  if (!existsSync91(skillPath)) {
91895
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.`);
91896
92327
  }
@@ -92057,19 +92488,19 @@ import {
92057
92488
  closeSync as closeSync16,
92058
92489
  existsSync as existsSync92,
92059
92490
  lstatSync as lstatSync11,
92060
- mkdirSync as mkdirSync51,
92491
+ mkdirSync as mkdirSync52,
92061
92492
  mkdtempSync as mkdtempSync5,
92062
92493
  openSync as openSync16,
92063
- readFileSync as readFileSync78,
92494
+ readFileSync as readFileSync79,
92064
92495
  readdirSync as readdirSync33,
92065
92496
  realpathSync as realpathSync7,
92066
- renameSync as renameSync22,
92497
+ renameSync as renameSync23,
92067
92498
  rmSync as rmSync18,
92068
92499
  statSync as statSync49,
92069
- writeFileSync as writeFileSync32
92500
+ writeFileSync as writeFileSync33
92070
92501
  } from "node:fs";
92071
- import { tmpdir as tmpdir5, homedir as homedir51 } from "node:os";
92072
- 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";
92073
92504
  import { spawnSync as spawnSync16 } from "node:child_process";
92074
92505
 
92075
92506
  // src/cli/skill-common.ts
@@ -92303,7 +92734,7 @@ function scanForClaudeP2(content) {
92303
92734
  function resolveSkillsPoolDir2(override) {
92304
92735
  const raw = override ?? "~/.switchroom/skills";
92305
92736
  if (raw.startsWith("~/")) {
92306
- return join89(homedir51(), raw.slice(2));
92737
+ return join90(homedir51(), raw.slice(2));
92307
92738
  }
92308
92739
  if (raw === "~")
92309
92740
  return homedir51();
@@ -92342,7 +92773,7 @@ function loadFromDir(dir) {
92342
92773
  const walk2 = (sub) => {
92343
92774
  const entries = readdirSync33(sub, { withFileTypes: true });
92344
92775
  for (const ent of entries) {
92345
- const full = join89(sub, ent.name);
92776
+ const full = join90(sub, ent.name);
92346
92777
  const rel = relative2(abs, full);
92347
92778
  if (ent.isSymbolicLink()) {
92348
92779
  fail3(`refusing to read symlink inside --from dir: ${rel}`);
@@ -92352,7 +92783,7 @@ function loadFromDir(dir) {
92352
92783
  continue;
92353
92784
  }
92354
92785
  if (ent.isFile()) {
92355
- const buf = readFileSync78(full);
92786
+ const buf = readFileSync79(full);
92356
92787
  files[rel.replace(/\\/g, "/")] = buf.toString("utf-8");
92357
92788
  }
92358
92789
  }
@@ -92377,7 +92808,7 @@ function loadFromTarball(tarPath) {
92377
92808
  fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
92378
92809
  }
92379
92810
  }
92380
- const staging = mkdtempSync5(join89(tmpdir5(), "skill-apply-extract-"));
92811
+ const staging = mkdtempSync5(join90(tmpdir6(), "skill-apply-extract-"));
92381
92812
  try {
92382
92813
  const flags = isGz ? ["-xzf"] : ["-xf"];
92383
92814
  const r = spawnSync16("tar", [
@@ -92401,7 +92832,7 @@ function loadFromTarball(tarPath) {
92401
92832
  }
92402
92833
  }
92403
92834
  function loadSingleFile(filePath) {
92404
- const content = readFileSync78(filePath, "utf-8");
92835
+ const content = readFileSync79(filePath, "utf-8");
92405
92836
  return { "SKILL.md": content };
92406
92837
  }
92407
92838
  function loadFromStdin() {
@@ -92463,10 +92894,10 @@ function validatePayload(name, files) {
92463
92894
  errors2.push(`${path9} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
92464
92895
  }
92465
92896
  } else if (PY_SCRIPT_RE2.test(path9)) {
92466
- const tmp = mkdtempSync5(join89(tmpdir5(), "skill-apply-py-"));
92467
- const tmpPy = join89(tmp, "check.py");
92897
+ const tmp = mkdtempSync5(join90(tmpdir6(), "skill-apply-py-"));
92898
+ const tmpPy = join90(tmp, "check.py");
92468
92899
  try {
92469
- writeFileSync32(tmpPy, content);
92900
+ writeFileSync33(tmpPy, content);
92470
92901
  const r = spawnSync16("python3", ["-m", "py_compile", tmpPy], {
92471
92902
  encoding: "utf-8"
92472
92903
  });
@@ -92487,12 +92918,12 @@ function diffSummary(currentDir, files) {
92487
92918
  if (existsSync92(currentDir)) {
92488
92919
  const walk2 = (sub) => {
92489
92920
  for (const ent of readdirSync33(sub, { withFileTypes: true })) {
92490
- const full = join89(sub, ent.name);
92921
+ const full = join90(sub, ent.name);
92491
92922
  const rel = relative2(currentDir, full);
92492
92923
  if (ent.isDirectory()) {
92493
92924
  walk2(full);
92494
92925
  } else if (ent.isFile()) {
92495
- currentFiles[rel.replace(/\\/g, "/")] = readFileSync78(full, "utf-8");
92926
+ currentFiles[rel.replace(/\\/g, "/")] = readFileSync79(full, "utf-8");
92496
92927
  }
92497
92928
  }
92498
92929
  };
@@ -92521,9 +92952,9 @@ function diffSummary(currentDir, files) {
92521
92952
  }
92522
92953
  function writePayload(poolDir, name, files) {
92523
92954
  if (!existsSync92(poolDir)) {
92524
- mkdirSync51(poolDir, { recursive: true, mode: 493 });
92955
+ mkdirSync52(poolDir, { recursive: true, mode: 493 });
92525
92956
  }
92526
- const target = join89(poolDir, name);
92957
+ const target = join90(poolDir, name);
92527
92958
  let targetIsSymlink = false;
92528
92959
  try {
92529
92960
  const st = lstatSync11(target);
@@ -92534,15 +92965,15 @@ function writePayload(poolDir, name, files) {
92534
92965
  if (targetIsSymlink) {
92535
92966
  fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
92536
92967
  }
92537
- const staging = mkdtempSync5(join89(poolDir, `.skill-apply-stage-${name}-`));
92968
+ const staging = mkdtempSync5(join90(poolDir, `.skill-apply-stage-${name}-`));
92538
92969
  let oldRename = null;
92539
92970
  try {
92540
92971
  for (const [path9, content] of Object.entries(files)) {
92541
- const full = join89(staging, path9);
92542
- mkdirSync51(dirname31(full), { recursive: true, mode: 493 });
92972
+ const full = join90(staging, path9);
92973
+ mkdirSync52(dirname31(full), { recursive: true, mode: 493 });
92543
92974
  const fd = openSync16(full, "wx");
92544
92975
  try {
92545
- writeFileSync32(fd, content);
92976
+ writeFileSync33(fd, content);
92546
92977
  } finally {
92547
92978
  closeSync16(fd);
92548
92979
  }
@@ -92558,9 +92989,9 @@ function writePayload(poolDir, name, files) {
92558
92989
  } catch {}
92559
92990
  if (targetExists) {
92560
92991
  oldRename = `${target}.skill-apply-old-${Date.now()}`;
92561
- renameSync22(target, oldRename);
92992
+ renameSync23(target, oldRename);
92562
92993
  }
92563
- renameSync22(staging, target);
92994
+ renameSync23(staging, target);
92564
92995
  if (oldRename) {
92565
92996
  rmSync18(oldRename, { recursive: true, force: true });
92566
92997
  oldRename = null;
@@ -92574,7 +93005,7 @@ function writePayload(poolDir, name, files) {
92574
93005
  if (existsSync92(target)) {
92575
93006
  rmSync18(target, { recursive: true, force: true });
92576
93007
  }
92577
- renameSync22(oldRename, target);
93008
+ renameSync23(oldRename, target);
92578
93009
  } catch {}
92579
93010
  }
92580
93011
  throw err2;
@@ -92616,7 +93047,7 @@ function registerSkillCommand(program3) {
92616
93047
  }
92617
93048
  const config = loadConfig();
92618
93049
  const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
92619
- const currentDir = join89(poolDir, name);
93050
+ const currentDir = join90(poolDir, name);
92620
93051
  console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
92621
93052
  console.log(source_default.bold("Diff vs current pool content:"));
92622
93053
  console.log(diffSummary(currentDir, files));
@@ -92650,19 +93081,19 @@ import {
92650
93081
  closeSync as closeSync17,
92651
93082
  existsSync as existsSync93,
92652
93083
  lstatSync as lstatSync12,
92653
- mkdirSync as mkdirSync52,
93084
+ mkdirSync as mkdirSync53,
92654
93085
  mkdtempSync as mkdtempSync6,
92655
93086
  openSync as openSync17,
92656
- readFileSync as readFileSync79,
93087
+ readFileSync as readFileSync80,
92657
93088
  readdirSync as readdirSync34,
92658
- renameSync as renameSync23,
93089
+ renameSync as renameSync24,
92659
93090
  rmSync as rmSync19,
92660
93091
  statSync as statSync50,
92661
93092
  utimesSync,
92662
- writeFileSync as writeFileSync33
93093
+ writeFileSync as writeFileSync34
92663
93094
  } from "node:fs";
92664
- import { dirname as dirname32, join as join90, relative as relative3, resolve as resolve55 } from "node:path";
92665
- 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";
92666
93097
  import { spawnSync as spawnSync17 } from "node:child_process";
92667
93098
  init_helpers();
92668
93099
  init_agent_config();
@@ -92673,10 +93104,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
92673
93104
  var PERSONAL_SKILLS_SUBPATH = "personal-skills";
92674
93105
  function resolveConfigSkillsDir(agent) {
92675
93106
  const override = process.env.SWITCHROOM_CONFIG_DIR;
92676
- const candidate = override ? resolve55(override) : join90(homedir52(), ".switchroom-config");
93107
+ const candidate = override ? resolve55(override) : join91(homedir52(), ".switchroom-config");
92677
93108
  if (!existsSync93(candidate))
92678
93109
  return null;
92679
- return join90(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
93110
+ return join91(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
92680
93111
  }
92681
93112
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
92682
93113
  function sweepMirrorPriors(configSkillsRoot) {
@@ -92694,7 +93125,7 @@ function sweepMirrorPriors(configSkillsRoot) {
92694
93125
  if (now - ts < MIRROR_PRIOR_TTL_MS)
92695
93126
  continue;
92696
93127
  try {
92697
- rmSync19(join90(configSkillsRoot, ent), { recursive: true, force: true });
93128
+ rmSync19(join91(configSkillsRoot, ent), { recursive: true, force: true });
92698
93129
  } catch {}
92699
93130
  }
92700
93131
  } catch {}
@@ -92703,7 +93134,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
92703
93134
  const configSkillsRoot = resolveConfigSkillsDir(agent);
92704
93135
  if (!configSkillsRoot)
92705
93136
  return;
92706
- const dest = join90(configSkillsRoot, name);
93137
+ const dest = join91(configSkillsRoot, name);
92707
93138
  try {
92708
93139
  if (liveSkillDir !== null) {
92709
93140
  try {
@@ -92718,34 +93149,34 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
92718
93149
  if (liveSkillDir === null) {
92719
93150
  sweepMirrorPriors(configSkillsRoot);
92720
93151
  if (existsSync93(dest)) {
92721
- const trash = join90(configSkillsRoot, `.${name}-trash-${Date.now()}`);
92722
- renameSync23(dest, trash);
93152
+ const trash = join91(configSkillsRoot, `.${name}-trash-${Date.now()}`);
93153
+ renameSync24(dest, trash);
92723
93154
  }
92724
93155
  return;
92725
93156
  }
92726
- mkdirSync52(configSkillsRoot, { recursive: true, mode: 493 });
93157
+ mkdirSync53(configSkillsRoot, { recursive: true, mode: 493 });
92727
93158
  sweepMirrorPriors(configSkillsRoot);
92728
- const staging = mkdtempSync6(join90(configSkillsRoot, `.${name}-staging-`));
93159
+ const staging = mkdtempSync6(join91(configSkillsRoot, `.${name}-staging-`));
92729
93160
  const walk2 = (src, dst) => {
92730
- mkdirSync52(dst, { recursive: true, mode: 493 });
93161
+ mkdirSync53(dst, { recursive: true, mode: 493 });
92731
93162
  for (const ent of readdirSync34(src, { withFileTypes: true })) {
92732
- const s = join90(src, ent.name);
92733
- const d = join90(dst, ent.name);
93163
+ const s = join91(src, ent.name);
93164
+ const d = join91(dst, ent.name);
92734
93165
  if (ent.isSymbolicLink())
92735
93166
  continue;
92736
93167
  if (ent.isDirectory())
92737
93168
  walk2(s, d);
92738
93169
  else if (ent.isFile()) {
92739
- writeFileSync33(d, readFileSync79(s));
93170
+ writeFileSync34(d, readFileSync80(s));
92740
93171
  }
92741
93172
  }
92742
93173
  };
92743
93174
  walk2(liveSkillDir, staging);
92744
93175
  if (existsSync93(dest)) {
92745
- const prior = join90(configSkillsRoot, `.${name}-prior-${Date.now()}`);
92746
- renameSync23(dest, prior);
93176
+ const prior = join91(configSkillsRoot, `.${name}-prior-${Date.now()}`);
93177
+ renameSync24(dest, prior);
92747
93178
  }
92748
- renameSync23(staging, dest);
93179
+ renameSync24(staging, dest);
92749
93180
  } catch (err2) {
92750
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.
92751
93182
  `));
@@ -92772,16 +93203,16 @@ function resolveAgent(opts) {
92772
93203
  function resolveAgentsRoot(opts) {
92773
93204
  if (opts.root)
92774
93205
  return resolve55(opts.root);
92775
- return join90(homedir52(), ".switchroom", "agents");
93206
+ return join91(homedir52(), ".switchroom", "agents");
92776
93207
  }
92777
93208
  function personalSkillDir(agentsRoot, agent, name) {
92778
- return join90(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
93209
+ return join91(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
92779
93210
  }
92780
93211
  function trashDir(agentsRoot, agent) {
92781
- return join90(agentsRoot, agent, ".claude", TRASH_DIRNAME);
93212
+ return join91(agentsRoot, agent, ".claude", TRASH_DIRNAME);
92782
93213
  }
92783
93214
  function countPersonalSkills(agentsRoot, agent) {
92784
- const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
93215
+ const skillsDir = join91(agentsRoot, agent, ".claude", "skills");
92785
93216
  if (!existsSync93(skillsDir))
92786
93217
  return 0;
92787
93218
  let n = 0;
@@ -92819,7 +93250,7 @@ function loadFromDir2(dir) {
92819
93250
  const files = {};
92820
93251
  const walk2 = (sub) => {
92821
93252
  for (const ent of readdirSync34(sub, { withFileTypes: true })) {
92822
- const full = join90(sub, ent.name);
93253
+ const full = join91(sub, ent.name);
92823
93254
  if (ent.isSymbolicLink()) {
92824
93255
  fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
92825
93256
  }
@@ -92829,7 +93260,7 @@ function loadFromDir2(dir) {
92829
93260
  }
92830
93261
  if (ent.isFile()) {
92831
93262
  const rel = relative3(abs, full).replace(/\\/g, "/");
92832
- files[rel] = readFileSync79(full, "utf-8");
93263
+ files[rel] = readFileSync80(full, "utf-8");
92833
93264
  }
92834
93265
  }
92835
93266
  };
@@ -92872,10 +93303,10 @@ function behavioralValidate(files) {
92872
93303
  errors2.push(`${path9} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
92873
93304
  }
92874
93305
  } else if (PY_SCRIPT_RE.test(path9)) {
92875
- const tmp = mkdtempSync6(join90(tmpdir6(), "skill-personal-py-"));
92876
- const tmpPy = join90(tmp, "check.py");
93306
+ const tmp = mkdtempSync6(join91(tmpdir7(), "skill-personal-py-"));
93307
+ const tmpPy = join91(tmp, "check.py");
92877
93308
  try {
92878
- writeFileSync33(tmpPy, content);
93309
+ writeFileSync34(tmpPy, content);
92879
93310
  const r = spawnSync17("python3", ["-m", "py_compile", tmpPy], {
92880
93311
  encoding: "utf-8"
92881
93312
  });
@@ -92897,7 +93328,7 @@ function sweepTrash(agentsRoot, agent) {
92897
93328
  for (const ent of readdirSync34(trash, { withFileTypes: true })) {
92898
93329
  if (!ent.isDirectory())
92899
93330
  continue;
92900
- const entPath = join90(trash, ent.name);
93331
+ const entPath = join91(trash, ent.name);
92901
93332
  try {
92902
93333
  const st = statSync50(entPath);
92903
93334
  if (now - st.mtimeMs > TRASH_TTL_MS) {
@@ -92917,16 +93348,16 @@ function writePersonalSkill(targetDir, files) {
92917
93348
  if (targetIsSymlink) {
92918
93349
  fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
92919
93350
  }
92920
- mkdirSync52(dirname32(targetDir), { recursive: true, mode: 493 });
92921
- 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-`));
92922
93353
  let oldRename = null;
92923
93354
  try {
92924
93355
  for (const [path9, content] of Object.entries(files)) {
92925
- const full = join90(staging, path9);
92926
- mkdirSync52(dirname32(full), { recursive: true, mode: 493 });
93356
+ const full = join91(staging, path9);
93357
+ mkdirSync53(dirname32(full), { recursive: true, mode: 493 });
92927
93358
  const fd = openSync17(full, "wx");
92928
93359
  try {
92929
- writeFileSync33(fd, content);
93360
+ writeFileSync34(fd, content);
92930
93361
  } finally {
92931
93362
  closeSync17(fd);
92932
93363
  }
@@ -92942,9 +93373,9 @@ function writePersonalSkill(targetDir, files) {
92942
93373
  } catch {}
92943
93374
  if (targetExists) {
92944
93375
  oldRename = `${targetDir}.personal-old-${Date.now()}`;
92945
- renameSync23(targetDir, oldRename);
93376
+ renameSync24(targetDir, oldRename);
92946
93377
  }
92947
- renameSync23(staging, targetDir);
93378
+ renameSync24(staging, targetDir);
92948
93379
  if (oldRename) {
92949
93380
  rmSync19(oldRename, { recursive: true, force: true });
92950
93381
  oldRename = null;
@@ -92958,7 +93389,7 @@ function writePersonalSkill(targetDir, files) {
92958
93389
  if (existsSync93(targetDir)) {
92959
93390
  rmSync19(targetDir, { recursive: true, force: true });
92960
93391
  }
92961
- renameSync23(oldRename, targetDir);
93392
+ renameSync24(oldRename, targetDir);
92962
93393
  } catch {}
92963
93394
  }
92964
93395
  throw err2;
@@ -93029,7 +93460,7 @@ function loadFiles(opts) {
93029
93460
  return loadFromDir2(p);
93030
93461
  }
93031
93462
  if (p.endsWith(".md")) {
93032
- return { "SKILL.md": readFileSync79(p, "utf-8") };
93463
+ return { "SKILL.md": readFileSync80(p, "utf-8") };
93033
93464
  }
93034
93465
  fail4(`--from must be a directory or a .md file. Got: ${opts.from}`);
93035
93466
  }
@@ -93069,10 +93500,10 @@ function editPersonalAction(name, opts) {
93069
93500
  }
93070
93501
  var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
93071
93502
  function defaultSharedRoot() {
93072
- return join90(homedir52(), ".switchroom", "skills");
93503
+ return join91(homedir52(), ".switchroom", "skills");
93073
93504
  }
93074
93505
  function defaultBundledRoot() {
93075
- return join90(homedir52(), ".switchroom", "skills", "_bundled");
93506
+ return join91(homedir52(), ".switchroom", "skills", "_bundled");
93076
93507
  }
93077
93508
  function resolveCloneSource(source, opts) {
93078
93509
  const m = CLONE_SOURCE_RE.exec(source);
@@ -93082,7 +93513,7 @@ function resolveCloneSource(source, opts) {
93082
93513
  const tier = m[1];
93083
93514
  const slug = m[2];
93084
93515
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
93085
- const dir = join90(root, slug);
93516
+ const dir = join91(root, slug);
93086
93517
  if (!existsSync93(dir)) {
93087
93518
  fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
93088
93519
  }
@@ -93098,7 +93529,7 @@ function readSourceFiles(dir) {
93098
93529
  const skipped = [];
93099
93530
  const walk2 = (sub) => {
93100
93531
  for (const ent of readdirSync34(sub, { withFileTypes: true })) {
93101
- const full = join90(sub, ent.name);
93532
+ const full = join91(sub, ent.name);
93102
93533
  if (ent.isSymbolicLink()) {
93103
93534
  continue;
93104
93535
  }
@@ -93118,7 +93549,7 @@ function readSourceFiles(dir) {
93118
93549
  fail4(`clone source has oversized file ${rel} (${st.size} bytes > ${CLONE_MAX_FILE_BYTES}); ` + `refuse to read`, 3);
93119
93550
  }
93120
93551
  } catch {}
93121
- files[rel] = readFileSync79(full, "utf-8");
93552
+ files[rel] = readFileSync80(full, "utf-8");
93122
93553
  }
93123
93554
  }
93124
93555
  };
@@ -93207,10 +93638,10 @@ function removePersonalAction(name, opts) {
93207
93638
  throw err2;
93208
93639
  }
93209
93640
  const trashRoot2 = trashDir(agentsRoot, agent);
93210
- mkdirSync52(trashRoot2, { recursive: true, mode: 493 });
93641
+ mkdirSync53(trashRoot2, { recursive: true, mode: 493 });
93211
93642
  const ts = Date.now();
93212
- const trashTarget = join90(trashRoot2, `${name}-${ts}`);
93213
- renameSync23(target, trashTarget);
93643
+ const trashTarget = join91(trashRoot2, `${name}-${ts}`);
93644
+ renameSync24(target, trashTarget);
93214
93645
  const now = new Date(ts);
93215
93646
  utimesSync(trashTarget, now, now);
93216
93647
  mirrorToConfigRepo(agent, name, null);
@@ -93228,7 +93659,7 @@ function listPersonalAction(opts) {
93228
93659
  const agent = resolveAgent(opts);
93229
93660
  const agentsRoot = resolveAgentsRoot(opts);
93230
93661
  sweepTrash(agentsRoot, agent);
93231
- const skillsDir = join90(agentsRoot, agent, ".claude", "skills");
93662
+ const skillsDir = join91(agentsRoot, agent, ".claude", "skills");
93232
93663
  const personal = [];
93233
93664
  if (existsSync93(skillsDir)) {
93234
93665
  for (const ent of readdirSync34(skillsDir, { withFileTypes: true })) {
@@ -93237,7 +93668,7 @@ function listPersonalAction(opts) {
93237
93668
  if (!ent.name.startsWith(PERSONAL_PREFIX))
93238
93669
  continue;
93239
93670
  const skillName = ent.name.slice(PERSONAL_PREFIX.length);
93240
- const skillPath = join90(skillsDir, ent.name);
93671
+ const skillPath = join91(skillsDir, ent.name);
93241
93672
  let fileCount = 0;
93242
93673
  let totalBytes = 0;
93243
93674
  const walk2 = (sub) => {
@@ -93245,10 +93676,10 @@ function listPersonalAction(opts) {
93245
93676
  if (e.isFile()) {
93246
93677
  fileCount += 1;
93247
93678
  try {
93248
- totalBytes += statSync50(join90(sub, e.name)).size;
93679
+ totalBytes += statSync50(join91(sub, e.name)).size;
93249
93680
  } catch {}
93250
93681
  } else if (e.isDirectory()) {
93251
- walk2(join90(sub, e.name));
93682
+ walk2(join91(sub, e.name));
93252
93683
  }
93253
93684
  }
93254
93685
  };
@@ -93287,11 +93718,11 @@ function registerSkillPersonalCommands(program3) {
93287
93718
  // src/cli/self-improve-propose-skill.ts
93288
93719
  import { createConnection as createConnection4 } from "node:net";
93289
93720
  import { homedir as homedir53 } from "node:os";
93290
- import { join as join91 } from "node:path";
93291
- import { readFileSync as readFileSync80 } from "node:fs";
93721
+ import { join as join92 } from "node:path";
93722
+ import { readFileSync as readFileSync81 } from "node:fs";
93292
93723
  var IPC_CONNECT_TIMEOUT_MS = 5000;
93293
93724
  function gatewaySocketPath() {
93294
- 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"));
93295
93726
  }
93296
93727
  function fail5(msg, code = 1) {
93297
93728
  console.error(msg);
@@ -93326,7 +93757,7 @@ function registerSelfImproveProposeSkillCommand(program3) {
93326
93757
  fail5("agent name required (--agent or $SWITCHROOM_AGENT_NAME)");
93327
93758
  let draft;
93328
93759
  try {
93329
- draft = JSON.parse(readFileSync80(opts.draft, "utf-8"));
93760
+ draft = JSON.parse(readFileSync81(opts.draft, "utf-8"));
93330
93761
  } catch (e) {
93331
93762
  fail5(`failed to read/parse --draft: ${e.message}`);
93332
93763
  }
@@ -93363,9 +93794,9 @@ function registerSelfImproveProposeSkillCommand(program3) {
93363
93794
  init_esm();
93364
93795
  init_helpers();
93365
93796
  var import_yaml25 = __toESM(require_dist(), 1);
93366
- 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";
93367
93798
  import { homedir as homedir54 } from "node:os";
93368
- import { join as join92, resolve as resolve56 } from "node:path";
93799
+ import { join as join93, resolve as resolve56 } from "node:path";
93369
93800
  var PERSONAL_PREFIX2 = "personal-";
93370
93801
  var BUNDLED_SUBDIR = "_bundled";
93371
93802
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
@@ -93379,12 +93810,12 @@ function defaultBundledRoot2() {
93379
93810
  return resolve56(homedir54(), ".switchroom/skills/_bundled");
93380
93811
  }
93381
93812
  function readSkillFrontmatter(skillDir) {
93382
- const mdPath = join92(skillDir, "SKILL.md");
93813
+ const mdPath = join93(skillDir, "SKILL.md");
93383
93814
  if (!existsSync94(mdPath))
93384
93815
  return null;
93385
93816
  let content;
93386
93817
  try {
93387
- content = readFileSync81(mdPath, "utf-8");
93818
+ content = readFileSync82(mdPath, "utf-8");
93388
93819
  } catch {
93389
93820
  return null;
93390
93821
  }
@@ -93412,7 +93843,7 @@ function readSkillFrontmatter(skillDir) {
93412
93843
  return { fm: parsed };
93413
93844
  }
93414
93845
  function statSkillMd(skillDir) {
93415
- const mdPath = join92(skillDir, "SKILL.md");
93846
+ const mdPath = join93(skillDir, "SKILL.md");
93416
93847
  try {
93417
93848
  const st = statSync51(mdPath);
93418
93849
  return { size: st.size, mtime: st.mtime.toISOString() };
@@ -93423,7 +93854,7 @@ function statSkillMd(skillDir) {
93423
93854
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
93424
93855
  if (!AGENT_NAME_RE3.test(agent))
93425
93856
  return [];
93426
- const skillsDir = join92(agentsRoot, agent, ".claude/skills");
93857
+ const skillsDir = join93(agentsRoot, agent, ".claude/skills");
93427
93858
  if (!existsSync94(skillsDir))
93428
93859
  return [];
93429
93860
  const out = [];
@@ -93436,7 +93867,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
93436
93867
  for (const ent of entries) {
93437
93868
  if (!ent.startsWith(PERSONAL_PREFIX2))
93438
93869
  continue;
93439
- const dirPath = join92(skillsDir, ent);
93870
+ const dirPath = join93(skillsDir, ent);
93440
93871
  try {
93441
93872
  if (!statSync51(dirPath).isDirectory())
93442
93873
  continue;
@@ -93476,7 +93907,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
93476
93907
  continue;
93477
93908
  if (ent.startsWith("."))
93478
93909
  continue;
93479
- const dirPath = join92(sharedRoot, ent);
93910
+ const dirPath = join93(sharedRoot, ent);
93480
93911
  try {
93481
93912
  if (!statSync51(dirPath).isDirectory())
93482
93913
  continue;
@@ -93512,7 +93943,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
93512
93943
  for (const ent of entries) {
93513
93944
  if (ent.startsWith("."))
93514
93945
  continue;
93515
- const dirPath = join92(bundledRoot, ent);
93946
+ const dirPath = join93(bundledRoot, ent);
93516
93947
  try {
93517
93948
  if (!statSync51(dirPath).isDirectory())
93518
93949
  continue;
@@ -93659,17 +94090,17 @@ init_helpers();
93659
94090
  init_operator_uid();
93660
94091
  import {
93661
94092
  existsSync as existsSync96,
93662
- mkdirSync as mkdirSync53,
94093
+ mkdirSync as mkdirSync54,
93663
94094
  readdirSync as readdirSync36,
93664
- readFileSync as readFileSync83,
93665
- writeFileSync as writeFileSync34,
94095
+ readFileSync as readFileSync84,
94096
+ writeFileSync as writeFileSync35,
93666
94097
  statSync as statSync52,
93667
94098
  lstatSync as lstatSync13,
93668
94099
  realpathSync as realpathSync8,
93669
94100
  copyFileSync as copyFileSync13
93670
94101
  } from "node:fs";
93671
94102
  import { homedir as homedir55 } from "node:os";
93672
- import { join as join93 } from "node:path";
94103
+ import { join as join94 } from "node:path";
93673
94104
  import { spawnSync as spawnSync20 } from "node:child_process";
93674
94105
 
93675
94106
  // src/cli/singleton-stale-cleanup.ts
@@ -94018,7 +94449,7 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir55()) {
94018
94449
  return resolved;
94019
94450
  }
94020
94451
  function resolveHostdSkillsTarget(hostHome) {
94021
- const skillsPath = join93(hostHome, ".switchroom", "skills");
94452
+ const skillsPath = join94(hostHome, ".switchroom", "skills");
94022
94453
  let st;
94023
94454
  try {
94024
94455
  st = lstatSync13(skillsPath);
@@ -94042,10 +94473,10 @@ function resolveHostdSkillsTarget(hostHome) {
94042
94473
  return target;
94043
94474
  }
94044
94475
  function hostdDir() {
94045
- return join93(homedir55(), ".switchroom", "hostd");
94476
+ return join94(homedir55(), ".switchroom", "hostd");
94046
94477
  }
94047
94478
  function hostdComposePath() {
94048
- return join93(hostdDir(), "docker-compose.yml");
94479
+ return join94(hostdDir(), "docker-compose.yml");
94049
94480
  }
94050
94481
  function backupExistingCompose() {
94051
94482
  const p = hostdComposePath();
@@ -94082,7 +94513,7 @@ async function doInstall(opts, program3) {
94082
94513
  }
94083
94514
  const dir = hostdDir();
94084
94515
  const composePath = hostdComposePath();
94085
- mkdirSync53(dir, { recursive: true });
94516
+ mkdirSync54(dir, { recursive: true });
94086
94517
  const imageTag = resolveHostdImageTag(opts.tag, cfg.release);
94087
94518
  const guard = checkDowngrade({
94088
94519
  container: "switchroom-hostd",
@@ -94115,7 +94546,7 @@ async function doInstall(opts, program3) {
94115
94546
  const bak = backupExistingCompose();
94116
94547
  if (bak)
94117
94548
  console.log(source_default.dim(` Backed up existing compose to ${bak}`));
94118
- writeFileSync34(composePath, yaml, "utf8");
94549
+ writeFileSync35(composePath, yaml, "utf8");
94119
94550
  console.log(source_default.green(` \u2713 Wrote ${composePath}`));
94120
94551
  const adminAgents = Object.entries(cfg.agents ?? {}).filter(([, a]) => a?.admin === true).map(([name]) => name);
94121
94552
  console.log(source_default.dim(` agents served (one socket each): ${allAgents.length === 0 ? "(none)" : allAgents.join(", ")}`));
@@ -94174,7 +94605,7 @@ function doStatus() {
94174
94605
  for (const name of readdirSync36(dir)) {
94175
94606
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
94176
94607
  continue;
94177
- const sockPath = join93(dir, name, "sock");
94608
+ const sockPath = join94(dir, name, "sock");
94178
94609
  if (existsSync96(sockPath)) {
94179
94610
  const st = statSync52(sockPath);
94180
94611
  if ((st.mode & 61440) === 49152) {
@@ -94223,7 +94654,7 @@ function registerHostdCommand(program3) {
94223
94654
  The log is created when hostd handles its first privileged-verb request.`));
94224
94655
  return;
94225
94656
  }
94226
- const raw = readFileSync83(logPath, "utf-8");
94657
+ const raw = readFileSync84(logPath, "utf-8");
94227
94658
  const limit = Math.max(1, parseInt(opts.tail ?? "50", 10) || 50);
94228
94659
  const filters = {
94229
94660
  agent: opts.agent,
@@ -94266,9 +94697,9 @@ The log is created when hostd handles its first privileged-verb request.`));
94266
94697
  init_source();
94267
94698
  init_helpers();
94268
94699
  init_operator_uid();
94269
- 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";
94270
94701
  import { homedir as homedir56 } from "node:os";
94271
- import { join as join94 } from "node:path";
94702
+ import { join as join95 } from "node:path";
94272
94703
  import { spawnSync as spawnSync21 } from "node:child_process";
94273
94704
  function resolveWebImageTag(explicitTag, release) {
94274
94705
  if (explicitTag)
@@ -94287,7 +94718,7 @@ function resolveWebHostHome(env2 = process.env, home2 = homedir56()) {
94287
94718
  }
94288
94719
  var WEB_COMPOSE_PROJECT = "switchroom-web";
94289
94720
  function renderWebComposeFile(opts) {
94290
- const { hostHome, imageTag, operatorUid } = opts;
94721
+ const { hostHome, imageTag, operatorUid, port } = opts;
94291
94722
  return `# AUTO-GENERATED by \`switchroom webd install\` \u2014 do not hand-edit.
94292
94723
  # Edits land at \`switchroom webd install\` time; backed up to
94293
94724
  # docker-compose.yml.bak-<ts> on overwrite.
@@ -94302,13 +94733,18 @@ services:
94302
94733
  image: ghcr.io/switchroom/switchroom-web:${imageTag}
94303
94734
  container_name: switchroom-web
94304
94735
  restart: always
94305
- # 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
94306
94737
  # tunnel (GitHub webhooks) and \`tailscale serve\` (dashboard) both
94307
94738
  # reach it there, and the dashboard CSRF gate trusts the
94308
94739
  # Tailscale-User-Login header only when the request arrives on
94309
94740
  # loopback (PR #1380). Host networking makes the container's
94310
94741
  # 127.0.0.1 the host loopback, so both keep working unchanged.
94311
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"]
94312
94748
  # The webhook.sock forward is peercred-gated by each agent's gateway
94313
94749
  # to {agent uid, SWITCHROOM_WEBHOOK_RECEIVER_UID = operator uid}.
94314
94750
  # Run as any other uid \u2192 every forward is 503'd. Operator uid also
@@ -94363,10 +94799,10 @@ services:
94363
94799
  `;
94364
94800
  }
94365
94801
  function webdDir() {
94366
- return join94(homedir56(), ".switchroom", "web");
94802
+ return join95(homedir56(), ".switchroom", "web");
94367
94803
  }
94368
94804
  function webdComposePath() {
94369
- return join94(webdDir(), "docker-compose.yml");
94805
+ return join95(webdDir(), "docker-compose.yml");
94370
94806
  }
94371
94807
  function backupExistingCompose2() {
94372
94808
  const p = webdComposePath();
@@ -94395,9 +94831,10 @@ async function doInstall2(opts, program3) {
94395
94831
  }
94396
94832
  const dir = webdDir();
94397
94833
  const composePath = webdComposePath();
94398
- mkdirSync54(dir, { recursive: true });
94834
+ mkdirSync55(dir, { recursive: true });
94399
94835
  const cfg = getConfig(program3);
94400
94836
  const imageTag = resolveWebImageTag(opts.tag, cfg.release);
94837
+ const port = cfg.web_service?.port ?? 8080;
94401
94838
  const guard = checkDowngrade({
94402
94839
  container: "switchroom-web",
94403
94840
  targetTag: imageTag,
@@ -94417,7 +94854,8 @@ async function doInstall2(opts, program3) {
94417
94854
  const yaml = renderWebComposeFile({
94418
94855
  hostHome,
94419
94856
  imageTag,
94420
- operatorUid
94857
+ operatorUid,
94858
+ port
94421
94859
  });
94422
94860
  if (opts.dryRun) {
94423
94861
  console.log(source_default.dim(`# Would write: ${composePath}`));
@@ -94428,7 +94866,7 @@ async function doInstall2(opts, program3) {
94428
94866
  const bak = backupExistingCompose2();
94429
94867
  if (bak)
94430
94868
  console.log(source_default.dim(` Backed up existing compose to ${bak}`));
94431
- writeFileSync35(composePath, yaml, "utf8");
94869
+ writeFileSync36(composePath, yaml, "utf8");
94432
94870
  try {
94433
94871
  if (typeof process.geteuid === "function" && process.geteuid() === 0) {
94434
94872
  chownSync9(dir, operatorUid, operatorUid);
@@ -94438,7 +94876,7 @@ async function doInstall2(opts, program3) {
94438
94876
  }
94439
94877
  } catch {}
94440
94878
  console.log(source_default.green(` \u2713 Wrote ${composePath}`));
94441
- 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}`));
94442
94880
  console.log(source_default.dim(` Pulling ghcr.io/switchroom/switchroom-web:${imageTag}\u2026`));
94443
94881
  const pull = runDocker2(["compose", "-p", WEB_COMPOSE_PROJECT, "-f", composePath, "pull"]);
94444
94882
  if (!pull.ok) {
@@ -94461,7 +94899,8 @@ ${up.stderr}`));
94461
94899
  console.log(source_default.dim(` Logs: docker logs switchroom-web --tail 50`));
94462
94900
  console.log(source_default.dim(` Verify: switchroom webd status`));
94463
94901
  console.log(source_default.yellow(` NOTE: the switchroom-web.service systemd unit (if still installed) also
94464
- ` + ` 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):
94465
94904
  ` + ` systemctl --user stop switchroom-web.service
94466
94905
  ` + ` systemctl --user disable switchroom-web.service`));
94467
94906
  }
@@ -94523,9 +94962,9 @@ function registerWebdCommand(program3) {
94523
94962
  // src/cli/host-repair.ts
94524
94963
  init_source();
94525
94964
  import { homedir as homedir57 } from "node:os";
94526
- import { join as join95 } from "node:path";
94965
+ import { join as join96 } from "node:path";
94527
94966
  var ARTIFACT_ALLOWLIST = {
94528
- dockerComposePluginDir: (home2) => join95(home2, ".docker", "cli-plugins", "docker-compose"),
94967
+ dockerComposePluginDir: (home2) => join96(home2, ".docker", "cli-plugins", "docker-compose"),
94529
94968
  stateSentinel: "/state"
94530
94969
  };
94531
94970
  function isStateBogusAutoDir(probe2) {
@@ -95016,6 +95455,7 @@ registerDriveCommand(program3);
95016
95455
  registerDriveMcpLauncherCommand(program3);
95017
95456
  registerM365McpLauncherCommand(program3);
95018
95457
  registerNotionMcpLauncherCommand(program3);
95458
+ registerHindsightMcpShimCommand(program3);
95019
95459
  registerDeliverFileCommand(program3);
95020
95460
  registerNotionCommand(program3);
95021
95461
  registerApplyCommand(program3);