switchroom 0.20.21 → 0.21.0

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 (36) hide show
  1. package/dist/auth-broker/index.js +1 -1
  2. package/dist/cli/switchroom.js +1953 -1539
  3. package/dist/host-control/main.js +286 -122
  4. package/dist/vault/approvals/kernel-server.js +1 -1
  5. package/dist/vault/broker/server.js +1 -1
  6. package/package.json +1 -1
  7. package/skills/switchroom-release/SKILL.md +12 -1
  8. package/telegram-plugin/dist/gateway/gateway.js +1405 -851
  9. package/telegram-plugin/gateway/always-allow-persist-queue.ts +2 -2
  10. package/telegram-plugin/gateway/boot-beacon.ts +9 -2
  11. package/telegram-plugin/gateway/gateway-heartbeat.ts +4 -3
  12. package/telegram-plugin/gateway/gateway.ts +42 -15
  13. package/telegram-plugin/gateway/inbound-router.ts +31 -6
  14. package/telegram-plugin/gateway/missed-approvals-store.ts +2 -2
  15. package/telegram-plugin/gateway/pending-card-store.ts +2 -2
  16. package/telegram-plugin/gateway/privacy-state.ts +2 -2
  17. package/telegram-plugin/gateway/scoped-grant-store.ts +2 -2
  18. package/telegram-plugin/gateway/system-message-observer.ts +242 -0
  19. package/telegram-plugin/gateway/turn-active-marker.ts +3 -4
  20. package/telegram-plugin/history.ts +178 -11
  21. package/telegram-plugin/registry/turns-schema.ts +8 -2
  22. package/telegram-plugin/tests/buzz-mirror.test.ts +12 -1
  23. package/telegram-plugin/tests/card-history-lane.test.ts +394 -0
  24. package/telegram-plugin/tests/system-message-observer.test.ts +216 -0
  25. package/vendor/hindsight-memory/scripts/drain_pending.py +88 -4
  26. package/vendor/hindsight-memory/scripts/lib/config.py +112 -11
  27. package/vendor/hindsight-memory/scripts/recall.py +11 -2
  28. package/vendor/hindsight-memory/scripts/reconcile_tail.py +36 -0
  29. package/vendor/hindsight-memory/scripts/retain.py +6 -1
  30. package/vendor/hindsight-memory/scripts/tests/test_config_retain_env.py +99 -0
  31. package/vendor/hindsight-memory/scripts/tests/test_recall_types_filter.py +81 -0
  32. package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +113 -0
  33. package/vendor/hindsight-memory/settings.json +2 -2
  34. package/vendor/hindsight-memory/tests/test_config.py +8 -3
  35. package/vendor/hindsight-memory/tests/test_hooks.py +10 -1
  36. package/vendor/hindsight-memory/tests/test_retain_context.py +69 -0
