switchroom 0.15.38 → 0.15.40

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.
@@ -28441,13 +28441,13 @@ function setAuthActive(yamlText, label) {
28441
28441
  if (typeof label !== "string" || label.length === 0) {
28442
28442
  throw new Error("setAuthActive: label must be a non-empty string");
28443
28443
  }
28444
- const doc = import_yaml9.parseDocument(yamlText);
28444
+ const doc = import_yaml10.parseDocument(yamlText);
28445
28445
  const root = doc.contents;
28446
- if (!import_yaml9.isMap(root)) {
28446
+ if (!import_yaml10.isMap(root)) {
28447
28447
  throw new Error("setAuthActive: YAML root is not a map");
28448
28448
  }
28449
28449
  const existing = root.get("auth", true);
28450
- if (import_yaml9.isMap(existing)) {
28450
+ if (import_yaml10.isMap(existing)) {
28451
28451
  if (existing.get("active") === label) {
28452
28452
  return yamlText;
28453
28453
  }
@@ -28460,9 +28460,9 @@ function setAuthActive(yamlText, label) {
28460
28460
  `) ? out : out + `
28461
28461
  `;
28462
28462
  }
28463
- var import_yaml9;
28463
+ var import_yaml10;
28464
28464
  var init_auth_active_yaml = __esm(() => {
28465
- import_yaml9 = __toESM(require_dist(), 1);
28465
+ import_yaml10 = __toESM(require_dist(), 1);
28466
28466
  });
28467
28467
 
28468
28468
  // src/auth/via-claude.ts
@@ -28476,11 +28476,11 @@ __export(exports_via_claude, {
28476
28476
  PRE_PASTE_RULES: () => PRE_PASTE_RULES,
28477
28477
  POST_PASTE_RULES: () => POST_PASTE_RULES
28478
28478
  });
28479
- import { execFileSync as execFileSync14, spawnSync as spawnSync2 } from "node:child_process";
28479
+ import { execFileSync as execFileSync14, spawnSync as spawnSync3 } from "node:child_process";
28480
28480
  import { existsSync as existsSync32, mkdirSync as mkdirSync19, readFileSync as readFileSync27 } from "node:fs";
28481
28481
  import { join as join25, resolve as resolve24 } from "node:path";
28482
28482
  function tmuxHasSession(session) {
28483
- const r = spawnSync2("tmux", ["has-session", "-t", session], {
28483
+ const r = spawnSync3("tmux", ["has-session", "-t", session], {
28484
28484
  stdio: ["ignore", "ignore", "ignore"]
28485
28485
  });
28486
28486
  return r.status === 0;
@@ -29947,12 +29947,12 @@ var init_doctor_docker = __esm(() => {
29947
29947
  // src/cli/doctor-auth-broker.ts
29948
29948
  import { existsSync as existsSync53, readFileSync as readFileSync50 } from "node:fs";
29949
29949
  import { createHash as createHash10 } from "node:crypto";
29950
- import { spawnSync as spawnSync6 } from "node:child_process";
29950
+ import { spawnSync as spawnSync7 } from "node:child_process";
29951
29951
  import { homedir as homedir27 } from "node:os";
29952
29952
  import { join as join49 } from "node:path";
29953
29953
  function defaultDockerInspect(container, format) {
29954
29954
  try {
29955
- const r = spawnSync6("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
29955
+ const r = spawnSync7("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
29956
29956
  if (r.status !== 0)
29957
29957
  return null;
29958
29958
  return r.stdout.trim();
@@ -29962,7 +29962,7 @@ function defaultDockerInspect(container, format) {
29962
29962
  }
29963
29963
  function defaultDockerExecExists(container, path4) {
29964
29964
  try {
29965
- const r = spawnSync6("docker", ["exec", container, "test", "-S", path4], { encoding: "utf-8", timeout: 5000 });
29965
+ const r = spawnSync7("docker", ["exec", container, "test", "-S", path4], { encoding: "utf-8", timeout: 5000 });
29966
29966
  return r.status === 0;
29967
29967
  } catch {
29968
29968
  return false;
@@ -29976,12 +29976,12 @@ function sha256Hex(content) {
29976
29976
  }
29977
29977
  function checkAuthBrokerServiceHealth(deps = {}) {
29978
29978
  const inspect = deps.dockerInspect ?? defaultDockerInspect;
29979
- const status = inspect(AUTH_BROKER_CONTAINER, "{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}");
29979
+ const status = inspect(AUTH_BROKER_CONTAINER2, "{{.State.Status}}|{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}");
29980
29980
  if (status === null) {
29981
29981
  return {
29982
29982
  name: "auth-broker: service health",
29983
29983
  status: "fail",
29984
- detail: `container \`${AUTH_BROKER_CONTAINER}\` not found via \`docker inspect\``,
29984
+ detail: `container \`${AUTH_BROKER_CONTAINER2}\` not found via \`docker inspect\``,
29985
29985
  fix: "Run `switchroom apply` to bring the broker online, then `switchroom doctor` again."
29986
29986
  };
29987
29987
  }
@@ -30029,7 +30029,7 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
30029
30029
  const missing = [];
30030
30030
  for (const agent of agents) {
30031
30031
  const socketPath = `/run/switchroom/auth-broker/${agent}/sock`;
30032
- if (!execExists(AUTH_BROKER_CONTAINER, socketPath)) {
30032
+ if (!execExists(AUTH_BROKER_CONTAINER2, socketPath)) {
30033
30033
  missing.push(agent);
30034
30034
  }
30035
30035
  }
@@ -30190,17 +30190,17 @@ function runAuthBrokerChecks(config, deps = {}) {
30190
30190
  const rank = { fail: 0, warn: 1, ok: 2, skip: 3 };
30191
30191
  return [...results].sort((a, b) => rank[a.status] - rank[b.status]);
30192
30192
  }
30193
- var AUTH_BROKER_CONTAINER = "switchroom-auth-broker";
30193
+ var AUTH_BROKER_CONTAINER2 = "switchroom-auth-broker";
30194
30194
  var init_doctor_auth_broker = __esm(() => {
30195
30195
  init_account_store();
30196
30196
  init_paths();
30197
30197
  });
30198
30198
 
30199
30199
  // src/cli/doctor-hostd.ts
30200
- import { spawnSync as spawnSync7 } from "node:child_process";
30200
+ import { spawnSync as spawnSync8 } from "node:child_process";
30201
30201
  function realDockerInspect(ref, format) {
30202
30202
  try {
30203
- const r = spawnSync7("docker", ["inspect", ref, "--format", format], {
30203
+ const r = spawnSync8("docker", ["inspect", ref, "--format", format], {
30204
30204
  encoding: "utf-8",
30205
30205
  timeout: 5000
30206
30206
  });
@@ -31571,6 +31571,13 @@ var init_doctor_notion = __esm(() => {
31571
31571
  });
31572
31572
 
31573
31573
  // src/cli/doctor-mcp-secrets.ts
31574
+ function entryScopeDenies(agent, allow, deny) {
31575
+ if (deny.includes(agent))
31576
+ return true;
31577
+ if (allow.length > 0 && !allow.includes(agent))
31578
+ return true;
31579
+ return false;
31580
+ }
31574
31581
  function computeMcpSecretRequirements(config) {
31575
31582
  const cfg = config;
31576
31583
  const reqs = [];
@@ -31650,20 +31657,19 @@ async function runMcpSecretChecks(config, deps = {}) {
31650
31657
  });
31651
31658
  continue;
31652
31659
  }
31653
- if (!acl.allow.includes(r.agent)) {
31654
- const updated = [...new Set([...acl.allow, r.agent])].sort().join(",");
31660
+ if (entryScopeDenies(r.agent, acl.allow, acl.deny)) {
31655
31661
  results.push({
31656
31662
  name,
31657
31663
  status: "fail",
31658
- detail: `agent '${r.agent}' loads MCP '${r.server}' but is NOT on the vault ACL for '${key}' \u2014 the broker will deny it at runtime`,
31659
- fix: `switchroom vault set ${key} --allow ${updated} (vault set overwrites the scope \u2014 re-state the full list including '${r.agent}').`
31664
+ detail: `agent '${r.agent}' loads MCP '${r.server}' but the vault entry scope for '${key}' denies it \u2014 the broker will deny at runtime`,
31665
+ fix: `switchroom vault set ${key} --allow ${[...new Set([...acl.allow, r.agent])].sort().join(",")} (vault set overwrites the scope \u2014 re-state the full list including '${r.agent}'; and drop it from --deny if present).`
31660
31666
  });
31661
31667
  continue;
31662
31668
  }
31663
31669
  results.push({
31664
31670
  name,
31665
31671
  status: "ok",
31666
- detail: `MCP '${r.server}' \u2192 '${key}' present + ACL-allowed`
31672
+ detail: `MCP '${r.server}' \u2192 '${key}' present + scope-allowed`
31667
31673
  });
31668
31674
  }
31669
31675
  }
@@ -32315,7 +32321,7 @@ __export(exports_doctor, {
32315
32321
  checkAgents: () => checkAgents,
32316
32322
  MFF_VAULT_KEY: () => MFF_VAULT_KEY
32317
32323
  });
32318
- import { execSync as execSync3, spawnSync as spawnSync8 } from "node:child_process";
32324
+ import { execSync as execSync3, spawnSync as spawnSync9 } from "node:child_process";
32319
32325
  import {
32320
32326
  accessSync as accessSync2,
32321
32327
  constants as fsConstants5,
@@ -32663,7 +32669,7 @@ function probeVaultBrokerSocketPair(agentName) {
32663
32669
  const dataPath = `/run/switchroom/broker/${agentName}/sock`;
32664
32670
  const unlockPath = `/run/switchroom/broker/${agentName}/unlock`;
32665
32671
  const script = `D=0; U=0; ` + `test -S '${dataPath}' && D=1; ` + `test -S '${unlockPath}' && U=1; ` + `echo "D=$D U=$U"`;
32666
- const r = spawnSync8("docker", ["exec", "switchroom-vault-broker", "sh", "-c", script], { stdio: "pipe", timeout: 3000 });
32672
+ const r = spawnSync9("docker", ["exec", "switchroom-vault-broker", "sh", "-c", script], { stdio: "pipe", timeout: 3000 });
32667
32673
  if (r.error || r.status === null)
32668
32674
  return "unreachable";
32669
32675
  if (r.status !== 0) {
@@ -32862,7 +32868,7 @@ function checkHindsightConsumer(config, opts) {
32862
32868
  }
32863
32869
  function probeAuthBrokerSocket(consumerName) {
32864
32870
  const containerPath = `/run/switchroom/auth-broker/${consumerName}/sock`;
32865
- const r = spawnSync8("docker", ["exec", "switchroom-auth-broker", "test", "-S", containerPath], { stdio: "pipe", timeout: 3000 });
32871
+ const r = spawnSync9("docker", ["exec", "switchroom-auth-broker", "test", "-S", containerPath], { stdio: "pipe", timeout: 3000 });
32866
32872
  if (r.error || r.status === null)
32867
32873
  return "unreachable";
32868
32874
  if (r.status === 0)
@@ -33753,7 +33759,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
33753
33759
  const python3 = which("python3") ?? "python3";
33754
33760
  let token;
33755
33761
  try {
33756
- const result = spawnSync8(python3, [authScript, "--quiet"], {
33762
+ const result = spawnSync9(python3, [authScript, "--quiet"], {
33757
33763
  timeout: timeoutMs,
33758
33764
  encoding: "utf-8",
33759
33765
  env: { ...process.env, ...env2 }
@@ -34014,7 +34020,11 @@ function registerDoctorCommand(program3) {
34014
34020
  const { getViaBrokerStructured: getViaBrokerStructured2 } = await Promise.resolve().then(() => (init_client(), exports_client));
34015
34021
  const result = await getViaBrokerStructured2(key);
34016
34022
  if (result.kind === "ok") {
34017
- return { kind: "ok", allow: result.entry.scope?.allow ?? [] };
34023
+ return {
34024
+ kind: "ok",
34025
+ allow: result.entry.scope?.allow ?? [],
34026
+ deny: result.entry.scope?.deny ?? []
34027
+ };
34018
34028
  }
34019
34029
  if (result.kind === "not_found")
34020
34030
  return { kind: "not_found" };
@@ -49780,9 +49790,9 @@ __export(exports_server, {
49780
49790
  dispatchTool: () => dispatchTool,
49781
49791
  TOOLS: () => TOOLS
49782
49792
  });
49783
- import { spawnSync as spawnSync14 } from "node:child_process";
49793
+ import { spawnSync as spawnSync15 } from "node:child_process";
49784
49794
  function execCli(args, stdin) {
49785
- const r = spawnSync14(CLI_BIN, args, {
49795
+ const r = spawnSync15(CLI_BIN, args, {
49786
49796
  encoding: "utf-8",
49787
49797
  env: process.env,
49788
49798
  timeout: 15000,
@@ -50692,8 +50702,8 @@ var {
50692
50702
  } = import__.default;
50693
50703
 
50694
50704
  // src/build-info.ts
50695
- var VERSION = "0.15.38";
50696
- var COMMIT_SHA = "d28a331f";
50705
+ var VERSION = "0.15.40";
50706
+ var COMMIT_SHA = "fd53cf02";
50697
50707
 
50698
50708
  // src/cli/agent.ts
50699
50709
  init_source();
@@ -52591,7 +52601,7 @@ function buildHumanizerEnvVars(agentDir, agent) {
52591
52601
  function buildToolSearchEnvVars() {
52592
52602
  if (process.env.SWITCHROOM_DISABLE_TOOL_SEARCH === "1")
52593
52603
  return {};
52594
- return { ENABLE_TOOL_SEARCH: process.env.SWITCHROOM_TOOL_SEARCH_MODE ?? "auto" };
52604
+ return { ENABLE_TOOL_SEARCH: process.env.SWITCHROOM_TOOL_SEARCH_MODE ?? "true" };
52595
52605
  }
52596
52606
  var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
52597
52607
  "permissions",
@@ -59722,6 +59732,134 @@ function pruneEmptyMap(doc, path) {
59722
59732
  // src/cli/auth-microsoft.ts
59723
59733
  init_helpers();
59724
59734
 
59735
+ // src/config/agent-workspace-account.ts
59736
+ var import_yaml9 = __toESM(require_dist(), 1);
59737
+ function blockKey(provider) {
59738
+ return `${provider}_workspace`;
59739
+ }
59740
+ function setAgentWorkspaceAccount(yamlText, provider, agent, account) {
59741
+ const doc = import_yaml9.parseDocument(yamlText);
59742
+ const agentsNode = doc.get("agents");
59743
+ if (!import_yaml9.isMap(agentsNode)) {
59744
+ throw new Error("switchroom.yaml has no `agents:` map");
59745
+ }
59746
+ if (!agentsNode.has(agent)) {
59747
+ throw new Error(`agent '${agent}' is not declared in switchroom.yaml`);
59748
+ }
59749
+ const path = ["agents", agent, blockKey(provider), "account"];
59750
+ const current = doc.getIn(path);
59751
+ if (current === account)
59752
+ return yamlText;
59753
+ doc.setIn(path, account);
59754
+ return String(doc);
59755
+ }
59756
+ function clearAgentWorkspaceAccount(yamlText, provider, agent) {
59757
+ const doc = import_yaml9.parseDocument(yamlText);
59758
+ const agentsNode = doc.get("agents");
59759
+ if (!import_yaml9.isMap(agentsNode))
59760
+ return yamlText;
59761
+ if (!agentsNode.has(agent))
59762
+ return yamlText;
59763
+ const blockPath = ["agents", agent, blockKey(provider)];
59764
+ const block = doc.getIn(blockPath);
59765
+ if (!import_yaml9.isMap(block))
59766
+ return yamlText;
59767
+ const accountPath = [...blockPath, "account"];
59768
+ if (doc.getIn(accountPath) === undefined)
59769
+ return yamlText;
59770
+ doc.deleteIn(accountPath);
59771
+ const after = doc.getIn(blockPath);
59772
+ if (import_yaml9.isMap(after) && after.items.length === 0) {
59773
+ doc.deleteIn(blockPath);
59774
+ }
59775
+ return String(doc);
59776
+ }
59777
+ function getAgentWorkspaceAccount(yamlText, provider, agent) {
59778
+ const doc = import_yaml9.parseDocument(yamlText);
59779
+ const v = doc.getIn(["agents", agent, blockKey(provider), "account"]);
59780
+ return typeof v === "string" ? v : null;
59781
+ }
59782
+
59783
+ // src/cli/microsoft-enable-plan.ts
59784
+ function planMicrosoftEnable(yamlText, account, agents) {
59785
+ const enabledBefore = getEnabledAgentsForMicrosoftAccount(yamlText, account) ?? [];
59786
+ let text = enableAgentsOnMicrosoftAccount(yamlText, account, agents);
59787
+ const selectorSet = [];
59788
+ const selectorConflict = [];
59789
+ for (const agent of agents) {
59790
+ const current = getAgentWorkspaceAccount(text, "microsoft", agent);
59791
+ if (current == null) {
59792
+ text = setAgentWorkspaceAccount(text, "microsoft", agent, account);
59793
+ selectorSet.push(agent);
59794
+ } else if (current.toLowerCase() !== account.toLowerCase()) {
59795
+ selectorConflict.push({ agent, current });
59796
+ }
59797
+ }
59798
+ const enabledAfter = getEnabledAgentsForMicrosoftAccount(text, account) ?? [];
59799
+ const newlyEnabled = enabledAfter.filter((a) => !enabledBefore.includes(a));
59800
+ return { text, newlyEnabled, enabledAfter, selectorSet, selectorConflict };
59801
+ }
59802
+ function planMicrosoftDisable(yamlText, account, agents) {
59803
+ const enabledBefore = getEnabledAgentsForMicrosoftAccount(yamlText, account) ?? [];
59804
+ let text = disableAgentsOnMicrosoftAccount(yamlText, account, agents);
59805
+ const enabledAfter = getEnabledAgentsForMicrosoftAccount(text, account) ?? [];
59806
+ const removed = enabledBefore.filter((a) => !enabledAfter.includes(a));
59807
+ const selectorCleared = [];
59808
+ for (const agent of removed) {
59809
+ if (getAgentWorkspaceAccount(text, "microsoft", agent)?.toLowerCase() === account.toLowerCase()) {
59810
+ text = clearAgentWorkspaceAccount(text, "microsoft", agent);
59811
+ selectorCleared.push(agent);
59812
+ }
59813
+ }
59814
+ return { text, removed, enabledAfter, selectorCleared };
59815
+ }
59816
+
59817
+ // src/auth/broker/reload-signal.ts
59818
+ import { spawnSync as spawnSync2 } from "node:child_process";
59819
+ var AUTH_BROKER_CONTAINER = "switchroom-auth-broker";
59820
+ var defaultRunner = (args) => {
59821
+ const r = spawnSync2("docker", args, { encoding: "utf8" });
59822
+ return {
59823
+ status: r.status,
59824
+ stderr: typeof r.stderr === "string" ? r.stderr : "",
59825
+ error: r.error
59826
+ };
59827
+ };
59828
+ function reloadAuthBroker(opts = {}) {
59829
+ const runner = opts.runner ?? defaultRunner;
59830
+ const container = opts.container ?? AUTH_BROKER_CONTAINER;
59831
+ const { status, stderr, error } = runner([
59832
+ "kill",
59833
+ "--signal=HUP",
59834
+ container
59835
+ ]);
59836
+ if (error) {
59837
+ if (error.code === "ENOENT") {
59838
+ return { ok: false, reason: "no-docker" };
59839
+ }
59840
+ return { ok: false, reason: "error", detail: error.message };
59841
+ }
59842
+ if (status === 0)
59843
+ return { ok: true };
59844
+ const lower = stderr.toLowerCase();
59845
+ if (lower.includes("no such container") || lower.includes("is not running") || lower.includes("cannot kill")) {
59846
+ return { ok: false, reason: "not-running" };
59847
+ }
59848
+ return { ok: false, reason: "error", detail: stderr.trim() || `exit ${status}` };
59849
+ }
59850
+ function authBrokerReloadHint(result) {
59851
+ if (result.ok)
59852
+ return null;
59853
+ switch (result.reason) {
59854
+ case "not-running":
59855
+ return "auth-broker is not running \u2014 it will pick up this change on next start.";
59856
+ case "no-docker":
59857
+ return "Could not reach Docker to hot-reload the auth-broker; restart it so the change takes effect: docker restart switchroom-auth-broker";
59858
+ case "error":
59859
+ return `Could not hot-reload the auth-broker (${result.detail ?? "unknown error"}); restart it so the change takes effect: docker restart switchroom-auth-broker`;
59860
+ }
59861
+ }
59862
+
59725
59863
  // src/auth/default-oauth-clients.ts
59726
59864
  var DEFAULT_MICROSOFT_CLIENT_ID = "9dff88fa-3126-457b-9d1d-37e58c219019";
59727
59865
  function resolveMicrosoftClientId(configClientId) {
@@ -59810,19 +59948,16 @@ function registerAuthMicrosoftSubcommands(program3, authParent) {
59810
59948
  registerAccountList2(account);
59811
59949
  }
59812
59950
  function registerEnable2(microsoftParent, program3) {
59813
- microsoftParent.command("enable <account> <agents...>").description("Enable a Microsoft account on one or more agents. Use `all` to enable on every declared agent. Appends to microsoft_accounts.<account>.enabled_for[] \u2014 does NOT mint the broker credentials (use `auth microsoft account add` for that).").action(withConfigError(async (account, agents) => {
59951
+ microsoftParent.command("enable <account> <agents...>").description("Enable a Microsoft account on one or more agents. Use `all` to enable on every declared agent. Appends to microsoft_accounts.<account>.enabled_for[] AND pins agents.<agent>.microsoft_workspace.account (both are required, else the broker returns ACCOUNT_NOT_FOUND), then hot-reloads the running auth-broker so it's live immediately. Does NOT mint the broker credentials \u2014 use `auth microsoft account add` for that.").action(withConfigError(async (account, agents) => {
59814
59952
  const normalizedAccount = validateAndNormalizeAccountEmail2(account);
59815
59953
  const config = getConfig(program3);
59816
59954
  agents = expandAllAgents2(agents, config);
59817
59955
  validateAgentSlugs(agents, config);
59818
59956
  const yamlPath = getConfigPath(program3);
59819
59957
  const before = readFileSync26(yamlPath, "utf-8");
59820
- const enabledBefore = getEnabledAgentsBefore2(before, normalizedAccount);
59821
- const after = enableAgentsOnMicrosoftAccount(before, normalizedAccount, agents);
59822
- if (after !== before)
59823
- writeFileSync16(yamlPath, after);
59824
- const enabledAfter = getEnabledAgentsForMicrosoftAccount(after, normalizedAccount) ?? [];
59825
- const newlyEnabled = enabledAfter.filter((a) => !enabledBefore.includes(a));
59958
+ const { text, newlyEnabled, enabledAfter, selectorSet, selectorConflict } = planMicrosoftEnable(before, normalizedAccount, agents);
59959
+ if (text !== before)
59960
+ writeFileSync16(yamlPath, text);
59826
59961
  console.log();
59827
59962
  if (newlyEnabled.length === 0) {
59828
59963
  console.log(`No change \u2014 all of ${agents.join(", ")} already enabled on ${source_default.bold(normalizedAccount)}.`);
@@ -59833,9 +59968,26 @@ function registerEnable2(microsoftParent, program3) {
59833
59968
  const alreadyEnabled = enabledAfter.filter((a) => !newlyEnabled.includes(a));
59834
59969
  console.log(` ${source_default.gray("already enabled on:")} ${alreadyEnabled.join(", ")}`);
59835
59970
  }
59971
+ if (selectorSet.length > 0) {
59972
+ console.log(` ${source_default.green("\u2713")} ${source_default.gray("pinned")} ${source_default.bold("microsoft_workspace.account")} ${source_default.gray("on:")} ${selectorSet.join(", ")}`);
59973
+ }
59974
+ for (const { agent, current } of selectorConflict) {
59975
+ console.log(source_default.yellow(` \u26a0 ${source_default.bold(agent)} already pins a different account (${source_default.bold(current)}) \u2014 left as-is. Repin with \`switchroom auth microsoft disable ${current} ${agent}\` first if that's unintended.`));
59976
+ }
59977
+ if (text !== before) {
59978
+ const reload = reloadAuthBroker();
59979
+ if (reload.ok) {
59980
+ console.log(` ${source_default.green("\u2713")} ${source_default.gray("auth-broker hot-reloaded \u2014 credentials are live.")}`);
59981
+ } else {
59982
+ const hint = authBrokerReloadHint(reload);
59983
+ if (hint)
59984
+ console.log(source_default.yellow(` \u26a0 ${hint}`));
59985
+ }
59986
+ }
59836
59987
  console.log();
59837
- if (newlyEnabled.length > 0) {
59838
- console.log(`Next: ${source_default.bold(`switchroom agent restart ${newlyEnabled.join(" ")}`)} so the wrapper picks up the new ACL.`);
59988
+ if (newlyEnabled.length > 0 || selectorSet.length > 0) {
59989
+ const restartTargets = [...new Set([...newlyEnabled, ...selectorSet])];
59990
+ console.log(`Next: ${source_default.bold(`switchroom agent restart ${restartTargets.join(" ")}`)} to regenerate the agent's MCP config and surface the Microsoft/OneDrive tools.`);
59839
59991
  console.log();
59840
59992
  }
59841
59993
  }));
@@ -59854,11 +60006,9 @@ function registerDisable2(microsoftParent, program3) {
59854
60006
  console.log();
59855
60007
  return;
59856
60008
  }
59857
- const after = disableAgentsOnMicrosoftAccount(before, normalizedAccount, agents);
59858
- if (after !== before)
59859
- writeFileSync16(yamlPath, after);
59860
- const enabledAfter = getEnabledAgentsForMicrosoftAccount(after, normalizedAccount) ?? [];
59861
- const removed = enabledBefore.filter((a) => !enabledAfter.includes(a));
60009
+ const { text, removed, enabledAfter, selectorCleared } = planMicrosoftDisable(before, normalizedAccount, agents);
60010
+ if (text !== before)
60011
+ writeFileSync16(yamlPath, text);
59862
60012
  console.log();
59863
60013
  if (removed.length === 0) {
59864
60014
  console.log(`No change \u2014 none of ${agents.join(", ")} were enabled on ${source_default.bold(normalizedAccount)}.`);
@@ -59870,9 +60020,22 @@ function registerDisable2(microsoftParent, program3) {
59870
60020
  } else {
59871
60021
  console.log(` ${source_default.gray("still enabled on:")} ${enabledAfter.join(", ")}`);
59872
60022
  }
60023
+ if (selectorCleared.length > 0) {
60024
+ console.log(` ${source_default.gray("cleared")} ${source_default.bold("microsoft_workspace.account")} ${source_default.gray("on:")} ${selectorCleared.join(", ")}`);
60025
+ }
60026
+ if (text !== before) {
60027
+ const reload = reloadAuthBroker();
60028
+ if (reload.ok) {
60029
+ console.log(` ${source_default.green("\u2713")} ${source_default.gray("auth-broker hot-reloaded \u2014 access revoked live.")}`);
60030
+ } else {
60031
+ const hint = authBrokerReloadHint(reload);
60032
+ if (hint)
60033
+ console.log(source_default.yellow(` \u26a0 ${hint}`));
60034
+ }
60035
+ }
59873
60036
  console.log();
59874
60037
  if (removed.length > 0) {
59875
- console.log(`Next: ${source_default.bold(`switchroom agent restart ${removed.join(" ")}`)} so the wrapper drops its access.`);
60038
+ console.log(`Next: ${source_default.bold(`switchroom agent restart ${removed.join(" ")}`)} so the agent drops the Microsoft MCP from its config.`);
59876
60039
  console.log();
59877
60040
  }
59878
60041
  }));
@@ -65682,8 +65845,8 @@ init_vault();
65682
65845
  // src/cli/vault-auto-unlock.ts
65683
65846
  init_loader();
65684
65847
  init_client();
65685
- var import_yaml10 = __toESM(require_dist(), 1);
65686
- import { spawnSync as spawnSync3 } from "node:child_process";
65848
+ var import_yaml11 = __toESM(require_dist(), 1);
65849
+ import { spawnSync as spawnSync4 } from "node:child_process";
65687
65850
  import { readFileSync as readFileSync33, writeFileSync as writeFileSync20 } from "node:fs";
65688
65851
  import { homedir as homedir18 } from "node:os";
65689
65852
  import { join as join34 } from "node:path";
@@ -65715,7 +65878,7 @@ function encryptCredential(passphrase, credPath) {
65715
65878
  }
65716
65879
  function setVaultBrokerAutoUnlock(configPath, value) {
65717
65880
  const raw = readFileSync33(configPath, "utf-8");
65718
- const doc = import_yaml10.default.parseDocument(raw);
65881
+ const doc = import_yaml11.default.parseDocument(raw);
65719
65882
  doc.setIn(["vault", "broker", "autoUnlock"], value);
65720
65883
  writeFileSync20(configPath, doc.toString(), "utf-8");
65721
65884
  }
@@ -65723,7 +65886,7 @@ var DEFAULT_COMPOSE_FILE = join34(homedir18(), ".switchroom", "compose", "docker
65723
65886
  async function applyAutoUnlock(opts = {}) {
65724
65887
  const log = opts.log ?? ((s) => console.log(s));
65725
65888
  const err = opts.err ?? ((s) => console.error(s));
65726
- const runDockerCompose = opts.runDockerCompose ?? ((args) => spawnSync3("docker", args, { stdio: "inherit" }));
65889
+ const runDockerCompose = opts.runDockerCompose ?? ((args) => spawnSync4("docker", args, { stdio: "inherit" }));
65727
65890
  const verifyTimeoutMs = opts.verifyTimeoutMs ?? 1e4;
65728
65891
  const configPath = opts.configPath ?? findConfigFile();
65729
65892
  const composeFile = opts.composeFile ?? DEFAULT_COMPOSE_FILE;
@@ -67637,9 +67800,9 @@ init_paths();
67637
67800
  import { createInterface as createInterface4 } from "node:readline";
67638
67801
 
67639
67802
  // src/cli/telegram-yaml.ts
67640
- var import_yaml11 = __toESM(require_dist(), 1);
67803
+ var import_yaml12 = __toESM(require_dist(), 1);
67641
67804
  function setLinearAgent(yamlText, agentName, opts) {
67642
- const doc = import_yaml11.parseDocument(yamlText);
67805
+ const doc = import_yaml12.parseDocument(yamlText);
67643
67806
  ensureAgent(doc, agentName);
67644
67807
  const block = { enabled: true, token: opts.token };
67645
67808
  if (opts.workspaceId)
@@ -67649,7 +67812,7 @@ function setLinearAgent(yamlText, agentName, opts) {
67649
67812
  return String(doc);
67650
67813
  }
67651
67814
  function setLinearDefaultTeam(yamlText, agentName, teamId) {
67652
- const doc = import_yaml11.parseDocument(yamlText);
67815
+ const doc = import_yaml12.parseDocument(yamlText);
67653
67816
  ensureAgent(doc, agentName);
67654
67817
  if (!doc.hasIn(["agents", agentName, "channels", "telegram", "linear_agent"])) {
67655
67818
  throw new Error(`agent '${agentName}' has no linear_agent block. Run 'switchroom linear-agent setup --agent ${agentName} --token <token>' first.`);
@@ -67664,13 +67827,13 @@ function setLinearDefaultTeam(yamlText, agentName, teamId) {
67664
67827
  return String(doc);
67665
67828
  }
67666
67829
  function setTelegramFeature(yamlText, agentName, feature, value) {
67667
- const doc = import_yaml11.parseDocument(yamlText);
67830
+ const doc = import_yaml12.parseDocument(yamlText);
67668
67831
  ensureAgent(doc, agentName);
67669
67832
  doc.setIn(["agents", agentName, "channels", "telegram", feature], value);
67670
67833
  return String(doc);
67671
67834
  }
67672
67835
  function removeTelegramFeature(yamlText, agentName, feature) {
67673
- const doc = import_yaml11.parseDocument(yamlText);
67836
+ const doc = import_yaml12.parseDocument(yamlText);
67674
67837
  if (!hasAgent(doc, agentName))
67675
67838
  return yamlText;
67676
67839
  if (!doc.hasIn(["agents", agentName, "channels", "telegram", feature])) {
@@ -67688,21 +67851,21 @@ function ensureAgent(doc, agentName) {
67688
67851
  }
67689
67852
  function hasAgent(doc, agentName) {
67690
67853
  const agents = doc.get("agents");
67691
- if (!import_yaml11.isMap(agents))
67854
+ if (!import_yaml12.isMap(agents))
67692
67855
  return false;
67693
67856
  return agents.has(agentName);
67694
67857
  }
67695
67858
  function pruneEmptyMap2(doc, path4) {
67696
67859
  const node = doc.getIn(path4);
67697
- if (import_yaml11.isMap(node) && node.items.length === 0) {
67860
+ if (import_yaml12.isMap(node) && node.items.length === 0) {
67698
67861
  doc.deleteIn(path4);
67699
67862
  }
67700
67863
  }
67701
67864
  function addWebhookSource(yamlText, agentName, source) {
67702
- const doc = import_yaml11.parseDocument(yamlText);
67865
+ const doc = import_yaml12.parseDocument(yamlText);
67703
67866
  ensureAgent(doc, agentName);
67704
67867
  const existing = doc.getIn(["agents", agentName, "channels", "telegram", "webhook_sources"]);
67705
- if (import_yaml11.isSeq(existing)) {
67868
+ if (import_yaml12.isSeq(existing)) {
67706
67869
  const seq = existing;
67707
67870
  for (const item of seq.items) {
67708
67871
  const v = item.value ?? item;
@@ -67716,10 +67879,10 @@ function addWebhookSource(yamlText, agentName, source) {
67716
67879
  return String(doc);
67717
67880
  }
67718
67881
  function addAgentSecret(yamlText, agentName, key) {
67719
- const doc = import_yaml11.parseDocument(yamlText);
67882
+ const doc = import_yaml12.parseDocument(yamlText);
67720
67883
  ensureAgent(doc, agentName);
67721
67884
  const existing = doc.getIn(["agents", agentName, "secrets"]);
67722
- if (import_yaml11.isSeq(existing)) {
67885
+ if (import_yaml12.isSeq(existing)) {
67723
67886
  const seq = existing;
67724
67887
  for (const item of seq.items) {
67725
67888
  const v = item.value ?? item;
@@ -67733,11 +67896,11 @@ function addAgentSecret(yamlText, agentName, key) {
67733
67896
  return String(doc);
67734
67897
  }
67735
67898
  function removeWebhookSource(yamlText, agentName, source) {
67736
- const doc = import_yaml11.parseDocument(yamlText);
67899
+ const doc = import_yaml12.parseDocument(yamlText);
67737
67900
  if (!hasAgent(doc, agentName))
67738
67901
  return yamlText;
67739
67902
  const existing = doc.getIn(["agents", agentName, "channels", "telegram", "webhook_sources"]);
67740
- if (!import_yaml11.isSeq(existing))
67903
+ if (!import_yaml12.isSeq(existing))
67741
67904
  return yamlText;
67742
67905
  const seq = existing;
67743
67906
  const beforeLen = seq.items.length;
@@ -68599,6 +68762,8 @@ var HINDSIGHT_DEFAULT_MEM_LIMIT = "4g";
68599
68762
  var HINDSIGHT_DEFAULT_MEM_RESERVATION = "2g";
68600
68763
  var HINDSIGHT_DEFAULT_PIDS_LIMIT = 1000;
68601
68764
  var HINDSIGHT_DEFAULT_SHM_SIZE = "2g";
68765
+ var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
68766
+ var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
68602
68767
  function isPortFree(port) {
68603
68768
  return new Promise((resolve28) => {
68604
68769
  const server = createServer4();
@@ -68690,6 +68855,16 @@ function startHindsight(ports) {
68690
68855
  `--memory-reservation=${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
68691
68856
  `--pids-limit=${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
68692
68857
  `--shm-size=${HINDSIGHT_DEFAULT_SHM_SIZE}`,
68858
+ "--health-cmd",
68859
+ HINDSIGHT_HEALTHCHECK_CMD,
68860
+ "--health-interval",
68861
+ "30s",
68862
+ "--health-timeout",
68863
+ "5s",
68864
+ "--health-retries",
68865
+ "3",
68866
+ "--health-start-period",
68867
+ "60s",
68693
68868
  "-p",
68694
68869
  `127.0.0.1:${apiPort}:8888`,
68695
68870
  "-p",
@@ -68697,6 +68872,8 @@ function startHindsight(ports) {
68697
68872
  "-v",
68698
68873
  "switchroom-hindsight-data:/home/hindsight/.pg0",
68699
68874
  "-v",
68875
+ "switchroom-hindsight-backups:/backups",
68876
+ "-v",
68700
68877
  `${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`,
68701
68878
  "--tmpfs",
68702
68879
  `/run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`,
@@ -68744,8 +68921,15 @@ function generateHindsightComposeSnippet() {
68744
68921
  ` mem_reservation: ${HINDSIGHT_DEFAULT_MEM_RESERVATION}`,
68745
68922
  ` pids_limit: ${HINDSIGHT_DEFAULT_PIDS_LIMIT}`,
68746
68923
  ` shm_size: ${HINDSIGHT_DEFAULT_SHM_SIZE}`,
68924
+ " healthcheck:",
68925
+ ` test: ${JSON.stringify(["CMD", "python3", "-c", HINDSIGHT_HEALTHCHECK_PY])}`,
68926
+ " interval: 30s",
68927
+ " timeout: 5s",
68928
+ " retries: 3",
68929
+ " start_period: 60s",
68747
68930
  " volumes:",
68748
68931
  " - switchroom-hindsight-data:/home/hindsight/.pg0",
68932
+ " - switchroom-hindsight-backups:/backups",
68749
68933
  ` - ${HINDSIGHT_BROKER_SOCK_VOLUME}:/run/switchroom/auth-broker`,
68750
68934
  " tmpfs:",
68751
68935
  ` - /run/claude-creds:rw,mode=0700,uid=${HINDSIGHT_DEFAULT_UID},gid=${HINDSIGHT_DEFAULT_UID}`,
@@ -68753,6 +68937,7 @@ function generateHindsightComposeSnippet() {
68753
68937
  "",
68754
68938
  "volumes:",
68755
68939
  " switchroom-hindsight-data:",
68940
+ " switchroom-hindsight-backups:",
68756
68941
  ` ${HINDSIGHT_BROKER_SOCK_VOLUME}:`,
68757
68942
  " external: true",
68758
68943
  " # Bound by the switchroom-auth-broker singleton in the main",
@@ -68763,17 +68948,17 @@ function generateHindsightComposeSnippet() {
68763
68948
  }
68764
68949
  async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFAULT_UID) {
68765
68950
  const fs4 = await import("node:fs");
68766
- const { parseDocument: parseDocument6, isMap: isMap6, isSeq: isSeq4, YAMLMap, YAMLSeq } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
68951
+ const { parseDocument: parseDocument7, isMap: isMap7, isSeq: isSeq4, YAMLMap, YAMLSeq } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
68767
68952
  const { atomicWriteFileSync: atomicWriteFileSync3 } = await Promise.resolve().then(() => (init_atomic(), exports_atomic));
68768
68953
  const raw = fs4.readFileSync(configPath, "utf-8");
68769
- const doc = parseDocument6(raw);
68954
+ const doc = parseDocument7(raw);
68770
68955
  const root = doc.contents;
68771
- if (!isMap6(root)) {
68956
+ if (!isMap7(root)) {
68772
68957
  return { added: false, reason: "switchroom.yaml root is not a map" };
68773
68958
  }
68774
68959
  const authRaw = root.get("auth", true);
68775
68960
  let authNode;
68776
- if (isMap6(authRaw)) {
68961
+ if (isMap7(authRaw)) {
68777
68962
  authNode = authRaw;
68778
68963
  } else {
68779
68964
  authNode = new YAMLMap;
@@ -68788,7 +68973,7 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
68788
68973
  doc.setIn(["auth", "consumers"], consumersNode);
68789
68974
  }
68790
68975
  for (const item of consumersNode.items) {
68791
- if (isMap6(item)) {
68976
+ if (isMap7(item)) {
68792
68977
  const name = item.get("name");
68793
68978
  if (name === HINDSIGHT_CONSUMER_NAME) {
68794
68979
  return { added: false, reason: "already present" };
@@ -68814,7 +68999,7 @@ async function ensureHindsightConsumer(configPath, account, uid = HINDSIGHT_DEFA
68814
68999
 
68815
69000
  // src/cli/memory.ts
68816
69001
  init_loader();
68817
- var import_yaml12 = __toESM(require_dist(), 1);
69002
+ var import_yaml13 = __toESM(require_dist(), 1);
68818
69003
  import { existsSync as existsSync44, readFileSync as readFileSync40, writeFileSync as writeFileSync23 } from "node:fs";
68819
69004
  import { join as join39 } from "node:path";
68820
69005
  function readRecallLog(agentDir, limit) {
@@ -69019,7 +69204,7 @@ Cross-agent reflection plan
69019
69204
  try {
69020
69205
  if (existsSync44(configPath)) {
69021
69206
  const raw = readFileSync40(configPath, "utf-8");
69022
- const doc = import_yaml12.default.parseDocument(raw);
69207
+ const doc = import_yaml13.default.parseDocument(raw);
69023
69208
  if (!doc.has("memory")) {
69024
69209
  doc.set("memory", { backend: "hindsight", shared_collection: "shared", config: { provider, url } });
69025
69210
  } else {
@@ -69143,7 +69328,7 @@ import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } fro
69143
69328
  init_lifecycle();
69144
69329
  init_manager();
69145
69330
  init_hindsight();
69146
- import { spawnSync as spawnSync5 } from "node:child_process";
69331
+ import { spawnSync as spawnSync6 } from "node:child_process";
69147
69332
  import { existsSync as existsSync47, readFileSync as readFileSync43, statSync as statSync22 } from "node:fs";
69148
69333
  import { resolve as resolve28, join as join44 } from "node:path";
69149
69334
 
@@ -73829,54 +74014,6 @@ function installGlobalErrorHandlers() {
73829
74014
  init_loader();
73830
74015
  init_merge();
73831
74016
 
73832
- // src/config/agent-workspace-account.ts
73833
- var import_yaml13 = __toESM(require_dist(), 1);
73834
- function blockKey(provider) {
73835
- return `${provider}_workspace`;
73836
- }
73837
- function setAgentWorkspaceAccount(yamlText, provider, agent, account) {
73838
- const doc = import_yaml13.parseDocument(yamlText);
73839
- const agentsNode = doc.get("agents");
73840
- if (!import_yaml13.isMap(agentsNode)) {
73841
- throw new Error("switchroom.yaml has no `agents:` map");
73842
- }
73843
- if (!agentsNode.has(agent)) {
73844
- throw new Error(`agent '${agent}' is not declared in switchroom.yaml`);
73845
- }
73846
- const path4 = ["agents", agent, blockKey(provider), "account"];
73847
- const current = doc.getIn(path4);
73848
- if (current === account)
73849
- return yamlText;
73850
- doc.setIn(path4, account);
73851
- return String(doc);
73852
- }
73853
- function clearAgentWorkspaceAccount(yamlText, provider, agent) {
73854
- const doc = import_yaml13.parseDocument(yamlText);
73855
- const agentsNode = doc.get("agents");
73856
- if (!import_yaml13.isMap(agentsNode))
73857
- return yamlText;
73858
- if (!agentsNode.has(agent))
73859
- return yamlText;
73860
- const blockPath = ["agents", agent, blockKey(provider)];
73861
- const block = doc.getIn(blockPath);
73862
- if (!import_yaml13.isMap(block))
73863
- return yamlText;
73864
- const accountPath = [...blockPath, "account"];
73865
- if (doc.getIn(accountPath) === undefined)
73866
- return yamlText;
73867
- doc.deleteIn(accountPath);
73868
- const after = doc.getIn(blockPath);
73869
- if (import_yaml13.isMap(after) && after.items.length === 0) {
73870
- doc.deleteIn(blockPath);
73871
- }
73872
- return String(doc);
73873
- }
73874
- function getAgentWorkspaceAccount(yamlText, provider, agent) {
73875
- const doc = import_yaml13.parseDocument(yamlText);
73876
- const v = doc.getIn(["agents", agent, blockKey(provider), "account"]);
73877
- return typeof v === "string" ? v : null;
73878
- }
73879
-
73880
74017
  // src/web/config-edit-plan.ts
73881
74018
  init_schema();
73882
74019
  var import_yaml14 = __toESM(require_dist(), 1);
@@ -73923,7 +74060,7 @@ import {
73923
74060
  rmSync as rmrf,
73924
74061
  writeFileSync as writeFileSync25
73925
74062
  } from "node:fs";
73926
- import { spawnSync as spawnSync4 } from "node:child_process";
74063
+ import { spawnSync as spawnSync5 } from "node:child_process";
73927
74064
  import { tmpdir as tmpdir4 } from "node:os";
73928
74065
  import { join as join41 } from "node:path";
73929
74066
 
@@ -73938,7 +74075,7 @@ function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "
73938
74075
  mkdirSync26(join41(dir, "new"), { recursive: true });
73939
74076
  writeFileSync25(join41(dir, "cur", name), before);
73940
74077
  writeFileSync25(join41(dir, "new", name), after);
73941
- const r = spawnSync4(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
74078
+ const r = spawnSync5(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
73942
74079
  if (r.status === 0)
73943
74080
  return "";
73944
74081
  if (r.status !== 1) {
@@ -74725,7 +74862,7 @@ function inspectEnv(container, keys) {
74725
74862
  const out = {};
74726
74863
  for (const k of keys)
74727
74864
  out[k] = null;
74728
- const res = spawnSync5("docker", ["inspect", "--format", "{{json .Config.Env}}", container], { encoding: "utf-8", timeout: 4000 });
74865
+ const res = spawnSync6("docker", ["inspect", "--format", "{{json .Config.Env}}", container], { encoding: "utf-8", timeout: 4000 });
74729
74866
  if (res.error || res.status !== 0 || !res.stdout)
74730
74867
  return out;
74731
74868
  try {
@@ -77637,7 +77774,7 @@ init_source();
77637
77774
  init_loader();
77638
77775
  init_lifecycle();
77639
77776
  import { cpSync as cpSync2, existsSync as existsSync58, mkdirSync as mkdirSync32, readFileSync as readFileSync52, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync26, chownSync as chownSync5 } from "node:fs";
77640
- import { spawnSync as spawnSync9 } from "node:child_process";
77777
+ import { spawnSync as spawnSync10 } from "node:child_process";
77641
77778
  import { join as join61, dirname as dirname15, resolve as resolve35 } from "node:path";
77642
77779
  import { homedir as homedir37 } from "node:os";
77643
77780
 
@@ -77707,7 +77844,7 @@ function rebuildRefusalMessage(scriptPath) {
77707
77844
  }
77708
77845
  function planUpdate(opts) {
77709
77846
  const composePath = opts.composePath ?? DEFAULT_COMPOSE_PATH;
77710
- const runner = opts.runner ?? defaultRunner;
77847
+ const runner = opts.runner ?? defaultRunner2;
77711
77848
  const scriptPath = opts.scriptPath ?? process.argv[1] ?? "";
77712
77849
  const steps = [];
77713
77850
  if (opts.pin) {
@@ -77912,8 +78049,8 @@ function planUpdate(opts) {
77912
78049
  });
77913
78050
  return steps;
77914
78051
  }
77915
- function defaultRunner(cmd, args) {
77916
- const r = spawnSync9(cmd, args, { stdio: "inherit" });
78052
+ function defaultRunner2(cmd, args) {
78053
+ const r = spawnSync10(cmd, args, { stdio: "inherit" });
77917
78054
  return { status: r.status ?? 1 };
77918
78055
  }
77919
78056
  function writeMarkerInPreferredLocation(agent, reason, runner) {
@@ -77988,7 +78125,7 @@ function defaultStatusProbe(composePath) {
77988
78125
  }
77989
78126
  let serviceList = [];
77990
78127
  try {
77991
- const r = spawnSync9("docker", ["compose", "-p", "switchroom", "-f", composePath, "config", "--services"], { encoding: "utf-8", timeout: 1e4 });
78128
+ const r = spawnSync10("docker", ["compose", "-p", "switchroom", "-f", composePath, "config", "--services"], { encoding: "utf-8", timeout: 1e4 });
77992
78129
  if (r.status !== 0) {
77993
78130
  warnings.push(`docker compose config --services failed: ${r.stderr?.trim() ?? r.error?.message ?? "unknown"}`);
77994
78131
  return { cliVersion, cliBuiltAt, services, warnings };
@@ -78005,7 +78142,7 @@ function defaultStatusProbe(composePath) {
78005
78142
  let containerCreatedAt = null;
78006
78143
  let status = "<unknown>";
78007
78144
  try {
78008
- const r = spawnSync9("docker", ["inspect", "-f", "{{.Config.Image}}|{{.Created}}|{{.State.Status}}", containerName2], { encoding: "utf-8", timeout: 5000 });
78145
+ const r = spawnSync10("docker", ["inspect", "-f", "{{.Config.Image}}|{{.Created}}|{{.State.Status}}", containerName2], { encoding: "utf-8", timeout: 5000 });
78009
78146
  if (r.status === 0) {
78010
78147
  const [img, created, st] = r.stdout.trim().split("|");
78011
78148
  image = img ?? null;
@@ -78021,7 +78158,7 @@ function defaultStatusProbe(composePath) {
78021
78158
  let imagePulledAt = null;
78022
78159
  if (image) {
78023
78160
  try {
78024
- const r = spawnSync9("docker", ["image", "inspect", "-f", "{{.Id}}|{{.Created}}|{{.Metadata.LastTagTime}}", image], { encoding: "utf-8", timeout: 5000 });
78161
+ const r = spawnSync10("docker", ["image", "inspect", "-f", "{{.Id}}|{{.Created}}|{{.Metadata.LastTagTime}}", image], { encoding: "utf-8", timeout: 5000 });
78025
78162
  if (r.status === 0) {
78026
78163
  const [id, created, lastTag] = r.stdout.trim().split("|");
78027
78164
  imageDigestShort = id?.replace(/^sha256:/, "").slice(0, 12) ?? null;
@@ -78167,7 +78304,7 @@ function registerUpdateCommand(program3) {
78167
78304
 
78168
78305
  // src/cli/rollout.ts
78169
78306
  init_helpers();
78170
- import { spawnSync as spawnSync10 } from "node:child_process";
78307
+ import { spawnSync as spawnSync11 } from "node:child_process";
78171
78308
  import { readFileSync as readFileSync53, chownSync as chownSync6, statSync as statSync27 } from "node:fs";
78172
78309
  init_atomic();
78173
78310
  function normalizeVersion(v) {
@@ -78339,11 +78476,11 @@ function registerRolloutCommand(program3) {
78339
78476
  const scriptPath = process.argv[1] ?? "switchroom";
78340
78477
  const deps = {
78341
78478
  run: (args) => {
78342
- const r = spawnSync10(process.execPath, [scriptPath, ...args], { stdio: "inherit" });
78479
+ const r = spawnSync11(process.execPath, [scriptPath, ...args], { stdio: "inherit" });
78343
78480
  return { status: r.status ?? 1 };
78344
78481
  },
78345
78482
  probeVersion: (agent) => {
78346
- const r = spawnSync10("docker", ["exec", `switchroom-${agent}`, "sh", "-lc", "switchroom --version"], { encoding: "utf8" });
78483
+ const r = spawnSync11("docker", ["exec", `switchroom-${agent}`, "sh", "-lc", "switchroom --version"], { encoding: "utf8" });
78347
78484
  if (r.status !== 0)
78348
78485
  return null;
78349
78486
  return (r.stdout ?? "").trim().split(`
@@ -79842,7 +79979,7 @@ init_helpers();
79842
79979
  init_loader();
79843
79980
  import { existsSync as existsSync64 } from "node:fs";
79844
79981
  import { resolve as resolve40, sep as sep3 } from "node:path";
79845
- import { spawnSync as spawnSync11 } from "node:child_process";
79982
+ import { spawnSync as spawnSync12 } from "node:child_process";
79846
79983
 
79847
79984
  // src/agents/workspace.ts
79848
79985
  import { readFile as readFile2, stat } from "node:fs/promises";
@@ -80555,7 +80692,7 @@ function registerWorkspaceCommand(program3) {
80555
80692
  process.exit(1);
80556
80693
  }
80557
80694
  const editor = process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
80558
- const child = spawnSync11(editor, [target], { stdio: "inherit" });
80695
+ const child = spawnSync12(editor, [target], { stdio: "inherit" });
80559
80696
  if (child.status !== 0 && child.status !== null) {
80560
80697
  process.exit(child.status);
80561
80698
  }
@@ -80622,7 +80759,7 @@ function registerWorkspaceCommand(program3) {
80622
80759
  `);
80623
80760
  return;
80624
80761
  }
80625
- const statusResult = spawnSync11("git", ["status", "--short"], {
80762
+ const statusResult = spawnSync12("git", ["status", "--short"], {
80626
80763
  cwd: dir,
80627
80764
  encoding: "utf-8"
80628
80765
  });
@@ -80637,7 +80774,7 @@ function registerWorkspaceCommand(program3) {
80637
80774
  return;
80638
80775
  }
80639
80776
  const message = opts.message || `checkpoint: ${new Date().toISOString()}`;
80640
- const addResult = spawnSync11("git", ["add", "-A"], {
80777
+ const addResult = spawnSync12("git", ["add", "-A"], {
80641
80778
  cwd: dir,
80642
80779
  encoding: "utf-8"
80643
80780
  });
@@ -80646,7 +80783,7 @@ function registerWorkspaceCommand(program3) {
80646
80783
  `);
80647
80784
  process.exit(1);
80648
80785
  }
80649
- const commitResult = spawnSync11("git", ["commit", "-m", message], {
80786
+ const commitResult = spawnSync12("git", ["commit", "-m", message], {
80650
80787
  cwd: dir,
80651
80788
  encoding: "utf-8"
80652
80789
  });
@@ -80655,7 +80792,7 @@ function registerWorkspaceCommand(program3) {
80655
80792
  `);
80656
80793
  process.exit(1);
80657
80794
  }
80658
- const shaResult = spawnSync11("git", ["rev-parse", "--short", "HEAD"], {
80795
+ const shaResult = spawnSync12("git", ["rev-parse", "--short", "HEAD"], {
80659
80796
  cwd: dir,
80660
80797
  encoding: "utf-8"
80661
80798
  });
@@ -80676,7 +80813,7 @@ function registerWorkspaceCommand(program3) {
80676
80813
  `);
80677
80814
  return;
80678
80815
  }
80679
- const child = spawnSync11("git", ["status", "--short"], {
80816
+ const child = spawnSync12("git", ["status", "--short"], {
80680
80817
  cwd: dir,
80681
80818
  stdio: "inherit"
80682
80819
  });
@@ -83273,14 +83410,14 @@ async function computeAgentConnectionIssues(config, agentName, vaultAclReader) {
83273
83410
  });
83274
83411
  continue;
83275
83412
  }
83276
- if (!acl.allow.includes(agentName)) {
83413
+ if (entryScopeDenies(agentName, acl.allow, acl.deny)) {
83277
83414
  const updated = [...new Set([...acl.allow, agentName])].sort().join(",");
83278
83415
  issues.push({
83279
83416
  server: r.server,
83280
83417
  key,
83281
83418
  kind: "acl",
83282
- detail: `MCP '${r.server}' not on the vault ACL for '${key}' \u2014 broker will deny at runtime`,
83283
- fix: `switchroom vault set ${key} --allow ${updated} (re-state the full list)`
83419
+ detail: `MCP '${r.server}' \u2014 vault entry scope for '${key}' denies this agent \u2014 broker will deny at runtime`,
83420
+ fix: `switchroom vault set ${key} --allow ${updated} (re-state the full list; drop from --deny if present)`
83284
83421
  });
83285
83422
  }
83286
83423
  }
@@ -83710,7 +83847,11 @@ Applying switchroom config...
83710
83847
  const { getViaBrokerStructured: getViaBrokerStructured2 } = await Promise.resolve().then(() => (init_client(), exports_client));
83711
83848
  const result = await getViaBrokerStructured2(key);
83712
83849
  if (result.kind === "ok") {
83713
- return { kind: "ok", allow: result.entry.scope?.allow ?? [] };
83850
+ return {
83851
+ kind: "ok",
83852
+ allow: result.entry.scope?.allow ?? [],
83853
+ deny: result.entry.scope?.deny ?? []
83854
+ };
83714
83855
  }
83715
83856
  if (result.kind === "not_found")
83716
83857
  return { kind: "not_found" };
@@ -85771,7 +85912,7 @@ import {
85771
85912
  } from "node:fs";
85772
85913
  import { tmpdir as tmpdir5, homedir as homedir46 } from "node:os";
85773
85914
  import { dirname as dirname24, join as join81, relative as relative2, resolve as resolve48 } from "node:path";
85774
- import { spawnSync as spawnSync12 } from "node:child_process";
85915
+ import { spawnSync as spawnSync13 } from "node:child_process";
85775
85916
 
85776
85917
  // src/cli/skill-common.ts
85777
85918
  var import_yaml23 = __toESM(require_dist(), 1);
@@ -86024,7 +86165,7 @@ function loadFromDir(dir) {
86024
86165
  function loadFromTarball(tarPath) {
86025
86166
  const isGz = tarPath.endsWith(".gz") || tarPath.endsWith(".tgz");
86026
86167
  const listFlags = isGz ? ["-tzf"] : ["-tf"];
86027
- const list2 = spawnSync12("tar", [...listFlags, tarPath], {
86168
+ const list2 = spawnSync13("tar", [...listFlags, tarPath], {
86028
86169
  encoding: "utf-8",
86029
86170
  stdio: ["ignore", "pipe", "pipe"]
86030
86171
  });
@@ -86041,7 +86182,7 @@ function loadFromTarball(tarPath) {
86041
86182
  const staging = mkdtempSync5(join81(tmpdir5(), "skill-apply-extract-"));
86042
86183
  try {
86043
86184
  const flags = isGz ? ["-xzf"] : ["-xf"];
86044
- const r = spawnSync12("tar", [
86185
+ const r = spawnSync13("tar", [
86045
86186
  ...flags,
86046
86187
  tarPath,
86047
86188
  "-C",
@@ -86116,7 +86257,7 @@ function validatePayload(name, files) {
86116
86257
  if (errors2.length === 0) {
86117
86258
  for (const [path8, content] of Object.entries(files)) {
86118
86259
  if (SH_SCRIPT_RE2.test(path8)) {
86119
- const r = spawnSync12("bash", ["-n"], {
86260
+ const r = spawnSync13("bash", ["-n"], {
86120
86261
  input: content,
86121
86262
  encoding: "utf-8"
86122
86263
  });
@@ -86128,7 +86269,7 @@ function validatePayload(name, files) {
86128
86269
  const tmpPy = join81(tmp, "check.py");
86129
86270
  try {
86130
86271
  writeFileSync40(tmpPy, content);
86131
- const r = spawnSync12("python3", ["-m", "py_compile", tmpPy], {
86272
+ const r = spawnSync13("python3", ["-m", "py_compile", tmpPy], {
86132
86273
  encoding: "utf-8"
86133
86274
  });
86134
86275
  if (r.status !== 0) {
@@ -86291,7 +86432,7 @@ function registerSkillCommand(program3) {
86291
86432
  \u2713 Wrote ${name} to ${currentDir}`));
86292
86433
  const applyBin = process.argv[1] ?? "switchroom";
86293
86434
  console.log(source_default.gray(`Running \`switchroom apply --non-interactive\`...`));
86294
- const r = spawnSync12(process.argv0, [applyBin, "apply", "--non-interactive"], { stdio: "inherit" });
86435
+ const r = spawnSync13(process.argv0, [applyBin, "apply", "--non-interactive"], { stdio: "inherit" });
86295
86436
  if (r.status !== 0) {
86296
86437
  console.error(source_default.yellow(`(warning: \`switchroom apply\` exited ${r.status} \u2014 skill is ` + `in the pool but symlinks may not be refreshed. Re-run manually.)`));
86297
86438
  }
@@ -86323,7 +86464,7 @@ import {
86323
86464
  } from "node:fs";
86324
86465
  import { dirname as dirname25, join as join82, relative as relative3, resolve as resolve49 } from "node:path";
86325
86466
  import { homedir as homedir47, tmpdir as tmpdir6 } from "node:os";
86326
- import { spawnSync as spawnSync13 } from "node:child_process";
86467
+ import { spawnSync as spawnSync14 } from "node:child_process";
86327
86468
  init_helpers();
86328
86469
  init_source();
86329
86470
  var PERSONAL_PREFIX = "personal-";
@@ -86512,7 +86653,7 @@ function behavioralValidate(files) {
86512
86653
  const errors2 = [];
86513
86654
  for (const [path8, content] of Object.entries(files)) {
86514
86655
  if (SH_SCRIPT_RE.test(path8)) {
86515
- const r = spawnSync13("bash", ["-n"], { input: content, encoding: "utf-8" });
86656
+ const r = spawnSync14("bash", ["-n"], { input: content, encoding: "utf-8" });
86516
86657
  if (r.status !== 0) {
86517
86658
  errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
86518
86659
  }
@@ -86521,7 +86662,7 @@ function behavioralValidate(files) {
86521
86662
  const tmpPy = join82(tmp, "check.py");
86522
86663
  try {
86523
86664
  writeFileSync41(tmpPy, content);
86524
- const r = spawnSync13("python3", ["-m", "py_compile", tmpPy], {
86665
+ const r = spawnSync14("python3", ["-m", "py_compile", tmpPy], {
86525
86666
  encoding: "utf-8"
86526
86667
  });
86527
86668
  if (r.status !== 0) {
@@ -87214,15 +87355,15 @@ init_helpers();
87214
87355
  import { existsSync as existsSync84, mkdirSync as mkdirSync48, readdirSync as readdirSync33, readFileSync as readFileSync72, writeFileSync as writeFileSync42, statSync as statSync35, copyFileSync as copyFileSync12 } from "node:fs";
87215
87356
  import { homedir as homedir49 } from "node:os";
87216
87357
  import { join as join84 } from "node:path";
87217
- import { spawnSync as spawnSync16 } from "node:child_process";
87358
+ import { spawnSync as spawnSync17 } from "node:child_process";
87218
87359
 
87219
87360
  // src/cli/deploy-version-guard.ts
87220
- import { spawnSync as spawnSync15 } from "node:child_process";
87221
- var defaultRunner2 = (args) => {
87222
- const r = spawnSync15("docker", args, { encoding: "utf8" });
87361
+ import { spawnSync as spawnSync16 } from "node:child_process";
87362
+ var defaultRunner3 = (args) => {
87363
+ const r = spawnSync16("docker", args, { encoding: "utf8" });
87223
87364
  return { ok: r.status === 0, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
87224
87365
  };
87225
- function deployedImageTag(container, run = defaultRunner2) {
87366
+ function deployedImageTag(container, run = defaultRunner3) {
87226
87367
  const r = run(["inspect", "-f", "{{.Config.Image}}", container]);
87227
87368
  if (!r.ok)
87228
87369
  return null;
@@ -87391,7 +87532,7 @@ function backupExistingCompose() {
87391
87532
  return bak;
87392
87533
  }
87393
87534
  function runDocker(args) {
87394
- const r = spawnSync16("docker", args, { encoding: "utf8" });
87535
+ const r = spawnSync17("docker", args, { encoding: "utf8" });
87395
87536
  return {
87396
87537
  ok: r.status === 0,
87397
87538
  stdout: r.stdout ?? "",
@@ -87593,7 +87734,7 @@ init_helpers();
87593
87734
  import { existsSync as existsSync85, mkdirSync as mkdirSync49, writeFileSync as writeFileSync43, copyFileSync as copyFileSync13 } from "node:fs";
87594
87735
  import { homedir as homedir50 } from "node:os";
87595
87736
  import { join as join85 } from "node:path";
87596
- import { spawnSync as spawnSync17 } from "node:child_process";
87737
+ import { spawnSync as spawnSync18 } from "node:child_process";
87597
87738
  function resolveWebImageTag(explicitTag, release) {
87598
87739
  if (explicitTag)
87599
87740
  return explicitTag;
@@ -87692,7 +87833,7 @@ function backupExistingCompose2() {
87692
87833
  return bak;
87693
87834
  }
87694
87835
  function runDocker2(args) {
87695
- const r = spawnSync17("docker", args, { encoding: "utf8" });
87836
+ const r = spawnSync18("docker", args, { encoding: "utf8" });
87696
87837
  return {
87697
87838
  ok: r.status === 0,
87698
87839
  stdout: r.stdout ?? "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.15.38",
3
+ "version": "0.15.40",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,6 +28,8 @@ You are operating in the **{{topicName}}** {{#if topicEmoji}}{{topicEmoji}} {{/i
28
28
  - Prefer `trash` over `rm` when available (recoverable beats gone forever).
29
29
  - Safe to do freely: read files, explore, organize, search the web, check calendars, work within this workspace.
30
30
  - Ask first: sending emails, tweets, public posts, anything that leaves the machine, anything you're uncertain about.
31
+ - **Batch foreseeable approvals; don't drip surprises.** When you can already see that several actions will each need the user's approval, tell them up front which approvals are coming and why. Request independent ones together so they can decide once; for dependent ones (one's input comes from another), say what you're doing first and what approval comes next — a permission card should never arrive out of the blue.
32
+ - **A timed-out approval isn't a denial.** If a request came back denied only because the user was away (a timeout, not an explicit "no"), don't silently abandon it. When they're back, remind them it's still pending and re-offer it if they still want it.
31
33
 
32
34
  ## Execution Bias
33
35
 
@@ -54519,11 +54519,11 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
54519
54519
  }
54520
54520
 
54521
54521
  // ../src/build-info.ts
54522
- var VERSION = "0.15.38";
54523
- var COMMIT_SHA = "d28a331f";
54524
- var COMMIT_DATE = "2026-06-18T11:43:40+10:00";
54522
+ var VERSION = "0.15.40";
54523
+ var COMMIT_SHA = "fd53cf02";
54524
+ var COMMIT_DATE = "2026-06-18T13:00:59+10:00";
54525
54525
  var LATEST_PR = null;
54526
- var COMMITS_AHEAD_OF_TAG = 13;
54526
+ var COMMITS_AHEAD_OF_TAG = 4;
54527
54527
 
54528
54528
  // gateway/boot-version.ts
54529
54529
  function formatRelativeAgo(iso) {