switchroom 0.15.31 → 0.15.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.
@@ -23724,11 +23724,12 @@ function generateCompose(opts) {
23724
23724
  const containerNamePrefix = opts.containerNamePrefix ?? "switchroom";
23725
23725
  const hostControlEnabled = config.host_control?.enabled !== false;
23726
23726
  const hostHomeForChecks = opts.homeDir ?? process.env.HOME ?? "";
23727
+ const probeHome = opts.probeHomeDir ?? hostHomeForChecks;
23727
23728
  const switchroomConfigPath = resolveConfigMountSource(opts.switchroomConfigPath, homePrefix);
23728
23729
  const bundledSkillsPoolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
23729
23730
  let resolvedAnalyticsId = null;
23730
- if (hostHomeForChecks !== "") {
23731
- const idPath = join10(hostHomeForChecks, ".switchroom", "analytics-id");
23731
+ if (probeHome !== "") {
23732
+ const idPath = join10(probeHome, ".switchroom", "analytics-id");
23732
23733
  if (existsSync15(idPath)) {
23733
23734
  try {
23734
23735
  const raw = readFileSync13(idPath, "utf-8").trim();
@@ -23911,7 +23912,7 @@ function generateCompose(opts) {
23911
23912
  if (a.strippedCaps.length > 0) {
23912
23913
  warn(`compose: stripping cap_add ${JSON.stringify(a.strippedCaps)} from agent "${a.name}" (Docker mode forbids capability extras; see RFC \u00a7security)`);
23913
23914
  }
23914
- emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, switchroomConfigPath, containerNamePrefix, {
23915
+ emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, probeHome, switchroomConfigPath, containerNamePrefix, {
23915
23916
  analyticsId: resolvedAnalyticsId,
23916
23917
  telemetryDisabled,
23917
23918
  posthogKeyOverride,
@@ -23941,7 +23942,7 @@ function generateCompose(opts) {
23941
23942
  return lines.join(`
23942
23943
  `);
23943
23944
  }
23944
- function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, switchroomConfigPath, containerNamePrefix, posthog, bundledSkillsPoolDir, hostControlEnabled, operatorUid) {
23945
+ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefix, hostHomeForChecks, probeHome, switchroomConfigPath, containerNamePrefix, posthog, bundledSkillsPoolDir, hostControlEnabled, operatorUid) {
23945
23946
  lines.push(` agent-${a.name}:`);
23946
23947
  emitImageOrBuild(lines, "agent", imageTag, buildMode, buildContext);
23947
23948
  lines.push(` container_name: ${containerNamePrefix}-${a.name}`);
@@ -24053,14 +24054,14 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
24053
24054
  lines.push(` - /:/host:rw`);
24054
24055
  }
24055
24056
  if (a.admin === true) {
24056
- if (existsSync15(`${hostHomeForChecks}/.switchroom/vault-audit.log`)) {
24057
+ if (existsSync15(`${probeHome}/.switchroom/vault-audit.log`)) {
24057
24058
  lines.push(` - ${homePrefix}/.switchroom/vault-audit.log:/state/agent/home/.switchroom/vault-audit.log:ro`);
24058
24059
  }
24059
- if (existsSync15(`${hostHomeForChecks}/.switchroom/host-control-audit.log`)) {
24060
+ if (existsSync15(`${probeHome}/.switchroom/host-control-audit.log`)) {
24060
24061
  lines.push(` - ${homePrefix}/.switchroom/host-control-audit.log:/state/agent/home/.switchroom/host-control-audit.log:ro`);
24061
24062
  }
24062
24063
  }
24063
- if (hostControlEnabled && existsSync15(`${hostHomeForChecks}/.switchroom/hostd/${a.name}`)) {
24064
+ if (hostControlEnabled && existsSync15(`${probeHome}/.switchroom/hostd/${a.name}`)) {
24064
24065
  lines.push(` - ${homePrefix}/.switchroom/hostd/${a.name}:/run/switchroom/hostd/${a.name}`);
24065
24066
  }
24066
24067
  if (a.bindMounts.length > 0) {
@@ -24078,38 +24079,38 @@ function emitAgentService(lines, a, imageTag, buildMode, buildContext, homePrefi
24078
24079
  lines.push(` - ${homePrefix}/.switchroom/logs/${a.name}:/var/log/switchroom`);
24079
24080
  lines.push(` - ${homePrefix}/.switchroom/agents/${a.name}:${homePrefix}/.switchroom/agents/${a.name}`);
24080
24081
  lines.push(` - ${homePrefix}/.claude/projects/${a.name}:${homePrefix}/.claude/projects/${a.name}`);
24081
- if (existsSync15(`${hostHomeForChecks}/.switchroom/skills`)) {
24082
+ if (existsSync15(`${probeHome}/.switchroom/skills`)) {
24082
24083
  lines.push(` - ${homePrefix}/.switchroom/skills:${homePrefix}/.switchroom/skills:ro`);
24083
24084
  }
24084
- if (existsSync15(`${hostHomeForChecks}/.switchroom/mcp-launchers`)) {
24085
+ if (existsSync15(`${probeHome}/.switchroom/mcp-launchers`)) {
24085
24086
  lines.push(` - ${homePrefix}/.switchroom/mcp-launchers:${homePrefix}/.switchroom/mcp-launchers:ro`);
24086
24087
  }
24087
- if (existsSync15(`${hostHomeForChecks}/.switchroom/fleet`)) {
24088
+ if (existsSync15(`${probeHome}/.switchroom/fleet`)) {
24088
24089
  lines.push(` - ${homePrefix}/.switchroom/fleet:${homePrefix}/.switchroom/fleet:ro`);
24089
24090
  }
24090
- if (existsSync15(`${hostHomeForChecks}/.switchroom/credentials/${a.name}`)) {
24091
+ if (existsSync15(`${probeHome}/.switchroom/credentials/${a.name}`)) {
24091
24092
  lines.push(` - ${homePrefix}/.switchroom/credentials/${a.name}:${homePrefix}/.switchroom/credentials:ro`);
24092
24093
  }
24093
24094
  try {
24094
- mkdirSync11(`${hostHomeForChecks}/.switchroom/audit/${a.name}`, { recursive: true });
24095
+ mkdirSync11(`${probeHome}/.switchroom/audit/${a.name}`, { recursive: true });
24095
24096
  } catch {}
24096
24097
  try {
24097
- mkdirSync11(`${hostHomeForChecks}/.switchroom/agents/${a.name}/schedule.d`, { recursive: true });
24098
+ mkdirSync11(`${probeHome}/.switchroom/agents/${a.name}/schedule.d`, { recursive: true });
24098
24099
  } catch {}
24099
24100
  lines.push(` - ${homePrefix}/.switchroom/audit/${a.name}:${homePrefix}/.switchroom/audit/${a.name}:rw`);
24100
- if (existsSync15(`${hostHomeForChecks}/.switchroom-config`)) {
24101
+ if (existsSync15(`${probeHome}/.switchroom-config`)) {
24101
24102
  try {
24102
- mkdirSync11(`${hostHomeForChecks}/.switchroom-config/agents/${a.name}/personal-skills`, { recursive: true });
24103
+ mkdirSync11(`${probeHome}/.switchroom-config/agents/${a.name}/personal-skills`, { recursive: true });
24103
24104
  } catch {}
24104
24105
  lines.push(` - ${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:${homePrefix}/.switchroom-config/agents/${a.name}/personal-skills:rw`);
24105
24106
  }
24106
- if (existsSync15(`${hostHomeForChecks}/.switchroom/bin/webkite`)) {
24107
+ if (existsSync15(`${probeHome}/.switchroom/bin/webkite`)) {
24107
24108
  lines.push(` - ${homePrefix}/.switchroom/bin/webkite:/usr/local/bin/webkite:ro`);
24108
24109
  }
24109
- if (existsSync15(`${hostHomeForChecks}/.cloakbrowser`)) {
24110
+ if (existsSync15(`${probeHome}/.cloakbrowser`)) {
24110
24111
  lines.push(` - ${homePrefix}/.cloakbrowser:/state/agent/home/.cloakbrowser:ro`);
24111
24112
  }
24112
- if (existsSync15(`${hostHomeForChecks}/.switchroom/webkite/config.toml`)) {
24113
+ if (existsSync15(`${probeHome}/.switchroom/webkite/config.toml`)) {
24113
24114
  lines.push(` - ${homePrefix}/.switchroom/webkite/config.toml:/state/agent/home/.config/webkite/config.toml:ro`);
24114
24115
  }
24115
24116
  if (bundledSkillsPoolDir && existsSync15(bundledSkillsPoolDir) && !bundledSkillsPoolDir.startsWith(`${hostHomeForChecks}/.switchroom/skills`)) {
@@ -50128,6 +50129,7 @@ var init_server3 = __esm(() => {
50128
50129
  // src/mcp/hostd/server.ts
50129
50130
  var exports_server2 = {};
50130
50131
  __export(exports_server2, {
50132
+ wireTimeoutForOp: () => wireTimeoutForOp,
50131
50133
  runHostdMcpServer: () => runHostdMcpServer,
50132
50134
  resolveAuditLogPath: () => resolveAuditLogPath,
50133
50135
  getLastUpdateApplyStatus: () => getLastUpdateApplyStatus,
@@ -50142,6 +50144,9 @@ function selfSocketPath() {
50142
50144
  function makeRequestId(prefix) {
50143
50145
  return `${prefix}-${Date.now()}-${randomBytes15(4).toString("hex")}`;
50144
50146
  }
50147
+ function wireTimeoutForOp(op) {
50148
+ return WIRE_TIMEOUT_MS_BY_OP[op] ?? DEFAULT_WIRE_TIMEOUT_MS;
50149
+ }
50145
50150
  async function dispatchTool2(name, args) {
50146
50151
  if (name === "get_status") {
50147
50152
  return getLastUpdateApplyStatus();
@@ -50278,7 +50283,7 @@ async function dispatchTool2(name, args) {
50278
50283
  }
50279
50284
  let resp;
50280
50285
  try {
50281
- resp = await hostdRequest({ socketPath: sockPath, timeoutMs: 1e4 }, req);
50286
+ resp = await hostdRequest({ socketPath: sockPath, timeoutMs: wireTimeoutForOp(req.op) }, req);
50282
50287
  } catch (err2) {
50283
50288
  return errorText2(`hostd wire error (request_id=${req.request_id}): ` + `${err2.message}`);
50284
50289
  }
@@ -50355,7 +50360,7 @@ async function runHostdMcpServer() {
50355
50360
  const transport = new StdioServerTransport;
50356
50361
  await server.connect(transport);
50357
50362
  }
50358
- var SELF_AGENT, TOOLS2;
50363
+ var SELF_AGENT, DEFAULT_WIRE_TIMEOUT_MS = 1e4, WIRE_TIMEOUT_MS_BY_OP, TOOLS2;
50359
50364
  var init_server4 = __esm(() => {
50360
50365
  init_server2();
50361
50366
  init_stdio2();
@@ -50363,6 +50368,9 @@ var init_server4 = __esm(() => {
50363
50368
  init_client4();
50364
50369
  init_audit_reader();
50365
50370
  SELF_AGENT = process.env.SWITCHROOM_AGENT_NAME ?? "";
50371
+ WIRE_TIMEOUT_MS_BY_OP = {
50372
+ config_propose_edit: 11 * 60 * 1000
50373
+ };
50366
50374
  TOOLS2 = [
50367
50375
  {
50368
50376
  name: "agent_restart",
@@ -50493,7 +50501,7 @@ var init_server4 = __esm(() => {
50493
50501
  },
50494
50502
  {
50495
50503
  name: "config_propose_edit",
50496
- description: "Propose a unified-diff patch against /state/config/switchroom.yaml. " + "The host validates the patch (applies cleanly + post-patch yaml parses " + "against the config schema + no secret leak), raises a Telegram approval " + "card in the OPERATOR's primary chat (NOT yours \u2014 the requesting agent's " + "chat is not the approval surface), and on Allow applies it in place and " + "reconciles (rolling back if reconcile fails); returns " + `result:"completed" on success. Use this \u2014 behind the operator's tap \u2014 ` + "to amend config instead of asking the operator to hand-edit the yaml. " + "Admin agents may propose ANY field; non-admin agents are confined to " + "their own agents.<self>.tools.allow. Requires " + "hostd.config_edit_enabled=true (operator opt-in; default off) \u2014 returns " + "E_CONFIG_EDIT_DISABLED otherwise. An applied edit is not live in the " + "running agent until it restarts (the approval card names which agents " + "to bounce).",
50504
+ description: "Propose a unified-diff patch against /state/config/switchroom.yaml. " + "The host validates the patch (applies cleanly + post-patch yaml parses " + "against the config schema + no secret leak), raises a Telegram approval " + "card in the OPERATOR's primary chat (NOT yours \u2014 the requesting agent's " + "chat is not the approval surface), and on Allow applies it in place and " + "reconciles (rolling back if reconcile fails); returns " + `result:"completed" on success. Use this \u2014 behind the operator's tap \u2014 ` + "to amend config instead of asking the operator to hand-edit the yaml. " + "Admin agents may propose ANY field; non-admin agents are confined to " + "their own agents.<self>.tools.allow. Requires " + "hostd.config_edit_enabled=true (operator opt-in; default off) \u2014 returns " + "E_CONFIG_EDIT_DISABLED otherwise. An applied edit is not live in the " + "running agent until it restarts (the approval card names which agents " + "to bounce). This call BLOCKS until the operator taps Allow/Deny (or the " + "~10-min approval window expires) \u2014 that is expected, NOT a hang. Issue " + "it ONCE and wait for the single result; do NOT re-fire while waiting " + "(a duplicate identical proposal is collapsed onto the pending one, but " + "re-firing only adds confusion). On a genuine failure you get a " + "structured error (E_*); surface that honestly rather than falling back " + "to asking the operator to hand-edit the yaml.",
50497
50505
  inputSchema: {
50498
50506
  type: "object",
50499
50507
  required: ["unified_diff", "reason", "target_path"],
@@ -50546,8 +50554,8 @@ var {
50546
50554
  } = import__.default;
50547
50555
 
50548
50556
  // src/build-info.ts
50549
- var VERSION = "0.15.31";
50550
- var COMMIT_SHA = "9c6a82c6";
50557
+ var VERSION = "0.15.32";
50558
+ var COMMIT_SHA = "61984cef";
50551
50559
 
50552
50560
  // src/cli/agent.ts
50553
50561
  init_source();
@@ -54637,6 +54645,7 @@ async function writeComposeFile(opts) {
54637
54645
  buildMode: opts.buildMode ?? "pull",
54638
54646
  buildContext: opts.buildContext,
54639
54647
  homeDir: process.env.SWITCHROOM_HOST_HOME || homedir6(),
54648
+ probeHomeDir: homedir6(),
54640
54649
  switchroomConfigPath: resolvedConfigPath,
54641
54650
  operatorUid
54642
54651
  });
@@ -15087,7 +15087,7 @@ import {
15087
15087
  closeSync as closeSync2
15088
15088
  } from "node:fs";
15089
15089
  import { join as join3, dirname as dirname4, resolve as resolve5 } from "node:path";
15090
- import { randomUUID as randomUUID2, randomBytes } from "node:crypto";
15090
+ import { createHash as createHash5, randomUUID as randomUUID2, randomBytes } from "node:crypto";
15091
15091
 
15092
15092
  // src/host-control/protocol.ts
15093
15093
  var MAX_FRAME_BYTES = 64 * 1024;
@@ -21729,6 +21729,23 @@ class HostdServer {
21729
21729
  ]).op("config_propose_edit").caller(caller.kind === "agent" ? "agent" : "operator").agentName(caller.kind === "agent" ? caller.name : undefined).build(req.request_id, Date.now() - started);
21730
21730
  }
21731
21731
  const callerName = caller.kind === "agent" ? caller.name : "operator";
21732
+ const dedupeKey = `${callerName}:${createHash5("sha256").update(req.args.unified_diff).digest("hex")}`;
21733
+ const pending = this.inflightConfigProposals.get(dedupeKey);
21734
+ if (pending) {
21735
+ process.stderr.write(`hostd: config_propose_edit — collapsed identical in-flight proposal from ${callerName} (dedupe)
21736
+ `);
21737
+ return await pending;
21738
+ }
21739
+ const run = this.runConfigProposeApprovalAndApply(req, caller, callerName, configPath, verdict.postApplyContent, started);
21740
+ this.inflightConfigProposals.set(dedupeKey, run);
21741
+ try {
21742
+ return await run;
21743
+ } finally {
21744
+ this.inflightConfigProposals.delete(dedupeKey);
21745
+ }
21746
+ }
21747
+ inflightConfigProposals = new Map;
21748
+ async runConfigProposeApprovalAndApply(req, caller, callerName, configPath, postApply, started) {
21732
21749
  const approvalId = (this.opts.generateApprovalId ?? defaultApprovalId)();
21733
21750
  const approval = await this.opts.approvalGateway.requestApproval({
21734
21751
  requestId: approvalId,
@@ -21765,7 +21782,6 @@ class HostdServer {
21765
21782
  });
21766
21783
  return this.reconcileFailedRolledBack(`snapshot read failed: ${e.message}`, req, caller, started);
21767
21784
  }
21768
- const postApply = verdict.postApplyContent;
21769
21785
  try {
21770
21786
  writeFileInPlacePreservingInode(configPath, postApply);
21771
21787
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.15.31",
3
+ "version": "0.15.32",
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": {
@@ -54460,10 +54460,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
54460
54460
  }
54461
54461
 
54462
54462
  // ../src/build-info.ts
54463
- var VERSION = "0.15.31";
54464
- var COMMIT_SHA = "9c6a82c6";
54465
- var COMMIT_DATE = "2026-06-15T11:52:29Z";
54466
- var LATEST_PR = 2380;
54463
+ var VERSION = "0.15.32";
54464
+ var COMMIT_SHA = "61984cef";
54465
+ var COMMIT_DATE = "2026-06-15T12:32:06Z";
54466
+ var LATEST_PR = 2384;
54467
54467
  var COMMITS_AHEAD_OF_TAG = 0;
54468
54468
 
54469
54469
  // gateway/boot-version.ts