@@ -21096,9 +21096,9 @@ var require_commander = __commonJS((exports2) => {
21096
21096
 
21097
21097
  // src/host-control/main.ts
21098
21098
  init_loader();
21099
- import { homedir as homedir8 } from "node:os";
21100
- import { existsSync as existsSync17, readdirSync as readdirSync7 } from "node:fs";
21101
- import { join as join13, resolve as resolve10 } from "node:path";
21099
+ import { homedir as homedir9 } from "node:os";
21100
+ import { existsSync as existsSync18, readdirSync as readdirSync7 } from "node:fs";
21101
+ import { join as join14, resolve as resolve10 } from "node:path";
21102
21102
 
21103
21103
  // src/host-control/config-degraded.ts
21104
21104
  init_loader();
@@ -21565,13 +21565,13 @@ function allocateAgentUid(name) {
21565
21565
  }
21566
21566
 
21567
21567
  // src/build-info.ts
21568
- var VERSION = "0.20.21";
21568
+ var VERSION = "0.21.0";
21569
21569
 
21570
21570
  // src/setup/hindsight-recall-passthrough.ts
21571
21571
  var HINDSIGHT_RECALL_TAG_WEIGHT_SEED = Object.freeze({ sidechain: 0.8 });
21572
21572
  var HINDSIGHT_RECALL_PROMPT_PREAMBLE_DEFAULT = "Relevant memories from past conversations (prioritize recent when " + "conflicting). Only use memories that are directly useful to continue " + "this conversation; ignore the rest:";
21573
21573
  var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
21574
- budget: "low",
21574
+ budget: "mid",
21575
21575
  maxTokens: 1024,
21576
21576
  preferObservations: true,
21577
21577
  contextTurns: 2,
@@ -22356,11 +22356,11 @@ import { spawn as spawn2, spawnSync as spawnSync4 } from "node:child_process";
22356
22356
  import { mkdir as mkdir2, chmod, chown, unlink, appendFile } from "node:fs/promises";
22357
22357
  import {
22358
22358
  readdirSync as readdirSync6,
22359
- existsSync as existsSync16,
22360
- readFileSync as readFileSync12,
22361
- writeFileSync as writeFileSync6,
22362
- renameSync as renameSync5,
22363
- mkdirSync as mkdirSync5,
22359
+ existsSync as existsSync17,
22360
+ readFileSync as readFileSync13,
22361
+ writeFileSync as writeFileSync7,
22362
+ renameSync as renameSync6,
22363
+ mkdirSync as mkdirSync6,
22364
22364
  openSync as openSync5,
22365
22365
  ftruncateSync,
22366
22366
  writeSync as writeSync3,
@@ -22369,7 +22369,7 @@ import {
22369
22369
  copyFileSync as copyFileSync3,
22370
22370
  unlinkSync as unlinkSync5
22371
22371
  } from "node:fs";
22372
- import { join as join12, dirname as dirname9, resolve as resolve9 } from "node:path";
22372
+ import { join as join13, dirname as dirname9, resolve as resolve9 } from "node:path";
22373
22373
  import { createHash as createHash6, randomUUID as randomUUID2, randomBytes } from "node:crypto";
22374
22374
 
22375
22375
  // src/host-control/protocol.ts
@@ -28540,6 +28540,169 @@ function resolveOperatorUid(ownershipDeps = {}) {
28540
28540
  return resolveOperatorUidFromOwnership(ownershipDeps);
28541
28541
  }
28542
28542
 
28543
+ // src/cli/host-cli-stamp.ts
28544
+ import {
28545
+ chownSync as chownSync2,
28546
+ existsSync as existsSync14,
28547
+ mkdirSync as mkdirSync5,
28548
+ readFileSync as readFileSync10,
28549
+ renameSync as renameSync5,
28550
+ statSync as statSync8,
28551
+ writeFileSync as writeFileSync5
28552
+ } from "node:fs";
28553
+ import { homedir as homedir7 } from "node:os";
28554
+ import { join as join9 } from "node:path";
28555
+ var HOST_CLI_STAMP_FILENAME = "host-cli.json";
28556
+ var SWITCHROOM_HOME_MARKER2 = "switchroom.yaml";
28557
+ var CONTAINER_OPERATOR_HOME2 = "/host-home";
28558
+ function resolveIo(io) {
28559
+ return {
28560
+ env: io.env ?? process.env,
28561
+ home: io.home ?? homedir7(),
28562
+ exists: io.exists ?? ((p) => existsSync14(p)),
28563
+ readFile: io.readFile ?? ((p) => readFileSync10(p, "utf8")),
28564
+ writeFile: io.writeFile ?? ((p, c) => {
28565
+ const tmp = `${p}.${process.pid}.tmp`;
28566
+ writeFileSync5(tmp, c, { mode: 420 });
28567
+ renameSync5(tmp, p);
28568
+ }),
28569
+ statUid: io.statUid ?? ((p) => {
28570
+ try {
28571
+ return statSync8(p).uid;
28572
+ } catch {
28573
+ return;
28574
+ }
28575
+ }),
28576
+ userForUid: io.userForUid ?? userFromPasswd,
28577
+ getuid: io.getuid ?? (() => process.getuid?.()),
28578
+ chown: io.chown ?? ((p, uid, gid) => chownSync2(p, uid, gid)),
28579
+ statOwner: io.statOwner ?? ((p) => {
28580
+ try {
28581
+ const st = statSync8(p);
28582
+ return { uid: st.uid, gid: st.gid };
28583
+ } catch {
28584
+ return;
28585
+ }
28586
+ })
28587
+ };
28588
+ }
28589
+ function userFromPasswd(uid) {
28590
+ try {
28591
+ for (const line of readFileSync10("/etc/passwd", "utf8").split(`
28592
+ `)) {
28593
+ const f = line.split(":");
28594
+ if (f[2] !== undefined && parseInt(f[2], 10) === uid && f[0])
28595
+ return f[0];
28596
+ }
28597
+ } catch {}
28598
+ return;
28599
+ }
28600
+ function homeFromPasswd(user) {
28601
+ try {
28602
+ for (const line of readFileSync10("/etc/passwd", "utf8").split(`
28603
+ `)) {
28604
+ const f = line.split(":");
28605
+ if (f[0] === user && f[5])
28606
+ return f[5];
28607
+ }
28608
+ } catch {}
28609
+ return;
28610
+ }
28611
+ function stampHomeCandidates(env, home2, homeForUser = homeFromPasswd) {
28612
+ const sudoUser = env.SUDO_USER;
28613
+ const roots = [
28614
+ env.SWITCHROOM_HOST_HOME?.trim(),
28615
+ sudoUser && sudoUser !== "root" ? homeForUser(sudoUser) : undefined,
28616
+ home2,
28617
+ CONTAINER_OPERATOR_HOME2
28618
+ ];
28619
+ const out = [];
28620
+ for (const r of roots) {
28621
+ if (!r)
28622
+ continue;
28623
+ const dir = join9(r, ".switchroom");
28624
+ if (!out.includes(dir))
28625
+ out.push(dir);
28626
+ }
28627
+ return out;
28628
+ }
28629
+ function resolveStampDir(io = {}) {
28630
+ const { env, home: home2, exists } = resolveIo(io);
28631
+ const homeForUser = (u) => homeFromPasswd(u);
28632
+ for (const dir of stampHomeCandidates(env, home2, homeForUser)) {
28633
+ if (exists(join9(dir, SWITCHROOM_HOME_MARKER2)))
28634
+ return dir;
28635
+ }
28636
+ return;
28637
+ }
28638
+ function readHostCliStamp(io = {}) {
28639
+ try {
28640
+ const dir = resolveStampDir(io);
28641
+ if (!dir)
28642
+ return;
28643
+ const { exists, readFile } = resolveIo(io);
28644
+ const path3 = join9(dir, HOST_CLI_STAMP_FILENAME);
28645
+ if (!exists(path3))
28646
+ return;
28647
+ const parsed = JSON.parse(readFile(path3));
28648
+ if (typeof parsed.version !== "string" || parsed.version.length === 0)
28649
+ return;
28650
+ return {
28651
+ version: parsed.version,
28652
+ installKind: parsed.installKind ?? "unknown",
28653
+ path: typeof parsed.path === "string" ? parsed.path : "",
28654
+ ...parsed.npmPrefix ? { npmPrefix: parsed.npmPrefix } : {},
28655
+ ...parsed.ownerUser ? { ownerUser: parsed.ownerUser } : {},
28656
+ ...typeof parsed.ownerUid === "number" ? { ownerUid: parsed.ownerUid } : {}
28657
+ };
28658
+ } catch {
28659
+ return;
28660
+ }
28661
+ }
28662
+ function hostCliInstallCommand(stamp, target) {
28663
+ const bare = target.replace(/^v/, "");
28664
+ if (!stamp) {
28665
+ return `upgrade the host operator CLI to ${bare} host-side ` + `(\`switchroom update\` for a static-binary install; ` + `\`npm i -g switchroom@${bare}\` for an npm install — as the user who owns ` + `the npm prefix)`;
28666
+ }
28667
+ switch (stamp.installKind) {
28668
+ case "npm-global": {
28669
+ const npmCmd = `npm i -g${stamp.npmPrefix ? ` --prefix ${stamp.npmPrefix}` : ""} switchroom@${bare}`;
28670
+ if (stamp.ownerUid === 0)
28671
+ return `sudo ${npmCmd}`;
28672
+ const who = stamp.ownerUser ?? (stamp.ownerUid !== undefined ? `uid ${stamp.ownerUid}` : undefined);
28673
+ const asNote = who ? ` — run as ${who}, NOT under sudo` : ` — run as the user owning ${stamp.path}, NOT under sudo`;
28674
+ return `${npmCmd}${asNote}`;
28675
+ }
28676
+ case "static-binary":
28677
+ return `switchroom update --pin v${bare} (replaces the static binary at ${stamp.path})`;
28678
+ case "source-checkout":
28679
+ return `git pull && bun install && npm run build (source checkout at ${stamp.path})`;
28680
+ case "container":
28681
+ case "unknown":
28682
+ default:
28683
+ return `upgrade the host operator CLI at ${stamp.path} to ${bare} using the ` + `method it was installed with`;
28684
+ }
28685
+ }
28686
+ function hostCliInstallShellCommand(stamp, target) {
28687
+ if (!stamp)
28688
+ return;
28689
+ const bare = target.replace(/^v/, "");
28690
+ if (stamp.installKind === "static-binary")
28691
+ return `switchroom update --pin v${bare}`;
28692
+ if (stamp.installKind === "npm-global" && stamp.ownerUid === 0) {
28693
+ return `sudo npm i -g${stamp.npmPrefix ? ` --prefix ${stamp.npmPrefix}` : ""} switchroom@${bare}`;
28694
+ }
28695
+ return;
28696
+ }
28697
+ function compareHostCliToTarget(stamp, target) {
28698
+ if (!stamp)
28699
+ return "unknown";
28700
+ const cmp = compareReleaseTags(`v${stamp.version.replace(/^v/, "")}`, `v${target.replace(/^v/, "")}`);
28701
+ if (cmp === null)
28702
+ return "unknown";
28703
+ return cmp < 0 ? "behind" : "current-or-ahead";
28704
+ }
28705
+
28543
28706
  // src/cli/rollout.ts
28544
28707
  init_atomic();
28545
28708
 
@@ -28558,15 +28721,15 @@ function hostdTemplateRegenVerdict(target, fromVersion) {
28558
28721
  }
28559
28722
 
28560
28723
  // src/cli/resolve-version.ts
28561
- import { existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
28562
- import { dirname as dirname7, join as join9 } from "node:path";
28724
+ import { existsSync as existsSync15, readFileSync as readFileSync11 } from "node:fs";
28725
+ import { dirname as dirname7, join as join10 } from "node:path";
28563
28726
  function readPackageVersion() {
28564
28727
  let dir = import.meta.dirname;
28565
28728
  for (let i = 0;i < 12 && dir && dir !== "/"; i++) {
28566
- const p = join9(dir, "package.json");
28567
- if (existsSync14(p)) {
28729
+ const p = join10(dir, "package.json");
28730
+ if (existsSync15(p)) {
28568
28731
  try {
28569
- const pkg = JSON.parse(readFileSync10(p, "utf-8"));
28732
+ const pkg = JSON.parse(readFileSync11(p, "utf-8"));
28570
28733
  if (pkg?.name === "switchroom" && typeof pkg.version === "string") {
28571
28734
  return pkg.version;
28572
28735
  }
@@ -28866,7 +29029,7 @@ var ROLLOUT_RUN_TIMEOUT_MS = envMs("SWITCHROOM_ROLLOUT_RUN_TIMEOUT_MS", 20 * 60
28866
29029
  var ROLLOUT_PROBE_TIMEOUT_MS = envMs("SWITCHROOM_ROLLOUT_PROBE_TIMEOUT_MS", 60 * 1000);
28867
29030
 
28868
29031
  // src/cli/write-compose.ts
28869
- import { chownSync as chownSync2 } from "node:fs";
29032
+ import { chownSync as chownSync3 } from "node:fs";
28870
29033
  import { mkdir, readFile, writeFile, rename, copyFile } from "node:fs/promises";
28871
29034
  init_merge();
28872
29035
  var AGENT_IMAGE_TAG_RE = /image:\s*\S*switchroom-agent:(\S+)/;
@@ -28956,7 +29119,7 @@ async function restoreComposeBackup(composePath, opts = {}) {
28956
29119
  const operatorUid = resolveOperatorUid();
28957
29120
  if (operatorUid !== undefined && process.geteuid?.() === 0) {
28958
29121
  try {
28959
- chownSync2(composePath, operatorUid, operatorUid);
29122
+ chownSync3(composePath, operatorUid, operatorUid);
28960
29123
  } catch {}
28961
29124
  }
28962
29125
  return { restored: true, imageTag: composeImageTag(content) };
@@ -28967,8 +29130,8 @@ var import__2 = __toESM(require_commander(), 1);
28967
29130
 
28968
29131
  // src/cli/update.ts
28969
29132
  init_loader();
28970
- import { join as join10, dirname as dirname8 } from "node:path";
28971
- import { homedir as homedir7 } from "node:os";
29133
+ import { join as join11, dirname as dirname8 } from "node:path";
29134
+ import { homedir as homedir8 } from "node:os";
28972
29135
 
28973
29136
  // src/agents/lifecycle.ts
28974
29137
  import { execFileSync, spawn, spawnSync } from "node:child_process";
@@ -29070,7 +29233,7 @@ function getAllAgentStatuses(config) {
29070
29233
 
29071
29234
  // src/cli/update.ts
29072
29235
  init_atomic();
29073
- var DEFAULT_COMPOSE_PATH = join10(homedir7(), ".switchroom", "compose", "docker-compose.yml");
29236
+ var DEFAULT_COMPOSE_PATH = join11(homedir8(), ".switchroom", "compose", "docker-compose.yml");
29074
29237
  var PACKAGED_INSTALL_KINDS = new Set([
29075
29238
  "static-binary",
29076
29239
  "npm-global",
@@ -29161,9 +29324,9 @@ function resolvePriorPinFromFleet(components, target, scope) {
29161
29324
  // src/host-control/config-edit-validator.ts
29162
29325
  init_schema();
29163
29326
  var import_yaml5 = __toESM(require_dist(), 1);
29164
- import { mkdtempSync, writeFileSync as writeFileSync5, rmSync as rmSync2, existsSync as existsSync15, readFileSync as readFileSync11 } from "node:fs";
29327
+ import { mkdtempSync, writeFileSync as writeFileSync6, rmSync as rmSync2, existsSync as existsSync16, readFileSync as readFileSync12 } from "node:fs";
29165
29328
  import { tmpdir } from "node:os";
29166
- import { join as join11, isAbsolute as isAbsolute2, normalize, basename as basename4 } from "node:path";
29329
+ import { join as join12, isAbsolute as isAbsolute2, normalize, basename as basename4 } from "node:path";
29167
29330
  import { spawnSync as spawnSync3 } from "node:child_process";
29168
29331
  import { isDeepStrictEqual } from "node:util";
29169
29332
  var MAX_PATCH_BYTES = 1024 * 1024;
@@ -29303,22 +29466,22 @@ function validateShape(unifiedDiff, targetPath) {
29303
29466
  return null;
29304
29467
  }
29305
29468
  function applyPatch(unifiedDiff, configPath, gitBin) {
29306
- if (!existsSync15(configPath)) {
29469
+ if (!existsSync16(configPath)) {
29307
29470
  return {
29308
29471
  ok: false,
29309
29472
  code: "E_PATCH_APPLY_FAILED",
29310
29473
  detail: `target config not found at ${configPath}`
29311
29474
  };
29312
29475
  }
29313
- const liveContent = readFileSync11(configPath, "utf8");
29314
- const scratchDir = mkdtempSync(join11(tmpdir(), "config-propose-edit-"));
29476
+ const liveContent = readFileSync12(configPath, "utf8");
29477
+ const scratchDir = mkdtempSync(join12(tmpdir(), "config-propose-edit-"));
29315
29478
  try {
29316
29479
  const targetBasename = configPath.split("/").pop() ?? "switchroom.yaml";
29317
- const scratchFile = join11(scratchDir, targetBasename);
29318
- writeFileSync5(scratchFile, liveContent);
29480
+ const scratchFile = join12(scratchDir, targetBasename);
29481
+ writeFileSync6(scratchFile, liveContent);
29319
29482
  const normalizedDiff = normalizeDiffHeadersToBasename(unifiedDiff, targetBasename);
29320
- const patchFile = join11(scratchDir, "proposal.patch");
29321
- writeFileSync5(patchFile, normalizedDiff);
29483
+ const patchFile = join12(scratchDir, "proposal.patch");
29484
+ writeFileSync6(patchFile, normalizedDiff);
29322
29485
  const bin = gitBin ?? "git";
29323
29486
  const baseArgs = [
29324
29487
  "apply",
@@ -29347,7 +29510,7 @@ function applyPatch(unifiedDiff, configPath, gitBin) {
29347
29510
  detail: `git apply failed: ${(real.stderr || "").trim().slice(0, 500)}`
29348
29511
  };
29349
29512
  }
29350
- const after = readFileSync11(scratchFile, "utf8");
29513
+ const after = readFileSync12(scratchFile, "utf8");
29351
29514
  return { ok: true, after };
29352
29515
  } finally {
29353
29516
  rmSync2(scratchDir, { recursive: true, force: true });
@@ -29545,7 +29708,7 @@ function validateConfigEdit(opts) {
29545
29708
  return schemaErr;
29546
29709
  let beforeData = {};
29547
29710
  try {
29548
- const beforeRaw = existsSync15(opts.configPath) ? readFileSync11(opts.configPath, "utf8") : "";
29711
+ const beforeRaw = existsSync16(opts.configPath) ? readFileSync12(opts.configPath, "utf8") : "";
29549
29712
  const beforeDoc = import_yaml5.parseDocument(beforeRaw, { merge: false, strict: false });
29550
29713
  beforeData = beforeDoc.toJS();
29551
29714
  } catch {
@@ -29918,33 +30081,30 @@ function etaLine(s) {
29918
30081
  const meanMs = done.reduce((t, a) => t + (a.durationMs ?? 0), 0) / done.length;
29919
30082
  return `~${formatDurationMs(meanMs * remaining)} left (rough est.)`;
29920
30083
  }
29921
- function deferredLines(target, fromVersion) {
29922
- const bare = target.trim().replace(/^v/, "");
30084
+ function deferredLines(target, fromVersion, hostCli) {
29923
30085
  const head = [
29924
- `**Verified on ${target}** — every component this roll owned passed \`verify-components\`, so this is host convergence, not just the agents. Anything the roll was told to skip is named in its warnings.`,
29925
- `**Still host-side (nothing in a roll can do these):**`
30086
+ `**Verified on ${target}** — every component this roll owned passed \`verify-components\`, so this is host convergence, not just the agents. Anything the roll was told to skip is named in its warnings.`
29926
30087
  ];
30088
+ const posture = compareHostCliToTarget(hostCli, target);
30089
+ const hostCliOutstanding = posture !== "current-or-ahead";
30090
+ if (posture === "current-or-ahead" && hostCli) {
30091
+ head.push(`Host operator CLI: **on ${hostCli.version}** (read from \`~/.switchroom/host-cli.json\`) — nothing to do.`);
30092
+ }
30093
+ const cliCommand = hostCliInstallCommand(hostCli, target);
30094
+ const cliLine = !hostCli ? `- host operator CLI (version not observable from here — no \`~/.switchroom/host-cli.json\`) — ${cliCommand}` : posture === "behind" ? `- host operator CLI — observed **${hostCli.version}**, run: \`${cliCommand}\`` : `- host operator CLI — observed **${hostCli.version}**, which is not comparable to ${target} (a channel/pre-release build). Confirm host-side with \`switchroom --version\`; if it is behind: ${cliCommand}`;
29927
30095
  const verdict = hostdTemplateRegenVerdict(target, fromVersion);
29928
- if (verdict === "required") {
30096
+ const collapsible = verdict === "required" && posture === "behind" ? hostCliInstallShellCommand(hostCli, target) : undefined;
30097
+ if (collapsible) {
29929
30098
  return [
29930
30099
  ...head,
30100
+ `**Still host-side (nothing in a roll can do these):**`,
29931
30101
  `- host operator CLI + hostd template regen — regen is **REQUIRED** for this roll (hostd mounts/env changed in ${HOSTD_TEMPLATE_LAST_CHANGED}). One copy-paste:`,
29932
- ` \`sudo npm i -g switchroom@${bare} && switchroom hostd install --tag ${target}\``
30102
+ ` \`${collapsible} && switchroom hostd install --tag ${target}\``
29933
30103
  ];
29934
30104
  }
29935
- const cliLine = `- host operator CLI — \`sudo npm i -g switchroom@${bare}\``;
29936
- if (verdict === "not-needed") {
29937
- return [
29938
- ...head,
29939
- cliLine,
29940
- `- hostd template regen: **not needed** for this release — hostd mounts/env unchanged since ${HOSTD_TEMPLATE_LAST_CHANGED}.`
29941
- ];
29942
- }
29943
- return [
29944
- ...head,
29945
- cliLine,
29946
- `- hostd template regen (only if the release changed hostd mounts/env) — \`switchroom hostd install --tag ${target}\``
29947
- ];
30105
+ const regenLine = verdict === "required" ? `- hostd template regen**REQUIRED** for this roll (hostd mounts/env changed in ${HOSTD_TEMPLATE_LAST_CHANGED}): \`switchroom hostd install --tag ${target}\`` : verdict === "not-needed" ? `- hostd template regen: **not needed** for this release — hostd mounts/env unchanged since ${HOSTD_TEMPLATE_LAST_CHANGED}.` : `- hostd template regen (only if the release changed hostd mounts/env) — \`switchroom hostd install --tag ${target}\``;
30106
+ const outstanding = [...hostCliOutstanding ? [cliLine] : [], regenLine];
30107
+ return [...head, `**Still host-side (nothing in a roll can do these):**`, ...outstanding];
29948
30108
  }
29949
30109
  function warningLines(warnings) {
29950
30110
  if (!warnings || warnings.length === 0)
@@ -29986,7 +30146,7 @@ function renderWith(s, compact) {
29986
30146
  if (warnings.length > 0)
29987
30147
  parts2.push("", ...warnings);
29988
30148
  if (s.deferred !== false)
29989
- parts2.push("", ...deferredLines(s.target, s.fromVersion));
30149
+ parts2.push("", ...deferredLines(s.target, s.fromVersion, s.hostCli));
29990
30150
  return parts2.join(`
29991
30151
  `);
29992
30152
  }
@@ -30148,11 +30308,11 @@ function resolveDigests(imageRefs) {
30148
30308
  return out;
30149
30309
  }
30150
30310
  function readCachedInstallType(bindRoot) {
30151
- const cacheDir = join12(bindRoot, ".switchroom");
30152
- const cachePath = join12(cacheDir, "install-type.json");
30153
- if (existsSync16(cachePath)) {
30311
+ const cacheDir = join13(bindRoot, ".switchroom");
30312
+ const cachePath = join13(cacheDir, "install-type.json");
30313
+ if (existsSync17(cachePath)) {
30154
30314
  try {
30155
- const raw = readFileSync12(cachePath, "utf-8");
30315
+ const raw = readFileSync13(cachePath, "utf-8");
30156
30316
  const parsed = JSON.parse(raw);
30157
30317
  if (parsed && typeof parsed.install_type === "string" && typeof parsed.detected_at === "string") {
30158
30318
  return parsed;
@@ -30169,10 +30329,10 @@ function readCachedInstallType(bindRoot) {
30169
30329
  source_paths: ctx.source_paths
30170
30330
  };
30171
30331
  try {
30172
- mkdirSync5(cacheDir, { recursive: true });
30332
+ mkdirSync6(cacheDir, { recursive: true });
30173
30333
  const tmp = `${cachePath}.tmp`;
30174
- writeFileSync6(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
30175
- renameSync5(tmp, cachePath);
30334
+ writeFileSync7(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
30335
+ renameSync6(tmp, cachePath);
30176
30336
  } catch {}
30177
30337
  return payload;
30178
30338
  }
@@ -30204,7 +30364,7 @@ function writeFileInPlacePreservingInode(targetPath, content) {
30204
30364
  } finally {
30205
30365
  closeSync5(fd);
30206
30366
  }
30207
- const readBack = readFileSync12(targetPath);
30367
+ const readBack = readFileSync13(targetPath);
30208
30368
  if (readBack.length !== buf.length) {
30209
30369
  throw new Error(`in-place write short: wrote ${buf.length} bytes but read back ${readBack.length}`);
30210
30370
  }
@@ -30239,11 +30399,11 @@ class HostdServer {
30239
30399
  fleetMutationInFlight = null;
30240
30400
  lastAutoRolloutFailedPin = null;
30241
30401
  autoRolloutLatchPath() {
30242
- return join12(this.hostdDirPath(), AUTO_ROLLOUT_LATCH_FILENAME);
30402
+ return join13(this.hostdDirPath(), AUTO_ROLLOUT_LATCH_FILENAME);
30243
30403
  }
30244
30404
  readAutoRolloutLatch() {
30245
30405
  try {
30246
- const raw = readFileSync12(this.autoRolloutLatchPath(), "utf-8");
30406
+ const raw = readFileSync13(this.autoRolloutLatchPath(), "utf-8");
30247
30407
  const parsed = JSON.parse(raw);
30248
30408
  if (parsed && parsed.v === 1 && typeof parsed.pin === "string" && parsed.pin.length > 0 && (parsed.outcome === "attempting" || parsed.outcome === "failed")) {
30249
30409
  return parsed;
@@ -30258,8 +30418,8 @@ class HostdServer {
30258
30418
  }
30259
30419
  writeAutoRolloutLatch(latch) {
30260
30420
  try {
30261
- mkdirSync5(this.hostdDirPath(), { recursive: true });
30262
- writeFileSync6(this.autoRolloutLatchPath(), JSON.stringify(latch, null, 2), {
30421
+ mkdirSync6(this.hostdDirPath(), { recursive: true });
30422
+ writeFileSync7(this.autoRolloutLatchPath(), JSON.stringify(latch, null, 2), {
30263
30423
  mode: 384
30264
30424
  });
30265
30425
  } catch (e) {
@@ -30282,7 +30442,7 @@ class HostdServer {
30282
30442
  return resolveHostdConfigPath(this.opts.configPath);
30283
30443
  }
30284
30444
  fleetComposePath() {
30285
- return join12(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
30445
+ return join13(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
30286
30446
  }
30287
30447
  recoverRolloutPinJournal(context) {
30288
30448
  let note = null;
@@ -30321,7 +30481,7 @@ class HostdServer {
30321
30481
  return note;
30322
30482
  }
30323
30483
  async start() {
30324
- const hostdDir = join12(this.opts.homeDir, ".switchroom", "hostd");
30484
+ const hostdDir = join13(this.opts.homeDir, ".switchroom", "hostd");
30325
30485
  await mkdir2(hostdDir, { recursive: true });
30326
30486
  await chmod(hostdDir, 493).catch(() => {
30327
30487
  return;
@@ -30342,13 +30502,13 @@ class HostdServer {
30342
30502
  }
30343
30503
  try {
30344
30504
  for (const name of agentNames) {
30345
- const dir = join12(hostdDir, name);
30346
- const sockPath = join12(dir, "sock");
30505
+ const dir = join13(hostdDir, name);
30506
+ const sockPath = join13(dir, "sock");
30347
30507
  await mkdir2(dir, { recursive: true });
30348
30508
  await chmod(dir, 493).catch(() => {
30349
30509
  return;
30350
30510
  });
30351
- if (existsSync16(sockPath))
30511
+ if (existsSync17(sockPath))
30352
30512
  await unlink(sockPath).catch(() => {
30353
30513
  return;
30354
30514
  });
@@ -30379,13 +30539,13 @@ class HostdServer {
30379
30539
  process.stderr.write(`hostd: SWITCHROOM_HOSTD_OPERATOR_UID='${opUidStr}' is not a positive integer; skipping operator listener
30380
30540
  `);
30381
30541
  } else {
30382
- const dir = join12(hostdDir, "operator");
30383
- const sockPath = join12(dir, "sock");
30542
+ const dir = join13(hostdDir, "operator");
30543
+ const sockPath = join13(dir, "sock");
30384
30544
  await mkdir2(dir, { recursive: true });
30385
30545
  await chmod(dir, 493).catch(() => {
30386
30546
  return;
30387
30547
  });
30388
- if (existsSync16(sockPath))
30548
+ if (existsSync17(sockPath))
30389
30549
  await unlink(sockPath).catch(() => {
30390
30550
  return;
30391
30551
  });
@@ -30421,11 +30581,11 @@ class HostdServer {
30421
30581
  }
30422
30582
  async reconcileOrphanedFleetMutations() {
30423
30583
  const path3 = this.auditLogPath();
30424
- if (!existsSync16(path3))
30584
+ if (!existsSync17(path3))
30425
30585
  return;
30426
30586
  let raw;
30427
30587
  try {
30428
- raw = readFileSync12(path3, "utf-8");
30588
+ raw = readFileSync13(path3, "utf-8");
30429
30589
  } catch {
30430
30590
  return;
30431
30591
  }
@@ -30824,11 +30984,11 @@ class HostdServer {
30824
30984
  missingApplyAssets() {
30825
30985
  const root = this.opts.applyAssetsRoot ?? resolve9(import.meta.dirname, "../..");
30826
30986
  return [
30827
- join12(root, "profiles"),
30828
- join12(root, "profiles", "default"),
30829
- join12(root, "vendor", "hindsight-memory"),
30830
- join12(root, "skills")
30831
- ].filter((p) => !existsSync16(p));
30987
+ join13(root, "profiles"),
30988
+ join13(root, "profiles", "default"),
30989
+ join13(root, "vendor", "hindsight-memory"),
30990
+ join13(root, "skills")
30991
+ ].filter((p) => !existsSync17(p));
30832
30992
  }
30833
30993
  applyAssetPreflight(request_id, started) {
30834
30994
  const missing = this.missingApplyAssets();
@@ -31086,19 +31246,19 @@ class HostdServer {
31086
31246
  return entry;
31087
31247
  }
31088
31248
  hostdDirPath() {
31089
- return join12(this.opts.homeDir, ".switchroom", "hostd");
31249
+ return join13(this.opts.homeDir, ".switchroom", "hostd");
31090
31250
  }
31091
31251
  hostdDirHostPath() {
31092
31252
  const hostHome = this.opts.hostHomeDir ?? process.env.SWITCHROOM_HOST_HOME?.trim() ?? this.opts.homeDir;
31093
- return join12(hostHome, ".switchroom", "hostd");
31253
+ return join13(hostHome, ".switchroom", "hostd");
31094
31254
  }
31095
31255
  async beginSelfBump(req, caller, started) {
31096
31256
  const ownVersion = this.opts.selfVersion ?? SWITCHROOM_VERSION;
31097
- const composePath = join12(this.hostdDirPath(), "docker-compose.yml");
31098
- if (!existsSync16(composePath)) {
31257
+ const composePath = join13(this.hostdDirPath(), "docker-compose.yml");
31258
+ if (!existsSync17(composePath)) {
31099
31259
  return deniedResponse(req.request_id, `rollout: hostd's CLI (v${ownVersion}) is older than the target ` + `${req.args.pin} and needs a self-bump first, but its compose file ` + `is missing at ${composePath}. Run \`switchroom hostd install ` + `--tag ${req.args.pin}\` on the host, then re-run the roll. ` + `Nothing was changed.`, Date.now() - started);
31100
31260
  }
31101
- const before = readFileSync12(composePath, "utf8");
31261
+ const before = readFileSync13(composePath, "utf8");
31102
31262
  const bumped = bumpHostdComposeImageTag(before, req.args.pin);
31103
31263
  if (!bumped) {
31104
31264
  return deniedResponse(req.request_id, `rollout: hostd needs a self-bump to ${req.args.pin} but no ` + `switchroom-hostd image line was found in ${composePath} ` + `(hand-edited compose?). Run \`switchroom hostd install --tag ` + `${req.args.pin}\` on the host, then re-run the roll. Nothing ` + `was changed.`, Date.now() - started);
@@ -31119,10 +31279,10 @@ class HostdServer {
31119
31279
  try {
31120
31280
  const baks = readdirSync6(this.hostdDirPath()).filter((f) => f.startsWith("docker-compose.yml.bak-selfbump-")).sort();
31121
31281
  for (const f of baks.slice(0, Math.max(0, baks.length - 4))) {
31122
- unlinkSync5(join12(this.hostdDirPath(), f));
31282
+ unlinkSync5(join13(this.hostdDirPath(), f));
31123
31283
  }
31124
31284
  } catch {}
31125
- const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
31285
+ const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
31126
31286
  const marker = {
31127
31287
  v: 1,
31128
31288
  request_id: req.request_id,
@@ -31135,10 +31295,10 @@ class HostdServer {
31135
31295
  prior_hostd_version: ownVersion
31136
31296
  };
31137
31297
  copyFileSync3(composePath, bakPath);
31138
- writeFileSync6(composePath, bumped.yaml, "utf8");
31298
+ writeFileSync7(composePath, bumped.yaml, "utf8");
31139
31299
  process.stderr.write(`hostd: self-bump rewrote ${bumped.bumpedCount} switchroom-hostd ` + `image line(s) → ${bumped.newImageRef}
31140
31300
  `);
31141
- writeFileSync6(markerPath, encodePendingRolloutMarker(marker), {
31301
+ writeFileSync7(markerPath, encodePendingRolloutMarker(marker), {
31142
31302
  mode: 384
31143
31303
  });
31144
31304
  const entry = {
@@ -31206,7 +31366,7 @@ class HostdServer {
31206
31366
  rollbackBump();
31207
31367
  entry.result = "error";
31208
31368
  entry.finished_at = Date.now();
31209
- entry.error = `self-bump helper failed (container exit ${Number.isFinite(code) ? code : "unknown"}). ` + `See ${join12(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)} on the host. ` + `Compose bump rolled back. Fallback: \`switchroom hostd install ` + `--tag ${req.args.pin}\` host-side, then re-run the roll.`;
31369
+ entry.error = `self-bump helper failed (container exit ${Number.isFinite(code) ? code : "unknown"}). ` + `See ${join13(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)} on the host. ` + `Compose bump rolled back. Fallback: \`switchroom hostd install ` + `--tag ${req.args.pin}\` host-side, then re-run the roll.`;
31210
31370
  if (this.fleetMutationInFlight && this.fleetMutationInFlight.request_id === entry.request_id) {
31211
31371
  this.fleetMutationInFlight = null;
31212
31372
  }
@@ -31233,10 +31393,10 @@ class HostdServer {
31233
31393
  try {
31234
31394
  if (!Number.isInteger(messageId) || messageId <= 0)
31235
31395
  return;
31236
- const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
31237
- if (!existsSync16(markerPath))
31396
+ const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
31397
+ if (!existsSync17(markerPath))
31238
31398
  return;
31239
- const marker = parsePendingRolloutMarker(readFileSync12(markerPath, "utf8"));
31399
+ const marker = parsePendingRolloutMarker(readFileSync13(markerPath, "utf8"));
31240
31400
  if (!marker)
31241
31401
  return;
31242
31402
  if (marker.request_id !== requestId)
@@ -31246,17 +31406,17 @@ class HostdServer {
31246
31406
  narration_message_id: messageId
31247
31407
  };
31248
31408
  const tmp = `${markerPath}.tmp`;
31249
- writeFileSync6(tmp, encodePendingRolloutMarker(updated), { mode: 384 });
31250
- renameSync5(tmp, markerPath);
31409
+ writeFileSync7(tmp, encodePendingRolloutMarker(updated), { mode: 384 });
31410
+ renameSync6(tmp, markerPath);
31251
31411
  } catch {}
31252
31412
  }
31253
31413
  async resumePendingSelfBumpRollout() {
31254
- const markerPath = join12(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
31255
- if (!existsSync16(markerPath))
31414
+ const markerPath = join13(this.hostdDirPath(), SELF_BUMP_MARKER_FILENAME);
31415
+ if (!existsSync17(markerPath))
31256
31416
  return;
31257
31417
  let raw;
31258
31418
  try {
31259
- raw = readFileSync12(markerPath, "utf8");
31419
+ raw = readFileSync13(markerPath, "utf8");
31260
31420
  } finally {
31261
31421
  try {
31262
31422
  unlinkSync5(markerPath);
@@ -31307,7 +31467,7 @@ class HostdServer {
31307
31467
  return;
31308
31468
  }
31309
31469
  if (needsSelfBump(ownVersion, marker.pin)) {
31310
- await failResume(`hostd is still on v${ownVersion} after the self-bump to ` + `${marker.pin} (helper failed? see ` + `${join12(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)}). ` + `Fallback: \`switchroom hostd install --tag ${marker.pin}\` ` + `host-side, then re-issue the rollout.`);
31470
+ await failResume(`hostd is still on v${ownVersion} after the self-bump to ` + `${marker.pin} (helper failed? see ` + `${join13(this.hostdDirHostPath(), SELF_BUMP_LOG_FILENAME)}). ` + `Fallback: \`switchroom hostd install --tag ${marker.pin}\` ` + `host-side, then re-issue the rollout.`);
31311
31471
  return;
31312
31472
  }
31313
31473
  process.stderr.write(`hostd: resuming rollout ${marker.request_id} → ${marker.pin} after ` + `self-bump (v${marker.prior_hostd_version} → v${ownVersion})
@@ -31415,7 +31575,7 @@ class HostdServer {
31415
31575
  }
31416
31576
  let beforeContent;
31417
31577
  try {
31418
- beforeContent = readFileSync12(configPath, "utf-8");
31578
+ beforeContent = readFileSync13(configPath, "utf-8");
31419
31579
  } catch {
31420
31580
  beforeContent = "";
31421
31581
  }
@@ -31535,7 +31695,7 @@ class HostdServer {
31535
31695
  try {
31536
31696
  let snapshot;
31537
31697
  try {
31538
- snapshot = readFileSync12(configPath, "utf-8");
31698
+ snapshot = readFileSync13(configPath, "utf-8");
31539
31699
  } catch (e) {
31540
31700
  await approval.finalize({
31541
31701
  outcome: "reconcile_failed_rolled_back",
@@ -31833,8 +31993,8 @@ ${output.recovery.stderr}` : "";
31833
31993
  if (this.opts.imageRefsForDigests)
31834
31994
  return this.opts.imageRefsForDigests();
31835
31995
  try {
31836
- const composePath = join12(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
31837
- if (!existsSync16(composePath))
31996
+ const composePath = join13(this.opts.bindRoot ?? this.opts.homeDir, ".switchroom", "compose", "docker-compose.yml");
31997
+ if (!existsSync17(composePath))
31838
31998
  return [];
31839
31999
  const r = spawnSync4("docker", [
31840
32000
  "compose",
@@ -32054,7 +32214,11 @@ ${pinNote}`.trim();
32054
32214
  ...entry.failed_agent ? { failedAgent: entry.failed_agent } : {},
32055
32215
  ...entry.got !== undefined ? { got: entry.got } : {},
32056
32216
  ...entry.drifted && entry.drifted.length > 0 ? { drifted: entry.drifted } : {},
32057
- ...entry.warnings && entry.warnings.length > 0 ? { warnings: entry.warnings } : {}
32217
+ ...entry.warnings && entry.warnings.length > 0 ? { warnings: entry.warnings } : {},
32218
+ ...(() => {
32219
+ const stamp = readHostCliStamp();
32220
+ return stamp ? { hostCli: stamp } : {};
32221
+ })()
32058
32222
  });
32059
32223
  this.opts.rolloutRelay.postTerminal({
32060
32224
  requestId: entry.request_id,
@@ -32092,10 +32256,10 @@ ${pinNote}`.trim();
32092
32256
  }
32093
32257
  rolloutRowExistsInLog(targetRequestId) {
32094
32258
  const path3 = this.auditLogPath();
32095
- if (!existsSync16(path3))
32259
+ if (!existsSync17(path3))
32096
32260
  return false;
32097
32261
  try {
32098
- const raw = readFileSync12(path3, "utf-8");
32262
+ const raw = readFileSync13(path3, "utf-8");
32099
32263
  return latestRolloutRowForRequest(raw, targetRequestId) !== null;
32100
32264
  } catch {
32101
32265
  return false;
@@ -32103,11 +32267,11 @@ ${pinNote}`.trim();
32103
32267
  }
32104
32268
  rolloutStatusFromLog(responseRequestId, targetRequestId, started) {
32105
32269
  const path3 = this.auditLogPath();
32106
- if (!existsSync16(path3))
32270
+ if (!existsSync17(path3))
32107
32271
  return null;
32108
32272
  let raw;
32109
32273
  try {
32110
- raw = readFileSync12(path3, "utf-8");
32274
+ raw = readFileSync13(path3, "utf-8");
32111
32275
  } catch {
32112
32276
  return null;
32113
32277
  }
@@ -32189,7 +32353,7 @@ ${pinNote}`.trim();
32189
32353
  }
32190
32354
  }
32191
32355
  auditLogPath() {
32192
- return this.opts.auditLogPath ?? join12(this.opts.homeDir, ".switchroom", "host-control-audit.log");
32356
+ return this.opts.auditLogPath ?? join13(this.opts.homeDir, ".switchroom", "host-control-audit.log");
32193
32357
  }
32194
32358
  maybeRotateAuditLog(path3) {
32195
32359
  const { maxBytes, maxFiles } = resolveRotationConfig({
@@ -33673,7 +33837,7 @@ function makeRestart(switchroomBin) {
33673
33837
  }
33674
33838
 
33675
33839
  // src/host-control/update-notifier.ts
33676
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync13, renameSync as renameSync6, writeFileSync as writeFileSync7 } from "node:fs";
33840
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync14, renameSync as renameSync7, writeFileSync as writeFileSync8 } from "node:fs";
33677
33841
  import { randomBytes as randomBytes3 } from "node:crypto";
33678
33842
  import { dirname as dirname10 } from "node:path";
33679
33843
  var FINALIZE_DETAIL_MAX = 450;
@@ -33790,7 +33954,7 @@ class UpdateNotifier {
33790
33954
  }
33791
33955
  readState() {
33792
33956
  try {
33793
- const raw = readFileSync13(this.opts.statePath, "utf8");
33957
+ const raw = readFileSync14(this.opts.statePath, "utf8");
33794
33958
  const parsed = JSON.parse(raw);
33795
33959
  if (parsed === null || typeof parsed !== "object")
33796
33960
  return null;
@@ -33810,11 +33974,11 @@ class UpdateNotifier {
33810
33974
  }
33811
33975
  writeState(state) {
33812
33976
  try {
33813
- mkdirSync6(dirname10(this.opts.statePath), { recursive: true });
33977
+ mkdirSync7(dirname10(this.opts.statePath), { recursive: true });
33814
33978
  const tmp = `${this.opts.statePath}.tmp`;
33815
- writeFileSync7(tmp, JSON.stringify(state) + `
33979
+ writeFileSync8(tmp, JSON.stringify(state) + `
33816
33980
  `, "utf8");
33817
- renameSync6(tmp, this.opts.statePath);
33981
+ renameSync7(tmp, this.opts.statePath);
33818
33982
  } catch (err2) {
33819
33983
  this.log(`failed to persist notify state: ${errMsg2(err2)}`);
33820
33984
  }
@@ -33919,7 +34083,7 @@ import { appendFile as appendFile2 } from "node:fs/promises";
33919
34083
  async function loadConfigResilient() {
33920
34084
  try {
33921
34085
  const config = loadConfig();
33922
- clearStaleDegradedMarker(homedir8(), (m) => process.stderr.write(`hostd: ${m}
34086
+ clearStaleDegradedMarker(homedir9(), (m) => process.stderr.write(`hostd: ${m}
33923
34087
  `));
33924
34088
  return config;
33925
34089
  } catch (err2) {
@@ -33931,13 +34095,13 @@ async function loadConfigResilient() {
33931
34095
  } catch {
33932
34096
  configPath = undefined;
33933
34097
  }
33934
- const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join13(homedir8(), ".switchroom", "agents");
34098
+ const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join14(homedir9(), ".switchroom", "agents");
33935
34099
  const listGatewayCandidates = () => {
33936
34100
  const out = [];
33937
34101
  try {
33938
34102
  for (const name of readdirSync7(agentsDir).sort()) {
33939
34103
  const sock = resolve10(agentsDir, name, "telegram", "gateway.sock");
33940
- if (existsSync17(sock))
34104
+ if (existsSync18(sock))
33941
34105
  out.push({ agent: name, sock });
33942
34106
  }
33943
34107
  } catch {}
@@ -33960,7 +34124,7 @@ async function loadConfigResilient() {
33960
34124
  };
33961
34125
  return waitForConfigRecovery({
33962
34126
  initialError: err2,
33963
- homeDir: homedir8(),
34127
+ homeDir: homedir9(),
33964
34128
  loadFn: () => loadConfig(),
33965
34129
  configPath,
33966
34130
  notify,
@@ -33984,10 +34148,10 @@ async function main() {
33984
34148
  `);
33985
34149
  process.exit(2);
33986
34150
  }
33987
- const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join13(homedir8(), ".switchroom", "agents");
34151
+ const agentsDir = process.env.SWITCHROOM_AGENTS_DIR ?? join14(homedir9(), ".switchroom", "agents");
33988
34152
  const resolveGatewaySocket = (agentName) => {
33989
34153
  const sock = resolve10(agentsDir, agentName, "telegram", "gateway.sock");
33990
- return existsSync17(sock) ? sock : null;
34154
+ return existsSync18(sock) ? sock : null;
33991
34155
  };
33992
34156
  const approvalGateway = new SocketApprovalGateway({
33993
34157
  resolveGatewaySocket,
@@ -34011,7 +34175,7 @@ async function main() {
34011
34175
  `)
34012
34176
  });
34013
34177
  const server = new HostdServer({
34014
- homeDir: homedir8(),
34178
+ homeDir: homedir9(),
34015
34179
  agentUids,
34016
34180
  fleetComponents: () => dockerFleetComponents(),
34017
34181
  config: {
@@ -34041,7 +34205,7 @@ async function main() {
34041
34205
  const autoRel = config.host_control?.auto_release_check;
34042
34206
  const watcherMode = resolveReleaseWatcherMode(config);
34043
34207
  if (watcherMode.mode !== "off") {
34044
- const eventsLog = join13(homedir8(), ".switchroom", "release-watcher-events.jsonl");
34208
+ const eventsLog = join14(homedir9(), ".switchroom", "release-watcher-events.jsonl");
34045
34209
  const log = (m) => {
34046
34210
  process.stderr.write(`hostd: ${m}
34047
34211
  `);
@@ -34100,7 +34264,7 @@ async function main() {
34100
34264
  `);
34101
34265
  } else
34102
34266
  notifier = new UpdateNotifier({
34103
- statePath: join13(homedir8(), ".switchroom", "release-notify-state.json"),
34267
+ statePath: join14(homedir9(), ".switchroom", "release-notify-state.json"),
34104
34268
  repostSuppressMs: cardTimeoutMs,
34105
34269
  isFleetMutationInFlight: () => server.isFleetMutationLocked(),
34106
34270
  startApply: (requestId) => {