switchroom 0.13.32 → 0.13.35

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.
@@ -13520,7 +13520,7 @@ var init_zod = __esm(() => {
13520
13520
  });
13521
13521
 
13522
13522
  // src/config/schema.ts
13523
- var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, HostControlConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
13523
+ var CodeRepoEntrySchema, AgentBindMountSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, ReactionsSchema, ReleaseBlock, NetworkIsolationSchema, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, DEFAULT_PROFILE = "default", AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, HostdConfigSchema, SwitchroomConfigSchema;
13524
13524
  var init_schema = __esm(() => {
13525
13525
  init_zod();
13526
13526
  CodeRepoEntrySchema = exports_external.object({
@@ -13866,8 +13866,15 @@ var init_schema = __esm(() => {
13866
13866
  weekly_budget_usd: exports_external.number().positive().optional().describe("Weekly USD spend budget. If unset, the greeting shows raw usage only."),
13867
13867
  monthly_budget_usd: exports_external.number().positive().optional().describe("Monthly USD spend budget. If unset, the greeting shows raw usage only.")
13868
13868
  });
13869
+ AutoReleaseCheckSchema = exports_external.object({
13870
+ enabled: exports_external.boolean().default(false).describe("When true, hostd polls the remote release tag every " + "`interval_minutes` and applies + restarts the fleet when a new " + "release is detected. Default false \u2014 opt-in."),
13871
+ interval_minutes: exports_external.number().int().min(5).max(1440).default(5).describe("Poll interval in minutes. Floor of 5m matches the agent-config " + "cron rate limit; ceiling of 1440m (24h) is a sanity cap."),
13872
+ apply_on_detect: exports_external.boolean().default(true).describe("When false, hostd logs `release_detected` but does NOT call " + "update_apply / restart all. Useful for dogfooding the detector " + "without rolling the fleet."),
13873
+ image_ref: exports_external.string().default("ghcr.io/switchroom/switchroom-agent:latest").describe("Image reference whose remote digest is compared to the local " + "image digest. Defaults to the agent image's :latest tag, which " + "is the canonical signal that a release has been promoted.")
13874
+ });
13869
13875
  HostControlConfigSchema = exports_external.object({
13870
- enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip \u2014 the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` \u2014 " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C \u00a75.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3).")
13876
+ enabled: exports_external.boolean().default(true).describe("Whether the host-control daemon is in use. Default: true (since " + "RFC C Phase 2 default-flip \u2014 the gateway's /restart, /new, /reset, " + "and /update apply slash-commands all dispatch through hostd, and " + "without it those verbs fail on docker-mode installs because the " + "agent container has no docker binary/socket). " + "When true, the compose generator emits per-agent bind mounts " + "at `~/.switchroom/hostd/<name>/sock` for every admin-flagged " + "agent. Install the daemon with `switchroom hostd install` \u2014 " + "it runs as a docker container in its own compose project " + "(`switchroom-hostd`), separate from the agent fleet's compose " + "project so `up -d --remove-orphans` cycles of the fleet " + "can't recreate the daemon mid-RPC. See RFC C \u00a75.1. " + "Set enabled: false only on legacy systemd-mode installs that " + "still rely on the in-container `spawnSwitchroomDetached` " + "shellout (removal is tracked as RFC C Phase 3)."),
13877
+ auto_release_check: AutoReleaseCheckSchema.default({}).describe("Pull-based release-triggered fleet restart (#1743). hostd polls " + "the remote release tag on a fixed interval and applies + " + "restarts the fleet (graceful) when a new release is detected. " + "Opt-in: default enabled=false.")
13871
13878
  });
13872
13879
  HostdConfigSchema = exports_external.object({
13873
13880
  config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit \u00a73). Default false \u2014 the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true (and once PR 1c lands the apply path), " + "admin agents can propose unified-diff patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
@@ -29101,7 +29108,7 @@ function decodeResponse3(line) {
29101
29108
  const obj = JSON.parse(line);
29102
29109
  return ResponseSchema3.parse(obj);
29103
29110
  }
29104
- var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema2, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ResponseEnvelope, ResponseSchema3;
29111
+ var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema2, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ErrorFixSchema, ErrorEnvelopeSchema, ResponseEnvelope, ResponseSchema3;
29105
29112
  var init_protocol3 = __esm(() => {
29106
29113
  init_zod();
29107
29114
  MAX_FRAME_BYTES3 = 64 * 1024;
@@ -29220,6 +29227,45 @@ var init_protocol3 = __esm(() => {
29220
29227
  ConfigProposeEditRequestSchema
29221
29228
  ]);
29222
29229
  ResultSchema = exports_external.enum(["started", "completed", "denied", "error"]);
29230
+ ErrorFixSchema = exports_external.discriminatedUnion("kind", [
29231
+ exports_external.object({
29232
+ kind: exports_external.literal("flip_yaml_flag"),
29233
+ yaml_path: exports_external.string(),
29234
+ to: exports_external.unknown()
29235
+ }),
29236
+ exports_external.object({
29237
+ kind: exports_external.literal("request_vault_grant"),
29238
+ vault_key: exports_external.string()
29239
+ }),
29240
+ exports_external.object({
29241
+ kind: exports_external.literal("operator_action"),
29242
+ subkind: exports_external.enum(["policy_denied", "infra", "out_of_scope"]),
29243
+ operator_steps: exports_external.array(exports_external.string()).min(1).optional()
29244
+ }),
29245
+ exports_external.object({
29246
+ kind: exports_external.literal("retry_after"),
29247
+ retry_at: exports_external.string()
29248
+ }),
29249
+ exports_external.object({
29250
+ kind: exports_external.literal("quota_exceeded"),
29251
+ quota: exports_external.string(),
29252
+ current: exports_external.number(),
29253
+ limit: exports_external.number()
29254
+ }),
29255
+ exports_external.object({
29256
+ kind: exports_external.literal("bad_input"),
29257
+ field: exports_external.string().optional()
29258
+ })
29259
+ ]);
29260
+ ErrorEnvelopeSchema = exports_external.object({
29261
+ v: exports_external.literal(1),
29262
+ code: exports_external.string().regex(/^(E_[A-Z0-9_]+|VAULT-[A-Z-]+)$/),
29263
+ human: exports_external.string().min(1),
29264
+ why: exports_external.string().optional(),
29265
+ fix: ErrorFixSchema.optional(),
29266
+ docs: exports_external.string().url().optional(),
29267
+ request_id: exports_external.string().min(1)
29268
+ });
29223
29269
  ResponseEnvelope = {
29224
29270
  v: exports_external.literal(1),
29225
29271
  request_id: exports_external.string().min(1).max(128),
@@ -29229,7 +29275,8 @@ var init_protocol3 = __esm(() => {
29229
29275
  audit_id: exports_external.string().min(1).optional(),
29230
29276
  stdout_tail: exports_external.string().optional(),
29231
29277
  stderr_tail: exports_external.string().optional(),
29232
- error: exports_external.string().optional()
29278
+ error: exports_external.string().optional(),
29279
+ error_envelope: ErrorEnvelopeSchema.optional()
29233
29280
  };
29234
29281
  ResponseSchema3 = exports_external.object(ResponseEnvelope);
29235
29282
  });
@@ -29400,6 +29447,220 @@ var init_doctor_agent_smoke = __esm(() => {
29400
29447
  init_client4();
29401
29448
  });
29402
29449
 
29450
+ // src/cli/doctor-vault-broker-durability.ts
29451
+ import { execFileSync as execFileSync14 } from "node:child_process";
29452
+ import { existsSync as existsSync50, statSync as statSync22 } from "node:fs";
29453
+ import { homedir as homedir27 } from "node:os";
29454
+ import { join as join46 } from "node:path";
29455
+ function probeBindMountInode(hostPath, brokerContainerPath, opts) {
29456
+ const statHost = opts?.statHost ?? defaultStatHost;
29457
+ const statBroker = opts?.statBroker ?? defaultStatBroker;
29458
+ const host = statHost(hostPath);
29459
+ if (host === null)
29460
+ return { kind: "host-missing", hostPath };
29461
+ const broker = statBroker(brokerContainerPath);
29462
+ if (broker.kind !== "ok-with-stat")
29463
+ return broker;
29464
+ if (String(host.ino) === broker.ino && host.size === broker.size) {
29465
+ return { kind: "ok" };
29466
+ }
29467
+ return {
29468
+ kind: "mismatch",
29469
+ hostInode: String(host.ino),
29470
+ brokerInode: broker.ino,
29471
+ hostSize: host.size,
29472
+ brokerSize: broker.size
29473
+ };
29474
+ }
29475
+ function defaultStatHost(p) {
29476
+ if (!existsSync50(p))
29477
+ return null;
29478
+ try {
29479
+ const s = statSync22(p, { bigint: true });
29480
+ return { ino: s.ino, size: Number(s.size) };
29481
+ } catch {
29482
+ return null;
29483
+ }
29484
+ }
29485
+ function defaultStatBroker(p) {
29486
+ const r = spawnDockerStat(p);
29487
+ if (r.error || r.status === null)
29488
+ return { kind: "broker-unreachable" };
29489
+ if (r.status !== 0) {
29490
+ if (r.status >= 125)
29491
+ return { kind: "broker-unreachable" };
29492
+ return {
29493
+ kind: "broker-stat-failed",
29494
+ msg: r.stderr?.trim() || `exit ${r.status}`
29495
+ };
29496
+ }
29497
+ const out = r.stdout.trim();
29498
+ const [inoStr, sizeStr] = out.split(/\s+/);
29499
+ const size = Number(sizeStr);
29500
+ if (!inoStr || !Number.isFinite(size)) {
29501
+ return {
29502
+ kind: "broker-stat-failed",
29503
+ msg: `unparseable stat output: ${out}`
29504
+ };
29505
+ }
29506
+ return { kind: "ok-with-stat", ino: inoStr, size };
29507
+ }
29508
+ function spawnDockerStat(p) {
29509
+ try {
29510
+ const stdout = execFileSync14("docker", ["exec", "switchroom-vault-broker", "stat", "-c", "%i %s", p], { stdio: ["ignore", "pipe", "pipe"], timeout: 3000, encoding: "utf8" });
29511
+ return { status: 0, stdout, stderr: "", error: null };
29512
+ } catch (err) {
29513
+ const e = err;
29514
+ return {
29515
+ status: typeof e.status === "number" ? e.status : null,
29516
+ stdout: e.stdout ?? "",
29517
+ stderr: e.stderr ?? "",
29518
+ error: e.status === undefined ? new Error(e.message ?? "spawn failed") : null
29519
+ };
29520
+ }
29521
+ }
29522
+ function formatBindMountResult(name, hostPath, brokerContainerPath, result) {
29523
+ if (result.kind === "ok") {
29524
+ return {
29525
+ name,
29526
+ status: "ok",
29527
+ detail: `${hostPath} == ${brokerContainerPath} (same inode)`
29528
+ };
29529
+ }
29530
+ if (result.kind === "host-missing") {
29531
+ return {
29532
+ name,
29533
+ status: "warn",
29534
+ detail: `host file ${hostPath} missing \u2014 pre-created by \`switchroom apply\` on greenfield`,
29535
+ fix: "Run `switchroom apply` to pre-create the host file at the correct mode"
29536
+ };
29537
+ }
29538
+ if (result.kind === "broker-unreachable") {
29539
+ return {
29540
+ name,
29541
+ status: "skip",
29542
+ detail: "vault-broker container unreachable \u2014 bind mount unverified"
29543
+ };
29544
+ }
29545
+ if (result.kind === "broker-stat-failed") {
29546
+ return {
29547
+ name,
29548
+ status: "warn",
29549
+ detail: `broker stat failed: ${result.msg}`
29550
+ };
29551
+ }
29552
+ return {
29553
+ name,
29554
+ status: "fail",
29555
+ detail: `inode mismatch \u2014 bind mount is NOT wiring host to broker. ` + `host inode=${result.hostInode} size=${result.hostSize}; ` + `broker inode=${result.brokerInode} size=${result.brokerSize}. ` + `The broker is operating on an ephemeral container-local file; ` + `data written there evaporates on container recreate.`,
29556
+ fix: "Run `switchroom apply` to regenerate compose with the correct " + "bind mount, then `docker compose -p switchroom up -d vault-broker` " + "to recreate the broker container."
29557
+ };
29558
+ }
29559
+ function probeBrokerUnlocked(opts) {
29560
+ const status = (opts?.statusProbe ?? defaultBrokerStatusProbe)();
29561
+ if (status === null) {
29562
+ return {
29563
+ name: "vault-broker unlocked (state)",
29564
+ status: "skip",
29565
+ detail: "vault-broker container unreachable"
29566
+ };
29567
+ }
29568
+ if (!status.unlocked) {
29569
+ return {
29570
+ name: "vault-broker unlocked (state)",
29571
+ status: "fail",
29572
+ detail: `broker reports locked despite config \u2014 auto-unlock failed silently. ` + `Common causes: \`/etc/machine-id\` mount missing or differs from the ` + `host the blob was sealed on; vault-auto-unlock blob corrupted; ` + `vault passphrase was rotated without re-running ` + `\`switchroom vault broker enable-auto-unlock\`.`,
29573
+ fix: "Re-run `switchroom vault broker enable-auto-unlock` on the host " + "to re-seal the blob against the current machine-id + passphrase. " + "Then restart the broker (`docker compose -p switchroom restart vault-broker`)."
29574
+ };
29575
+ }
29576
+ return {
29577
+ name: "vault-broker unlocked (state)",
29578
+ status: "ok",
29579
+ detail: `${status.keyCount} key(s) loaded`
29580
+ };
29581
+ }
29582
+ function defaultBrokerStatusProbe() {
29583
+ try {
29584
+ const out = execFileSync14("switchroom", ["vault", "broker", "status"], { stdio: ["ignore", "pipe", "pipe"], timeout: 3000, encoding: "utf8" });
29585
+ const parsed = JSON.parse(out.trim());
29586
+ if (!parsed.running)
29587
+ return null;
29588
+ return { unlocked: parsed.unlocked, keyCount: parsed.keyCount };
29589
+ } catch {
29590
+ return null;
29591
+ }
29592
+ }
29593
+ function runVaultBrokerDurabilityChecks(_config, opts) {
29594
+ const home2 = homedir27();
29595
+ const probe2 = opts?.inodeProbe ?? probeBindMountInode;
29596
+ return [
29597
+ probeBrokerUnlocked(opts?.statusProbe),
29598
+ probeAutoUnlockBlob(home2),
29599
+ probeMachineIdMount(),
29600
+ formatBindMountResult("vault-broker: vault.enc bind mount", join46(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join46(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
29601
+ formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join46(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join46(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
29602
+ formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join46(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join46(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log"))
29603
+ ];
29604
+ }
29605
+ function probeAutoUnlockBlob(home2) {
29606
+ const blobPath = join46(home2, ".switchroom", "vault-auto-unlock");
29607
+ if (!existsSync50(blobPath)) {
29608
+ return {
29609
+ name: "vault-broker: auto-unlock blob",
29610
+ status: "warn",
29611
+ detail: `${blobPath} not present \u2014 broker will fall back to interactive unlock`,
29612
+ fix: "Run `switchroom vault broker enable-auto-unlock` to seal the blob with the current passphrase + machine-id"
29613
+ };
29614
+ }
29615
+ const sz = statSync22(blobPath).size;
29616
+ if (sz === 0) {
29617
+ return {
29618
+ name: "vault-broker: auto-unlock blob",
29619
+ status: "warn",
29620
+ detail: `${blobPath} is 0 bytes (placeholder) \u2014 broker will fall back to interactive unlock`,
29621
+ fix: "Run `switchroom vault broker enable-auto-unlock` to actually seal the blob"
29622
+ };
29623
+ }
29624
+ return {
29625
+ name: "vault-broker: auto-unlock blob",
29626
+ status: "ok",
29627
+ detail: `${blobPath} present (${sz} bytes, machine-bound)`
29628
+ };
29629
+ }
29630
+ function probeMachineIdMount() {
29631
+ const hostExists = existsSync50("/etc/machine-id");
29632
+ if (!hostExists) {
29633
+ return {
29634
+ name: "vault-broker: machine-id passthrough",
29635
+ status: "fail",
29636
+ detail: "/etc/machine-id missing on host \u2014 auto-unlock key derivation impossible",
29637
+ fix: "Generate a machine-id (`systemd-machine-id-setup`) and re-seal the auto-unlock blob"
29638
+ };
29639
+ }
29640
+ const r = spawnDockerStat("/etc/machine-id");
29641
+ if (r.error || r.status === null || r.status >= 125) {
29642
+ return {
29643
+ name: "vault-broker: machine-id passthrough",
29644
+ status: "skip",
29645
+ detail: "vault-broker container unreachable"
29646
+ };
29647
+ }
29648
+ if (r.status !== 0) {
29649
+ return {
29650
+ name: "vault-broker: machine-id passthrough",
29651
+ status: "fail",
29652
+ detail: "broker container has no /etc/machine-id \u2014 compose `/etc/machine-id:/etc/machine-id:ro` mount is missing",
29653
+ fix: "Run `switchroom apply` to regenerate compose with the machine-id passthrough"
29654
+ };
29655
+ }
29656
+ return {
29657
+ name: "vault-broker: machine-id passthrough",
29658
+ status: "ok",
29659
+ detail: "broker reads the host machine-id"
29660
+ };
29661
+ }
29662
+ var init_doctor_vault_broker_durability = () => {};
29663
+
29403
29664
  // src/cli/doctor.ts
29404
29665
  var exports_doctor = {};
29405
29666
  __export(exports_doctor, {
@@ -29445,25 +29706,25 @@ import { execSync as execSync3, spawnSync as spawnSync7 } from "node:child_proce
29445
29706
  import {
29446
29707
  accessSync as accessSync2,
29447
29708
  constants as fsConstants5,
29448
- existsSync as existsSync50,
29709
+ existsSync as existsSync51,
29449
29710
  lstatSync as lstatSync5,
29450
29711
  mkdirSync as mkdirSync27,
29451
29712
  readFileSync as readFileSync45,
29452
29713
  readdirSync as readdirSync19,
29453
- statSync as statSync22
29714
+ statSync as statSync23
29454
29715
  } from "node:fs";
29455
- import { dirname as dirname12, join as join46, resolve as resolve30 } from "node:path";
29716
+ import { dirname as dirname12, join as join47, resolve as resolve30 } from "node:path";
29456
29717
  import { createPublicKey, createPrivateKey } from "node:crypto";
29457
29718
  function findInNvm(bin) {
29458
- const nvmRoot = join46(process.env.HOME ?? "", ".nvm", "versions", "node");
29459
- if (!existsSync50(nvmRoot))
29719
+ const nvmRoot = join47(process.env.HOME ?? "", ".nvm", "versions", "node");
29720
+ if (!existsSync51(nvmRoot))
29460
29721
  return null;
29461
29722
  try {
29462
29723
  const versions = readdirSync19(nvmRoot).sort().reverse();
29463
29724
  for (const v of versions) {
29464
- const candidate = join46(nvmRoot, v, "bin", bin);
29725
+ const candidate = join47(nvmRoot, v, "bin", bin);
29465
29726
  try {
29466
- const s = statSync22(candidate);
29727
+ const s = statSync23(candidate);
29467
29728
  if (s.isFile() || s.isSymbolicLink()) {
29468
29729
  return candidate;
29469
29730
  }
@@ -29626,21 +29887,21 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
29626
29887
  if (envBrowsersPath && envBrowsersPath.length > 0) {
29627
29888
  cacheLocations.push(envBrowsersPath);
29628
29889
  }
29629
- cacheLocations.push(join46(homeDir, ".cache", "ms-playwright"));
29890
+ cacheLocations.push(join47(homeDir, ".cache", "ms-playwright"));
29630
29891
  for (const cacheDir of cacheLocations) {
29631
- if (!existsSync50(cacheDir))
29892
+ if (!existsSync51(cacheDir))
29632
29893
  continue;
29633
29894
  try {
29634
29895
  const entries = readdirSync19(cacheDir).filter((e) => e.startsWith("chromium"));
29635
29896
  for (const entry of entries) {
29636
29897
  const candidates2 = [
29637
- join46(cacheDir, entry, "chrome-linux64", "chrome"),
29638
- join46(cacheDir, entry, "chrome-linux", "chrome"),
29639
- join46(cacheDir, entry, "chrome-linux64", "headless_shell"),
29640
- join46(cacheDir, entry, "chrome-linux", "headless_shell")
29898
+ join47(cacheDir, entry, "chrome-linux64", "chrome"),
29899
+ join47(cacheDir, entry, "chrome-linux", "chrome"),
29900
+ join47(cacheDir, entry, "chrome-linux64", "headless_shell"),
29901
+ join47(cacheDir, entry, "chrome-linux", "headless_shell")
29641
29902
  ];
29642
29903
  for (const path4 of candidates2) {
29643
- if (existsSync50(path4))
29904
+ if (existsSync51(path4))
29644
29905
  return path4;
29645
29906
  }
29646
29907
  }
@@ -29720,8 +29981,8 @@ function checkConfig(config, configPath) {
29720
29981
  function checkLegacyState() {
29721
29982
  const results = [];
29722
29983
  const h = process.env.HOME ?? "/root";
29723
- const clerkDir = join46(h, LEGACY_STATE_DIR);
29724
- const clerkPresent = existsSync50(clerkDir);
29984
+ const clerkDir = join47(h, LEGACY_STATE_DIR);
29985
+ const clerkPresent = existsSync51(clerkDir);
29725
29986
  results.push({
29726
29987
  name: "legacy ~/.clerk state",
29727
29988
  status: clerkPresent ? "warn" : "ok",
@@ -29730,7 +29991,7 @@ function checkLegacyState() {
29730
29991
  fix: "Legacy state detected. Run `mv ~/.clerk ~/.switchroom` and rename " + "any top-level `clerk:` key in switchroom.yaml to `switchroom:`. " + "This back-compat shim is REMOVED in v0.13.0 \u2014 no automatic " + "migration exists."
29731
29992
  } : {}
29732
29993
  });
29733
- const legacySock = join46(h, ".switchroom", "vault-broker.sock");
29994
+ const legacySock = join47(h, ".switchroom", "vault-broker.sock");
29734
29995
  let sockStat = null;
29735
29996
  try {
29736
29997
  sockStat = lstatSync5(legacySock);
@@ -29851,7 +30112,7 @@ function checkVault(config) {
29851
30112
  detail: "Approval auth: passphrase (two-factor)"
29852
30113
  };
29853
30114
  const pairsResult = checkVaultBrokerSocketPairs(config);
29854
- if (!existsSync50(vaultPath)) {
30115
+ if (!existsSync51(vaultPath)) {
29855
30116
  return [
29856
30117
  postureResult,
29857
30118
  {
@@ -30026,8 +30287,8 @@ async function checkHindsight(config) {
30026
30287
  }
30027
30288
  function checkPendingRetainsQueue(dir) {
30028
30289
  const home2 = process.env.HOME ?? "";
30029
- const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join46(home2, ".hindsight", "pending-retains");
30030
- if (!existsSync50(pendingDir)) {
30290
+ const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join47(home2, ".hindsight", "pending-retains");
30291
+ if (!existsSync51(pendingDir)) {
30031
30292
  return {
30032
30293
  name: "pending-retains queue",
30033
30294
  status: "ok",
@@ -30098,7 +30359,7 @@ function tryReadHostFile(path4) {
30098
30359
  }
30099
30360
  }
30100
30361
  function parseEnvFile(path4) {
30101
- if (!existsSync50(path4))
30362
+ if (!existsSync51(path4))
30102
30363
  return {};
30103
30364
  let content;
30104
30365
  try {
@@ -30157,7 +30418,7 @@ async function checkTelegram(config) {
30157
30418
  const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
30158
30419
  if (plugin !== "switchroom")
30159
30420
  continue;
30160
- const envPath = join46(agentsDir, name, "telegram", ".env");
30421
+ const envPath = join47(agentsDir, name, "telegram", ".env");
30161
30422
  const read = tryReadHostFile(envPath);
30162
30423
  if (read.kind === "eacces") {
30163
30424
  results.push({
@@ -30209,7 +30470,7 @@ async function checkTelegram(config) {
30209
30470
  }
30210
30471
  function checkStartShStale(agentName, startShPath) {
30211
30472
  const label = `${agentName}: start.sh scheduler block`;
30212
- if (!existsSync50(startShPath)) {
30473
+ if (!existsSync51(startShPath)) {
30213
30474
  return {
30214
30475
  name: label,
30215
30476
  status: "warn",
@@ -30240,7 +30501,7 @@ function checkStartShStale(agentName, startShPath) {
30240
30501
  }
30241
30502
  function checkLeakedHomeSwitchroom(agentName, agentDir) {
30242
30503
  const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
30243
- const path4 = join46(agentDir, "home", ".switchroom");
30504
+ const path4 = join47(agentDir, "home", ".switchroom");
30244
30505
  let stats;
30245
30506
  try {
30246
30507
  stats = lstatSync5(path4);
@@ -30277,8 +30538,8 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
30277
30538
  }
30278
30539
  function checkRepoHygiene(repoRoot) {
30279
30540
  const results = [];
30280
- const exportDir = join46(repoRoot, "clerk-export");
30281
- if (existsSync50(exportDir)) {
30541
+ const exportDir = join47(repoRoot, "clerk-export");
30542
+ if (existsSync51(exportDir)) {
30282
30543
  results.push({
30283
30544
  name: "repo hygiene: clerk-export/ on disk (#1072)",
30284
30545
  status: "warn",
@@ -30286,8 +30547,8 @@ function checkRepoHygiene(repoRoot) {
30286
30547
  fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
30287
30548
  });
30288
30549
  }
30289
- const knownTarball = join46(repoRoot, "clerk-export-with-secrets.tar.gz");
30290
- if (existsSync50(knownTarball)) {
30550
+ const knownTarball = join47(repoRoot, "clerk-export-with-secrets.tar.gz");
30551
+ if (existsSync51(knownTarball)) {
30291
30552
  results.push({
30292
30553
  name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
30293
30554
  status: "warn",
@@ -30304,7 +30565,7 @@ function checkRepoHygiene(repoRoot) {
30304
30565
  results.push({
30305
30566
  name: `repo hygiene: ${name} on disk (#1072)`,
30306
30567
  status: "warn",
30307
- detail: `${join46(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
30568
+ detail: `${join47(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
30308
30569
  fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
30309
30570
  });
30310
30571
  }
@@ -30327,10 +30588,10 @@ function checkRepoHygiene(repoRoot) {
30327
30588
  }
30328
30589
  function isSwitchroomCheckout(dir) {
30329
30590
  try {
30330
- if (!existsSync50(join46(dir, ".git")))
30591
+ if (!existsSync51(join47(dir, ".git")))
30331
30592
  return false;
30332
- const pkgPath = join46(dir, "package.json");
30333
- if (!existsSync50(pkgPath))
30593
+ const pkgPath = join47(dir, "package.json");
30594
+ if (!existsSync51(pkgPath))
30334
30595
  return false;
30335
30596
  const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
30336
30597
  return pkg.name === "switchroom";
@@ -30345,7 +30606,7 @@ function checkAgents(config, configPath) {
30345
30606
  const authStatuses = getAllAuthStatuses(config);
30346
30607
  for (const [name, agentConfig] of Object.entries(config.agents)) {
30347
30608
  const agentDir = resolve30(agentsDir, name);
30348
- if (!existsSync50(agentDir)) {
30609
+ if (!existsSync51(agentDir)) {
30349
30610
  results.push({
30350
30611
  name: `${name}: scaffold`,
30351
30612
  status: "fail",
@@ -30366,7 +30627,7 @@ function checkAgents(config, configPath) {
30366
30627
  fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
30367
30628
  });
30368
30629
  }
30369
- results.push(checkStartShStale(name, join46(agentDir, "start.sh")));
30630
+ results.push(checkStartShStale(name, join47(agentDir, "start.sh")));
30370
30631
  results.push(checkLeakedHomeSwitchroom(name, agentDir));
30371
30632
  const status = statuses[name];
30372
30633
  const active = status?.active ?? "unknown";
@@ -30443,8 +30704,8 @@ function checkAgents(config, configPath) {
30443
30704
  }
30444
30705
  }
30445
30706
  if (agentConfig.channels?.telegram?.plugin === "switchroom") {
30446
- const mcpJsonPath = join46(agentDir, ".mcp.json");
30447
- if (!existsSync50(mcpJsonPath)) {
30707
+ const mcpJsonPath = join47(agentDir, ".mcp.json");
30708
+ if (!existsSync51(mcpJsonPath)) {
30448
30709
  results.push({
30449
30710
  name: `${name}: .mcp.json`,
30450
30711
  status: "fail",
@@ -30529,7 +30790,7 @@ function mffEnvPath(config) {
30529
30790
  return agent ? resolve30(home2, ".switchroom/credentials", agent, "my-family-finance/.env") : resolve30(home2, ".switchroom/credentials/my-family-finance/.env");
30530
30791
  }
30531
30792
  function mffEnvState(envPath) {
30532
- if (!existsSync50(envPath))
30793
+ if (!existsSync51(envPath))
30533
30794
  return "absent";
30534
30795
  try {
30535
30796
  accessSync2(envPath, fsConstants5.R_OK);
@@ -30547,7 +30808,7 @@ function checkMffVaultKeyPresent(passphrase, vaultPath) {
30547
30808
  fix: "Export SWITCHROOM_VAULT_PASSPHRASE to enable MFF vault probes"
30548
30809
  };
30549
30810
  }
30550
- if (!existsSync50(vaultPath)) {
30811
+ if (!existsSync51(vaultPath)) {
30551
30812
  return {
30552
30813
  name: "mff: vault key present",
30553
30814
  status: "fail",
@@ -30600,7 +30861,7 @@ function deriveEd25519PublicKeyBytes(keyMaterial) {
30600
30861
  }
30601
30862
  }
30602
30863
  function checkMffVaultKeyFormat(passphrase, vaultPath) {
30603
- if (!passphrase || !existsSync50(vaultPath)) {
30864
+ if (!passphrase || !existsSync51(vaultPath)) {
30604
30865
  return {
30605
30866
  name: "mff: vault key format",
30606
30867
  status: "warn",
@@ -30744,8 +31005,8 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
30744
31005
  };
30745
31006
  }
30746
31007
  const credDir = dirname12(envPath);
30747
- const authScript = join46(credDir, "claude-auth.py");
30748
- if (!existsSync50(authScript)) {
31008
+ const authScript = join47(credDir, "claude-auth.py");
31009
+ if (!existsSync51(authScript)) {
30749
31010
  return {
30750
31011
  name: "mff: auth flow",
30751
31012
  status: "warn",
@@ -31023,6 +31284,10 @@ function registerDoctorCommand(program3) {
31023
31284
  },
31024
31285
  { title: "Legacy State", results: checkLegacyState() },
31025
31286
  { title: "Vault", results: checkVault(config) },
31287
+ {
31288
+ title: "Vault-broker durability",
31289
+ results: runVaultBrokerDurabilityChecks(config)
31290
+ },
31026
31291
  { title: "Vault access", results: await runSecretAccessChecks(config) },
31027
31292
  { title: "Memory (Hindsight)", results: await checkHindsight(config) },
31028
31293
  { title: "Telegram", results: await checkTelegram(config) },
@@ -31106,6 +31371,7 @@ var init_doctor = __esm(() => {
31106
31371
  init_doctor_inlined_secrets();
31107
31372
  init_doctor_audit_integrity();
31108
31373
  init_doctor_agent_smoke();
31374
+ init_doctor_vault_broker_durability();
31109
31375
  MANIFEST_WARN_ONLY = new Set([
31110
31376
  "@playwright/mcp",
31111
31377
  "hindsight.backend",
@@ -47034,7 +47300,7 @@ __export(exports_server2, {
47034
47300
  TOOLS: () => TOOLS2
47035
47301
  });
47036
47302
  import { randomBytes as randomBytes14 } from "node:crypto";
47037
- import { existsSync as existsSync74, readFileSync as readFileSync60 } from "node:fs";
47303
+ import { existsSync as existsSync75, readFileSync as readFileSync60 } from "node:fs";
47038
47304
  function selfSocketPath() {
47039
47305
  return `/run/switchroom/hostd/${SELF_AGENT}/sock`;
47040
47306
  }
@@ -47049,7 +47315,7 @@ async function dispatchTool2(name, args) {
47049
47315
  return errorText2("hostd MCP: SWITCHROOM_AGENT_NAME env var is not set \u2014 cannot " + "determine which per-agent socket to talk to.");
47050
47316
  }
47051
47317
  const sockPath = selfSocketPath();
47052
- if (!existsSync74(sockPath)) {
47318
+ if (!existsSync75(sockPath)) {
47053
47319
  return errorText2(`hostd MCP: socket not bound at ${sockPath}. The host-control ` + `daemon is either not installed (run \`switchroom hostd install\`) ` + `or this agent isn't admin-flagged in switchroom.yaml. RFC C ` + `bind-mounts the per-agent socket only when host_control.enabled ` + `is true AND the agent has admin: true.`);
47054
47320
  }
47055
47321
  let req;
@@ -47184,22 +47450,30 @@ async function dispatchTool2(name, args) {
47184
47450
  if (resp.result === "started" || resp.result === "completed") {
47185
47451
  return jsonText2(resp);
47186
47452
  }
47187
- return {
47188
- content: [{ type: "text", text: JSON.stringify(resp) }],
47189
- isError: true
47190
- };
47453
+ const content = [
47454
+ { type: "text", text: JSON.stringify(resp) }
47455
+ ];
47456
+ if (resp.error_envelope) {
47457
+ const env2 = resp.error_envelope;
47458
+ content.push({
47459
+ type: "text",
47460
+ text: `Structured error \u2014 fix.kind=${env2.fix?.kind ?? "none"}
47461
+ ` + JSON.stringify(env2, null, 2)
47462
+ });
47463
+ }
47464
+ return { content, isError: true };
47191
47465
  }
47192
47466
  function resolveAuditLogPath() {
47193
47467
  if (process.env.HOSTD_AUDIT_LOG_PATH)
47194
47468
  return process.env.HOSTD_AUDIT_LOG_PATH;
47195
47469
  const bindMounted = "/host-home/.switchroom/host-control-audit.log";
47196
- if (existsSync74(bindMounted))
47470
+ if (existsSync75(bindMounted))
47197
47471
  return bindMounted;
47198
47472
  return defaultAuditLogPath2();
47199
47473
  }
47200
47474
  function getLastUpdateApplyStatus() {
47201
47475
  const path8 = resolveAuditLogPath();
47202
- if (!existsSync74(path8)) {
47476
+ if (!existsSync75(path8)) {
47203
47477
  return errorText2(`get_status: audit log not found at ${path8}. No update_apply has run yet?`);
47204
47478
  }
47205
47479
  let raw;
@@ -47437,8 +47711,8 @@ var {
47437
47711
  } = import__.default;
47438
47712
 
47439
47713
  // src/build-info.ts
47440
- var VERSION = "0.13.32";
47441
- var COMMIT_SHA = "affa1f03";
47714
+ var VERSION = "0.13.35";
47715
+ var COMMIT_SHA = "c41aabe5";
47442
47716
 
47443
47717
  // src/cli/agent.ts
47444
47718
  init_source();
@@ -49488,17 +49762,16 @@ function classifyChange(path, agentDir, useHotReloadStable) {
49488
49762
  return "stale-till-restart";
49489
49763
  }
49490
49764
  function buildSettingsHooksBlock(p) {
49491
- const { agentName, agentConfig, hindsightEnabled, useSwitchroomPlugin, configPath } = p;
49765
+ const { agentName, agentConfig, hindsightEnabled, useSwitchroomPlugin } = p;
49492
49766
  const userHooks = translateHooksToClaudeShape(agentConfig.hooks);
49493
49767
  const wrapper = `bash "${join8(DOCKER_BIN_PATH, "run-hook.sh")}"`;
49494
49768
  const wrap = (source, command) => `${wrapper} ${shellSingleQuote(source)} ${command}`;
49495
49769
  const handoffEnabled = agentConfig.session_continuity?.enabled !== false;
49496
- const handoffConfigArg = configPath ? ` --config ${shellSingleQuote(DOCKER_CONFIG_PATH)}` : "";
49497
49770
  const stopHooks = [];
49498
49771
  if (handoffEnabled) {
49499
49772
  stopHooks.push({
49500
49773
  type: "command",
49501
- command: wrap("hook:handoff", `switchroom${handoffConfigArg} handoff ${agentName}`),
49774
+ command: wrap("hook:handoff", `switchroom handoff ${agentName}`),
49502
49775
  timeout: 35,
49503
49776
  async: true
49504
49777
  });
@@ -69811,17 +70084,17 @@ init_doctor();
69811
70084
  init_source();
69812
70085
  init_loader();
69813
70086
  init_lifecycle();
69814
- import { cpSync as cpSync2, existsSync as existsSync51, mkdirSync as mkdirSync28, readFileSync as readFileSync46, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync23 } from "node:fs";
70087
+ import { cpSync as cpSync2, existsSync as existsSync52, mkdirSync as mkdirSync28, readFileSync as readFileSync46, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync24 } from "node:fs";
69815
70088
  import { spawnSync as spawnSync8 } from "node:child_process";
69816
- import { join as join47, dirname as dirname13, resolve as resolve31 } from "node:path";
69817
- import { homedir as homedir27 } from "node:os";
69818
- var DEFAULT_COMPOSE_PATH = join47(homedir27(), ".switchroom", "compose", "docker-compose.yml");
70089
+ import { join as join48, dirname as dirname13, resolve as resolve31 } from "node:path";
70090
+ import { homedir as homedir28 } from "node:os";
70091
+ var DEFAULT_COMPOSE_PATH = join48(homedir28(), ".switchroom", "compose", "docker-compose.yml");
69819
70092
  function runningFromSwitchroomCheckout(scriptPath) {
69820
70093
  let dir = dirname13(scriptPath);
69821
70094
  for (let i = 0;i < 12; i++) {
69822
- if (existsSync51(join47(dir, ".git"))) {
70095
+ if (existsSync52(join48(dir, ".git"))) {
69823
70096
  try {
69824
- const pkg = JSON.parse(readFileSync46(join47(dir, "package.json"), "utf-8"));
70097
+ const pkg = JSON.parse(readFileSync46(join48(dir, "package.json"), "utf-8"));
69825
70098
  if (pkg.name === "switchroom")
69826
70099
  return true;
69827
70100
  } catch {}
@@ -69873,7 +70146,7 @@ function planUpdate(opts) {
69873
70146
  steps.push({
69874
70147
  name: "pull-images",
69875
70148
  description: "Pull broker / kernel / agent images from GHCR",
69876
- skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync51(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
70149
+ skipReason: opts.skipImages ? "--skip-images flag set" : !existsSync52(composePath) ? `compose file not found at ${composePath} (run \`switchroom apply --compose-only\` first)` : undefined,
69877
70150
  run: () => {
69878
70151
  const r = runner("docker", [
69879
70152
  "compose",
@@ -69952,14 +70225,14 @@ function planUpdate(opts) {
69952
70225
  return;
69953
70226
  }
69954
70227
  const source = resolve31(import.meta.dirname, "../../skills");
69955
- const dest = join47(homedir27(), ".switchroom", "skills", "_bundled");
69956
- if (!existsSync51(source)) {
70228
+ const dest = join48(homedir28(), ".switchroom", "skills", "_bundled");
70229
+ if (!existsSync52(source)) {
69957
70230
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
69958
70231
  `);
69959
70232
  return;
69960
70233
  }
69961
70234
  try {
69962
- if (existsSync51(dest)) {
70235
+ if (existsSync52(dest)) {
69963
70236
  rmSync12(dest, { recursive: true, force: true });
69964
70237
  }
69965
70238
  mkdirSync28(dirname13(dest), { recursive: true });
@@ -70062,12 +70335,12 @@ function defaultStatusProbe(composePath) {
70062
70335
  } catch {}
70063
70336
  if (scriptPath) {
70064
70337
  try {
70065
- cliBuiltAt = new Date(statSync23(scriptPath).mtimeMs).toISOString();
70338
+ cliBuiltAt = new Date(statSync24(scriptPath).mtimeMs).toISOString();
70066
70339
  } catch {}
70067
70340
  let dir = dirname13(scriptPath);
70068
70341
  for (let i = 0;i < 8; i++) {
70069
- const pkgPath = join47(dir, "package.json");
70070
- if (existsSync51(pkgPath)) {
70342
+ const pkgPath = join48(dir, "package.json");
70343
+ if (existsSync52(pkgPath)) {
70071
70344
  try {
70072
70345
  const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
70073
70346
  if (typeof pkg.version === "string")
@@ -70090,7 +70363,7 @@ function defaultStatusProbe(composePath) {
70090
70363
  warnings.push("could not resolve CLI version (no package.json found above the resolved script path)");
70091
70364
  }
70092
70365
  const services = [];
70093
- if (!existsSync51(composePath)) {
70366
+ if (!existsSync52(composePath)) {
70094
70367
  warnings.push(`compose file not found at ${composePath}; service status unknown`);
70095
70368
  return { cliVersion, cliBuiltAt, services, warnings };
70096
70369
  }
@@ -70285,8 +70558,8 @@ init_source();
70285
70558
  init_helpers();
70286
70559
  init_lifecycle();
70287
70560
  import { execSync as execSync4 } from "node:child_process";
70288
- import { existsSync as existsSync52, readFileSync as readFileSync47 } from "node:fs";
70289
- import { dirname as dirname14, join as join48 } from "node:path";
70561
+ import { existsSync as existsSync53, readFileSync as readFileSync47 } from "node:fs";
70562
+ import { dirname as dirname14, join as join49 } from "node:path";
70290
70563
  function getClaudeCodeVersion() {
70291
70564
  try {
70292
70565
  const out = execSync4("claude --version 2>/dev/null", {
@@ -70336,11 +70609,11 @@ function formatUptime3(timestamp) {
70336
70609
  function locateSwitchroomInstallDir() {
70337
70610
  let dir = import.meta.dirname;
70338
70611
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
70339
- const pkgPath = join48(dir, "package.json");
70340
- if (existsSync52(pkgPath)) {
70612
+ const pkgPath = join49(dir, "package.json");
70613
+ if (existsSync53(pkgPath)) {
70341
70614
  try {
70342
70615
  const pkg = JSON.parse(readFileSync47(pkgPath, "utf-8"));
70343
- if (pkg.name === "switchroom" && existsSync52(join48(dir, ".git"))) {
70616
+ if (pkg.name === "switchroom" && existsSync53(join49(dir, ".git"))) {
70344
70617
  return dir;
70345
70618
  }
70346
70619
  } catch {}
@@ -70517,21 +70790,32 @@ init_loader();
70517
70790
  import { resolve as resolve33 } from "node:path";
70518
70791
  function registerHandoffCommand(program3) {
70519
70792
  program3.command("handoff <agent>", { hidden: true }).description("Build the agent's session handoff sidecars \u2014 a transcript-tail " + "briefing (.handoff.md) and topic line (.handoff-topic). " + "[internal \u2014 used by the Stop hook]").option("--max-turns <n>", "Max turns kept in the handoff transcript tail", String(DEFAULT_MAX_TURNS)).action(withConfigError(async (agentName, opts) => {
70520
- const config = getConfig(program3);
70521
- const agentConfig = config.agents[agentName];
70522
- if (!agentConfig) {
70523
- process.stderr.write(`handoff: agent "${agentName}" not defined in switchroom.yaml
70793
+ let agentConfig;
70794
+ let agentDir;
70795
+ try {
70796
+ const config = getConfig(program3);
70797
+ agentConfig = config.agents[agentName];
70798
+ if (!agentConfig) {
70799
+ process.stderr.write(`handoff: agent "${agentName}" not defined in switchroom.yaml
70524
70800
  `);
70525
- return;
70801
+ return;
70802
+ }
70803
+ const agentsDir = resolveAgentsDir(config);
70804
+ agentDir = resolve33(agentsDir, agentName);
70805
+ } catch (err) {
70806
+ if (!(err instanceof ConfigError))
70807
+ throw err;
70808
+ process.stderr.write(`handoff: yaml unavailable (${err.message}); using defaults
70809
+ `);
70810
+ agentConfig = undefined;
70811
+ agentDir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
70526
70812
  }
70527
- const continuity = agentConfig.session_continuity;
70813
+ const continuity = agentConfig?.session_continuity;
70528
70814
  if (continuity?.enabled === false) {
70529
70815
  process.stderr.write(`handoff: session_continuity.enabled=false for "${agentName}"; skipping
70530
70816
  `);
70531
70817
  return;
70532
70818
  }
70533
- const agentsDir = resolveAgentsDir(config);
70534
- const agentDir = resolve33(agentsDir, agentName);
70535
70819
  const claudeConfigDir = resolve33(agentDir, ".claude");
70536
70820
  const jsonl = findLatestSessionJsonl(claudeConfigDir);
70537
70821
  if (!jsonl) {
@@ -70555,18 +70839,18 @@ function registerHandoffCommand(program3) {
70555
70839
  // src/issues/store.ts
70556
70840
  import {
70557
70841
  closeSync as closeSync11,
70558
- existsSync as existsSync53,
70842
+ existsSync as existsSync54,
70559
70843
  mkdirSync as mkdirSync29,
70560
70844
  openSync as openSync11,
70561
70845
  readdirSync as readdirSync20,
70562
70846
  readFileSync as readFileSync48,
70563
70847
  renameSync as renameSync11,
70564
- statSync as statSync24,
70848
+ statSync as statSync25,
70565
70849
  unlinkSync as unlinkSync11,
70566
70850
  writeFileSync as writeFileSync25,
70567
70851
  writeSync as writeSync7
70568
70852
  } from "node:fs";
70569
- import { join as join49 } from "node:path";
70853
+ import { join as join50 } from "node:path";
70570
70854
  import { randomBytes as randomBytes11 } from "node:crypto";
70571
70855
  import { execSync as execSync5 } from "node:child_process";
70572
70856
 
@@ -70886,8 +71170,8 @@ function redactedMarker(ruleId) {
70886
71170
  var ISSUES_FILE = "issues.jsonl";
70887
71171
  var ISSUES_LOCK = "issues.lock";
70888
71172
  function readAll(stateDir) {
70889
- const path4 = join49(stateDir, ISSUES_FILE);
70890
- if (!existsSync53(path4))
71173
+ const path4 = join50(stateDir, ISSUES_FILE);
71174
+ if (!existsSync54(path4))
70891
71175
  return [];
70892
71176
  let raw;
70893
71177
  try {
@@ -70964,7 +71248,7 @@ function record(stateDir, input, nowFn = Date.now) {
70964
71248
  });
70965
71249
  }
70966
71250
  function resolve34(stateDir, fingerprint, nowFn = Date.now) {
70967
- if (!existsSync53(join49(stateDir, ISSUES_FILE)))
71251
+ if (!existsSync54(join50(stateDir, ISSUES_FILE)))
70968
71252
  return 0;
70969
71253
  return withLock(stateDir, () => {
70970
71254
  const all = readAll(stateDir);
@@ -70982,7 +71266,7 @@ function resolve34(stateDir, fingerprint, nowFn = Date.now) {
70982
71266
  });
70983
71267
  }
70984
71268
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
70985
- if (!existsSync53(join49(stateDir, ISSUES_FILE)))
71269
+ if (!existsSync54(join50(stateDir, ISSUES_FILE)))
70986
71270
  return 0;
70987
71271
  return withLock(stateDir, () => {
70988
71272
  const all = readAll(stateDir);
@@ -71000,7 +71284,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
71000
71284
  });
71001
71285
  }
71002
71286
  function prune(stateDir, opts = {}) {
71003
- if (!existsSync53(join49(stateDir, ISSUES_FILE)))
71287
+ if (!existsSync54(join50(stateDir, ISSUES_FILE)))
71004
71288
  return 0;
71005
71289
  return withLock(stateDir, () => {
71006
71290
  const all = readAll(stateDir);
@@ -71033,7 +71317,7 @@ function ensureDir(stateDir) {
71033
71317
  mkdirSync29(stateDir, { recursive: true });
71034
71318
  }
71035
71319
  function writeAll(stateDir, events) {
71036
- const path4 = join49(stateDir, ISSUES_FILE);
71320
+ const path4 = join50(stateDir, ISSUES_FILE);
71037
71321
  sweepOrphanTmpFiles(stateDir);
71038
71322
  const tmp = `${path4}.tmp-${process.pid}-${randomBytes11(4).toString("hex")}`;
71039
71323
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -71055,9 +71339,9 @@ function sweepOrphanTmpFiles(stateDir) {
71055
71339
  for (const entry of entries) {
71056
71340
  if (!entry.startsWith(TMP_PREFIX))
71057
71341
  continue;
71058
- const tmpPath = join49(stateDir, entry);
71342
+ const tmpPath = join50(stateDir, entry);
71059
71343
  try {
71060
- const stat = statSync24(tmpPath);
71344
+ const stat = statSync25(tmpPath);
71061
71345
  if (stat.mtimeMs < cutoff) {
71062
71346
  unlinkSync11(tmpPath);
71063
71347
  }
@@ -71067,7 +71351,7 @@ function sweepOrphanTmpFiles(stateDir) {
71067
71351
  var LOCK_RETRY_MS = 25;
71068
71352
  var LOCK_TIMEOUT_MS = 1e4;
71069
71353
  function withLock(stateDir, fn) {
71070
- const lockPath = join49(stateDir, ISSUES_LOCK);
71354
+ const lockPath = join50(stateDir, ISSUES_LOCK);
71071
71355
  const startedAt = Date.now();
71072
71356
  let fd = null;
71073
71357
  while (fd === null) {
@@ -71350,22 +71634,22 @@ function relTime(deltaMs) {
71350
71634
 
71351
71635
  // src/cli/deps.ts
71352
71636
  init_source();
71353
- import { existsSync as existsSync56 } from "node:fs";
71354
- import { homedir as homedir30 } from "node:os";
71355
- import { join as join52, resolve as resolve35 } from "node:path";
71637
+ import { existsSync as existsSync57 } from "node:fs";
71638
+ import { homedir as homedir31 } from "node:os";
71639
+ import { join as join53, resolve as resolve35 } from "node:path";
71356
71640
 
71357
71641
  // src/deps/python.ts
71358
71642
  import { createHash as createHash10 } from "node:crypto";
71359
71643
  import {
71360
- existsSync as existsSync54,
71644
+ existsSync as existsSync55,
71361
71645
  mkdirSync as mkdirSync30,
71362
71646
  readFileSync as readFileSync49,
71363
71647
  rmSync as rmSync13,
71364
71648
  writeFileSync as writeFileSync26
71365
71649
  } from "node:fs";
71366
- import { dirname as dirname15, join as join50 } from "node:path";
71367
- import { homedir as homedir28 } from "node:os";
71368
- import { execFileSync as execFileSync14 } from "node:child_process";
71650
+ import { dirname as dirname15, join as join51 } from "node:path";
71651
+ import { homedir as homedir29 } from "node:os";
71652
+ import { execFileSync as execFileSync15 } from "node:child_process";
71369
71653
 
71370
71654
  class PythonEnvError extends Error {
71371
71655
  stderr;
@@ -71376,7 +71660,7 @@ class PythonEnvError extends Error {
71376
71660
  }
71377
71661
  }
71378
71662
  function defaultPythonCacheRoot() {
71379
- return join50(homedir28(), ".switchroom", "deps", "python");
71663
+ return join51(homedir29(), ".switchroom", "deps", "python");
71380
71664
  }
71381
71665
  function hashFile(path4) {
71382
71666
  return createHash10("sha256").update(readFileSync49(path4)).digest("hex");
@@ -71385,16 +71669,16 @@ function ensurePythonEnv(opts) {
71385
71669
  const { skillName, requirementsPath, force = false } = opts;
71386
71670
  const cacheRoot = opts.cacheRoot ?? defaultPythonCacheRoot();
71387
71671
  const hostPython = opts.pythonBin ?? "python3";
71388
- if (!existsSync54(requirementsPath)) {
71672
+ if (!existsSync55(requirementsPath)) {
71389
71673
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
71390
71674
  }
71391
- const venvDir = join50(cacheRoot, skillName);
71392
- const stampPath = join50(venvDir, ".requirements.sha256");
71393
- const binDir = join50(venvDir, "bin");
71394
- const pythonBin = join50(binDir, "python");
71395
- const pipBin = join50(binDir, "pip");
71675
+ const venvDir = join51(cacheRoot, skillName);
71676
+ const stampPath = join51(venvDir, ".requirements.sha256");
71677
+ const binDir = join51(venvDir, "bin");
71678
+ const pythonBin = join51(binDir, "python");
71679
+ const pipBin = join51(binDir, "pip");
71396
71680
  const targetHash = hashFile(requirementsPath);
71397
- if (!force && existsSync54(stampPath) && existsSync54(pythonBin)) {
71681
+ if (!force && existsSync55(stampPath) && existsSync55(pythonBin)) {
71398
71682
  const existingHash = readFileSync49(stampPath, "utf8").trim();
71399
71683
  if (existingHash === targetHash) {
71400
71684
  return {
@@ -71407,12 +71691,12 @@ function ensurePythonEnv(opts) {
71407
71691
  };
71408
71692
  }
71409
71693
  }
71410
- if (existsSync54(venvDir)) {
71694
+ if (existsSync55(venvDir)) {
71411
71695
  rmSync13(venvDir, { recursive: true, force: true });
71412
71696
  }
71413
71697
  mkdirSync30(dirname15(venvDir), { recursive: true });
71414
71698
  try {
71415
- execFileSync14(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
71699
+ execFileSync15(hostPython, ["-m", "venv", venvDir], { stdio: "pipe" });
71416
71700
  } catch (err) {
71417
71701
  const e = err;
71418
71702
  throw new PythonEnvError(`Failed to create venv for skill "${skillName}" with ${hostPython}: ${e.message}`, e.stderr?.toString());
@@ -71424,7 +71708,7 @@ function ensurePythonEnv(opts) {
71424
71708
  delete childEnv.PIP_TARGET;
71425
71709
  delete childEnv.PIP_PREFIX;
71426
71710
  delete childEnv.PYTHONUSERBASE;
71427
- execFileSync14(pipBin, ["install", "--disable-pip-version-check", "-r", requirementsPath], { stdio: "pipe", env: childEnv });
71711
+ execFileSync15(pipBin, ["install", "--disable-pip-version-check", "-r", requirementsPath], { stdio: "pipe", env: childEnv });
71428
71712
  } catch (err) {
71429
71713
  const e = err;
71430
71714
  throw new PythonEnvError(`Failed to install requirements for skill "${skillName}": ${e.message}`, e.stderr?.toString());
@@ -71445,15 +71729,15 @@ function ensurePythonEnv(opts) {
71445
71729
  import { createHash as createHash11 } from "node:crypto";
71446
71730
  import {
71447
71731
  copyFileSync as copyFileSync9,
71448
- existsSync as existsSync55,
71732
+ existsSync as existsSync56,
71449
71733
  mkdirSync as mkdirSync31,
71450
71734
  readFileSync as readFileSync50,
71451
71735
  rmSync as rmSync14,
71452
71736
  writeFileSync as writeFileSync27
71453
71737
  } from "node:fs";
71454
- import { dirname as dirname16, join as join51 } from "node:path";
71455
- import { homedir as homedir29 } from "node:os";
71456
- import { execFileSync as execFileSync15 } from "node:child_process";
71738
+ import { dirname as dirname16, join as join52 } from "node:path";
71739
+ import { homedir as homedir30 } from "node:os";
71740
+ import { execFileSync as execFileSync16 } from "node:child_process";
71457
71741
 
71458
71742
  class NodeEnvError extends Error {
71459
71743
  stderr;
@@ -71475,7 +71759,7 @@ var LOCKFILES_FOR = {
71475
71759
  npm: ["package-lock.json"]
71476
71760
  };
71477
71761
  function defaultNodeCacheRoot() {
71478
- return join51(homedir29(), ".switchroom", "deps", "node");
71762
+ return join52(homedir30(), ".switchroom", "deps", "node");
71479
71763
  }
71480
71764
  function hashDepInputs(packageJsonPath) {
71481
71765
  const sourceDir = dirname16(packageJsonPath);
@@ -71484,8 +71768,8 @@ function hashDepInputs(packageJsonPath) {
71484
71768
  `);
71485
71769
  hasher.update(readFileSync50(packageJsonPath));
71486
71770
  for (const lockName of ALL_LOCKFILES) {
71487
- const lockPath = join51(sourceDir, lockName);
71488
- if (existsSync55(lockPath)) {
71771
+ const lockPath = join52(sourceDir, lockName);
71772
+ if (existsSync56(lockPath)) {
71489
71773
  hasher.update(`
71490
71774
  `);
71491
71775
  hasher.update(lockName);
@@ -71500,16 +71784,16 @@ function ensureNodeEnv(opts) {
71500
71784
  const { skillName, packageJsonPath, force = false } = opts;
71501
71785
  const cacheRoot = opts.cacheRoot ?? defaultNodeCacheRoot();
71502
71786
  const installer = opts.installer ?? "bun";
71503
- if (!existsSync55(packageJsonPath)) {
71787
+ if (!existsSync56(packageJsonPath)) {
71504
71788
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
71505
71789
  }
71506
71790
  const sourceDir = dirname16(packageJsonPath);
71507
- const envDir = join51(cacheRoot, skillName);
71508
- const stampPath = join51(envDir, ".package.sha256");
71509
- const nodeModulesDir = join51(envDir, "node_modules");
71510
- const binDir = join51(nodeModulesDir, ".bin");
71791
+ const envDir = join52(cacheRoot, skillName);
71792
+ const stampPath = join52(envDir, ".package.sha256");
71793
+ const nodeModulesDir = join52(envDir, "node_modules");
71794
+ const binDir = join52(nodeModulesDir, ".bin");
71511
71795
  const targetHash = hashDepInputs(packageJsonPath);
71512
- if (!force && existsSync55(stampPath) && existsSync55(nodeModulesDir)) {
71796
+ if (!force && existsSync56(stampPath) && existsSync56(nodeModulesDir)) {
71513
71797
  const existingHash = readFileSync50(stampPath, "utf8").trim();
71514
71798
  if (existingHash === targetHash) {
71515
71799
  return {
@@ -71521,26 +71805,26 @@ function ensureNodeEnv(opts) {
71521
71805
  };
71522
71806
  }
71523
71807
  }
71524
- if (existsSync55(envDir)) {
71808
+ if (existsSync56(envDir)) {
71525
71809
  rmSync14(envDir, { recursive: true, force: true });
71526
71810
  }
71527
71811
  mkdirSync31(envDir, { recursive: true });
71528
- copyFileSync9(packageJsonPath, join51(envDir, "package.json"));
71812
+ copyFileSync9(packageJsonPath, join52(envDir, "package.json"));
71529
71813
  let copiedLockfile = false;
71530
71814
  for (const lockName of LOCKFILES_FOR[installer]) {
71531
- const lockPath = join51(sourceDir, lockName);
71532
- if (existsSync55(lockPath)) {
71533
- copyFileSync9(lockPath, join51(envDir, lockName));
71815
+ const lockPath = join52(sourceDir, lockName);
71816
+ if (existsSync56(lockPath)) {
71817
+ copyFileSync9(lockPath, join52(envDir, lockName));
71534
71818
  copiedLockfile = true;
71535
71819
  }
71536
71820
  }
71537
71821
  try {
71538
71822
  if (installer === "bun") {
71539
71823
  const args = copiedLockfile ? ["install", "--frozen-lockfile"] : ["install"];
71540
- execFileSync15("bun", args, { cwd: envDir, stdio: "pipe" });
71824
+ execFileSync16("bun", args, { cwd: envDir, stdio: "pipe" });
71541
71825
  } else {
71542
71826
  const args = copiedLockfile ? ["ci"] : ["install"];
71543
- execFileSync15("npm", args, { cwd: envDir, stdio: "pipe" });
71827
+ execFileSync16("npm", args, { cwd: envDir, stdio: "pipe" });
71544
71828
  }
71545
71829
  } catch (err) {
71546
71830
  const e = err;
@@ -71559,28 +71843,28 @@ function ensureNodeEnv(opts) {
71559
71843
 
71560
71844
  // src/cli/deps.ts
71561
71845
  function builtinSkillsRoot() {
71562
- return resolve35(homedir30(), ".switchroom/skills/_bundled");
71846
+ return resolve35(homedir31(), ".switchroom/skills/_bundled");
71563
71847
  }
71564
71848
  function registerDepsCommand(program3) {
71565
71849
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
71566
71850
  deps.command("rebuild <skill>").description("Rebuild the Python venv and/or Node node_modules cache for a skill").option("-p, --python", "Rebuild only the Python env").option("-n, --node", "Rebuild only the Node env").action(async (skill, opts) => {
71567
71851
  const skillsRoot = builtinSkillsRoot();
71568
- if (!existsSync56(skillsRoot)) {
71852
+ if (!existsSync57(skillsRoot)) {
71569
71853
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
71570
71854
  process.exit(1);
71571
71855
  }
71572
- const skillDir = join52(skillsRoot, skill);
71573
- if (!existsSync56(skillDir)) {
71856
+ const skillDir = join53(skillsRoot, skill);
71857
+ if (!existsSync57(skillDir)) {
71574
71858
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
71575
71859
  process.exit(1);
71576
71860
  }
71577
- const requirementsPath = join52(skillDir, "requirements.txt");
71578
- const packageJsonPath = join52(skillDir, "package.json");
71579
- const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync56(requirementsPath));
71580
- const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync56(packageJsonPath));
71861
+ const requirementsPath = join53(skillDir, "requirements.txt");
71862
+ const packageJsonPath = join53(skillDir, "package.json");
71863
+ const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync57(requirementsPath));
71864
+ const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync57(packageJsonPath));
71581
71865
  let did = 0;
71582
71866
  if (wantPython) {
71583
- if (!existsSync56(requirementsPath)) {
71867
+ if (!existsSync57(requirementsPath)) {
71584
71868
  console.error(source_default.red(`Skill "${skill}" has no requirements.txt at ${requirementsPath}`));
71585
71869
  process.exit(1);
71586
71870
  }
@@ -71604,7 +71888,7 @@ function registerDepsCommand(program3) {
71604
71888
  }
71605
71889
  }
71606
71890
  if (wantNode) {
71607
- if (!existsSync56(packageJsonPath)) {
71891
+ if (!existsSync57(packageJsonPath)) {
71608
71892
  console.error(source_default.red(`Skill "${skill}" has no package.json at ${packageJsonPath}`));
71609
71893
  process.exit(1);
71610
71894
  }
@@ -71637,7 +71921,7 @@ function registerDepsCommand(program3) {
71637
71921
  // src/cli/workspace.ts
71638
71922
  init_helpers();
71639
71923
  init_loader();
71640
- import { existsSync as existsSync57 } from "node:fs";
71924
+ import { existsSync as existsSync58 } from "node:fs";
71641
71925
  import { resolve as resolve36, sep as sep3 } from "node:path";
71642
71926
  import { spawnSync as spawnSync9 } from "node:child_process";
71643
71927
 
@@ -72414,7 +72698,7 @@ function registerWorkspaceCommand(program3) {
72414
72698
  if (!dir)
72415
72699
  return;
72416
72700
  const gitDir = resolve36(dir, ".git");
72417
- if (!existsSync57(gitDir)) {
72701
+ if (!existsSync58(gitDir)) {
72418
72702
  process.stdout.write(`Workspace is not a git repository. Re-run \`switchroom agent create ${agentName}\` ` + `or manually \`git init\` in ${dir} to enable versioning.
72419
72703
  `);
72420
72704
  return;
@@ -72468,7 +72752,7 @@ function registerWorkspaceCommand(program3) {
72468
72752
  if (!dir)
72469
72753
  return;
72470
72754
  const gitDir = resolve36(dir, ".git");
72471
- if (!existsSync57(gitDir)) {
72755
+ if (!existsSync58(gitDir)) {
72472
72756
  process.stdout.write(`Workspace is not a git repository.
72473
72757
  `);
72474
72758
  return;
@@ -72493,7 +72777,7 @@ function resolveAgentWorkspaceDirOrExit(program3, agentName) {
72493
72777
  const agentsDir = resolveAgentsDir(config);
72494
72778
  const agentDir = resolve36(agentsDir, agentName);
72495
72779
  const dir = resolveAgentWorkspaceDir(agentDir);
72496
- if (!existsSync57(dir)) {
72780
+ if (!existsSync58(dir)) {
72497
72781
  process.stderr.write(`workspace: ${dir} does not exist yet. Run \`switchroom setup\` or \`switchroom agent scaffold ${agentName}\` to seed it.
72498
72782
  `);
72499
72783
  return;
@@ -72529,8 +72813,8 @@ function safeParseInt(value, fallback) {
72529
72813
  init_helpers();
72530
72814
  init_loader();
72531
72815
  init_merge();
72532
- import { copyFileSync as copyFileSync10, existsSync as existsSync58, readFileSync as readFileSync51, writeFileSync as writeFileSync28 } from "node:fs";
72533
- import { join as join53, resolve as resolve37 } from "node:path";
72816
+ import { copyFileSync as copyFileSync10, existsSync as existsSync59, readFileSync as readFileSync51, writeFileSync as writeFileSync28 } from "node:fs";
72817
+ import { join as join54, resolve as resolve37 } from "node:path";
72534
72818
  init_schema();
72535
72819
  function resolveSoulTargetOrExit(program3, agentName) {
72536
72820
  const config = getConfig(program3);
@@ -72545,7 +72829,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
72545
72829
  const agentsDir = resolveAgentsDir(config);
72546
72830
  const agentDir = resolve37(agentsDir, agentName);
72547
72831
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
72548
- if (!existsSync58(workspaceDir)) {
72832
+ if (!existsSync59(workspaceDir)) {
72549
72833
  console.error(`soul: ${workspaceDir} does not exist yet. Run \`switchroom setup\` ` + `or \`switchroom agent scaffold ${agentName}\` to seed it.`);
72550
72834
  process.exit(1);
72551
72835
  }
@@ -72554,7 +72838,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
72554
72838
  profileName,
72555
72839
  profilePath,
72556
72840
  workspaceDir,
72557
- soulPath: join53(workspaceDir, "SOUL.md"),
72841
+ soulPath: join54(workspaceDir, "SOUL.md"),
72558
72842
  soul: merged.soul
72559
72843
  };
72560
72844
  }
@@ -72571,7 +72855,7 @@ function registerSoulCommand(program3) {
72571
72855
  const t = resolveSoulTargetOrExit(program3, agentName);
72572
72856
  if (!t)
72573
72857
  return;
72574
- if (!existsSync58(t.soulPath)) {
72858
+ if (!existsSync59(t.soulPath)) {
72575
72859
  console.error(`soul: ${t.soulPath} does not exist yet \u2014 run ` + `\`switchroom soul reset ${agentName}\` to seed it.`);
72576
72860
  process.exit(1);
72577
72861
  }
@@ -72586,7 +72870,7 @@ function registerSoulCommand(program3) {
72586
72870
  console.error(`soul: profile "${t.profileName}" ships no SOUL.md.hbs \u2014 ` + `nothing to re-seed from.`);
72587
72871
  process.exit(1);
72588
72872
  }
72589
- const exists = existsSync58(t.soulPath);
72873
+ const exists = existsSync59(t.soulPath);
72590
72874
  if (exists && !opts.yes) {
72591
72875
  if (!isInteractive()) {
72592
72876
  console.error(`soul: ${t.soulPath} already exists. Re-run with --yes to ` + `replace it (the current file is backed up to SOUL.md.bak).`);
@@ -72601,7 +72885,7 @@ function registerSoulCommand(program3) {
72601
72885
  let backupPath;
72602
72886
  if (exists) {
72603
72887
  backupPath = `${t.soulPath}.bak`;
72604
- if (existsSync58(backupPath)) {
72888
+ if (existsSync59(backupPath)) {
72605
72889
  backupPath = `${t.soulPath}.bak.${Date.now()}`;
72606
72890
  }
72607
72891
  copyFileSync10(t.soulPath, backupPath);
@@ -72620,8 +72904,8 @@ function registerSoulCommand(program3) {
72620
72904
  // src/cli/debug.ts
72621
72905
  init_helpers();
72622
72906
  init_loader();
72623
- import { existsSync as existsSync59, readFileSync as readFileSync52, readdirSync as readdirSync21, statSync as statSync25 } from "node:fs";
72624
- import { resolve as resolve38, join as join54 } from "node:path";
72907
+ import { existsSync as existsSync60, readFileSync as readFileSync52, readdirSync as readdirSync21, statSync as statSync26 } from "node:fs";
72908
+ import { resolve as resolve38, join as join55 } from "node:path";
72625
72909
  import { createHash as createHash12 } from "node:crypto";
72626
72910
  init_merge();
72627
72911
  init_hindsight();
@@ -72635,8 +72919,8 @@ function sha256(content) {
72635
72919
  return createHash12("sha256").update(content).digest("hex").slice(0, 16);
72636
72920
  }
72637
72921
  function findLatestTranscriptJsonl(claudeConfigDir) {
72638
- const projectsDir = join54(claudeConfigDir, "projects");
72639
- if (!existsSync59(projectsDir))
72922
+ const projectsDir = join55(claudeConfigDir, "projects");
72923
+ if (!existsSync60(projectsDir))
72640
72924
  return;
72641
72925
  try {
72642
72926
  const entries = readdirSync21(projectsDir, { withFileTypes: true });
@@ -72644,11 +72928,11 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
72644
72928
  for (const entry of entries) {
72645
72929
  if (!entry.isDirectory())
72646
72930
  continue;
72647
- const projectPath = join54(projectsDir, entry.name);
72648
- const transcriptPath = join54(projectPath, "transcript.jsonl");
72649
- if (!existsSync59(transcriptPath))
72931
+ const projectPath = join55(projectsDir, entry.name);
72932
+ const transcriptPath = join55(projectPath, "transcript.jsonl");
72933
+ if (!existsSync60(transcriptPath))
72650
72934
  continue;
72651
- const stat3 = statSync25(transcriptPath);
72935
+ const stat3 = statSync26(transcriptPath);
72652
72936
  if (!latest || stat3.mtimeMs > latest.mtime) {
72653
72937
  latest = { path: transcriptPath, mtime: stat3.mtimeMs };
72654
72938
  }
@@ -72709,16 +72993,16 @@ function registerDebugCommand(program3) {
72709
72993
  }
72710
72994
  const agentsDir = resolveAgentsDir(config);
72711
72995
  const agentDir = resolve38(agentsDir, agentName);
72712
- if (!existsSync59(agentDir)) {
72996
+ if (!existsSync60(agentDir)) {
72713
72997
  console.error(`Agent directory not found: ${agentDir}`);
72714
72998
  process.exit(1);
72715
72999
  }
72716
73000
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
72717
- const claudeConfigDir = join54(agentDir, ".claude");
72718
- const claudeMdPath = join54(agentDir, "CLAUDE.md");
72719
- const soulMdPath = join54(agentDir, "SOUL.md");
72720
- const workspaceSoulMdPath = join54(workspaceDir, "SOUL.md");
72721
- const handoffPath = join54(agentDir, ".handoff.md");
73001
+ const claudeConfigDir = join55(agentDir, ".claude");
73002
+ const claudeMdPath = join55(agentDir, "CLAUDE.md");
73003
+ const soulMdPath = join55(agentDir, "SOUL.md");
73004
+ const workspaceSoulMdPath = join55(workspaceDir, "SOUL.md");
73005
+ const handoffPath = join55(agentDir, ".handoff.md");
72722
73006
  const lastN = parseInt(opts.last, 10);
72723
73007
  if (isNaN(lastN) || lastN < 1) {
72724
73008
  console.error("--last must be a positive integer");
@@ -72764,7 +73048,7 @@ function registerDebugCommand(program3) {
72764
73048
  }
72765
73049
  console.log(`=== Append System Prompt (per-session) ===
72766
73050
  `);
72767
- const handoffContent = existsSync59(handoffPath) ? readFileSync52(handoffPath, "utf-8") : "";
73051
+ const handoffContent = existsSync60(handoffPath) ? readFileSync52(handoffPath, "utf-8") : "";
72768
73052
  if (handoffContent.trim().length > 0) {
72769
73053
  console.log(`-- Handoff Briefing (${formatBytes(handoffContent.length)}) --`);
72770
73054
  console.log(handoffContent);
@@ -72775,7 +73059,7 @@ function registerDebugCommand(program3) {
72775
73059
  }
72776
73060
  console.log(`=== CLAUDE.md (auto-loaded by Claude Code) ===
72777
73061
  `);
72778
- const claudeMdContent = existsSync59(claudeMdPath) ? readFileSync52(claudeMdPath, "utf-8") : "";
73062
+ const claudeMdContent = existsSync60(claudeMdPath) ? readFileSync52(claudeMdPath, "utf-8") : "";
72779
73063
  if (claudeMdContent.trim().length > 0) {
72780
73064
  console.log(`(${formatBytes(claudeMdContent.length)})`);
72781
73065
  console.log(claudeMdContent);
@@ -72786,7 +73070,7 @@ function registerDebugCommand(program3) {
72786
73070
  }
72787
73071
  console.log(`=== Persona (SOUL.md) ===
72788
73072
  `);
72789
- const soulMdContent = existsSync59(soulMdPath) ? readFileSync52(soulMdPath, "utf-8") : existsSync59(workspaceSoulMdPath) ? readFileSync52(workspaceSoulMdPath, "utf-8") : "";
73073
+ const soulMdContent = existsSync60(soulMdPath) ? readFileSync52(soulMdPath, "utf-8") : existsSync60(workspaceSoulMdPath) ? readFileSync52(workspaceSoulMdPath, "utf-8") : "";
72790
73074
  if (soulMdContent.trim().length > 0) {
72791
73075
  console.log(`(${formatBytes(soulMdContent.length)})`);
72792
73076
  console.log(soulMdContent);
@@ -72866,10 +73150,10 @@ function registerDebugCommand(program3) {
72866
73150
  init_source();
72867
73151
 
72868
73152
  // src/worktree/claim.ts
72869
- import { execFileSync as execFileSync16 } from "node:child_process";
72870
- import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as existsSync61, unlinkSync as unlinkSync13 } from "node:fs";
72871
- import { join as join56, resolve as resolve40 } from "node:path";
72872
- import { homedir as homedir32 } from "node:os";
73153
+ import { execFileSync as execFileSync17 } from "node:child_process";
73154
+ import { closeSync as closeSync12, mkdirSync as mkdirSync33, openSync as openSync12, existsSync as existsSync62, unlinkSync as unlinkSync13 } from "node:fs";
73155
+ import { join as join57, resolve as resolve40 } from "node:path";
73156
+ import { homedir as homedir33 } from "node:os";
72873
73157
  import { randomBytes as randomBytes12 } from "node:crypto";
72874
73158
 
72875
73159
  // src/worktree/registry.ts
@@ -72879,16 +73163,16 @@ import {
72879
73163
  readFileSync as readFileSync53,
72880
73164
  readdirSync as readdirSync22,
72881
73165
  unlinkSync as unlinkSync12,
72882
- existsSync as existsSync60,
73166
+ existsSync as existsSync61,
72883
73167
  renameSync as renameSync12
72884
73168
  } from "node:fs";
72885
- import { join as join55, resolve as resolve39 } from "node:path";
72886
- import { homedir as homedir31 } from "node:os";
73169
+ import { join as join56, resolve as resolve39 } from "node:path";
73170
+ import { homedir as homedir32 } from "node:os";
72887
73171
  function registryDir() {
72888
- return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ?? join55(homedir31(), ".switchroom", "worktrees"));
73172
+ return resolve39(process.env.SWITCHROOM_WORKTREE_DIR ?? join56(homedir32(), ".switchroom", "worktrees"));
72889
73173
  }
72890
73174
  function recordPath(id) {
72891
- return join55(registryDir(), `${id}.json`);
73175
+ return join56(registryDir(), `${id}.json`);
72892
73176
  }
72893
73177
  function ensureDir2() {
72894
73178
  mkdirSync32(registryDir(), { recursive: true });
@@ -72939,7 +73223,7 @@ function acquireRepoLock(repoPath) {
72939
73223
  const lockDir = registryDir();
72940
73224
  mkdirSync33(lockDir, { recursive: true });
72941
73225
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
72942
- const lockPath = join56(lockDir, `.lock-${lockName}`);
73226
+ const lockPath = join57(lockDir, `.lock-${lockName}`);
72943
73227
  const deadline = Date.now() + 5000;
72944
73228
  let fd = null;
72945
73229
  while (fd === null) {
@@ -72966,7 +73250,7 @@ function acquireRepoLock(repoPath) {
72966
73250
  }
72967
73251
  var DEFAULT_CONCURRENCY = 5;
72968
73252
  function worktreesBaseDir() {
72969
- return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ?? join56(homedir32(), ".switchroom", "worktree-checkouts"));
73253
+ return resolve40(process.env.SWITCHROOM_WORKTREE_BASE ?? join57(homedir33(), ".switchroom", "worktree-checkouts"));
72970
73254
  }
72971
73255
  function shortId() {
72972
73256
  return randomBytes12(4).toString("hex");
@@ -72988,12 +73272,12 @@ function resolveRepoPath(repo, codeRepos) {
72988
73272
  }
72989
73273
  function expandHome(p) {
72990
73274
  if (p.startsWith("~/"))
72991
- return join56(homedir32(), p.slice(2));
73275
+ return join57(homedir33(), p.slice(2));
72992
73276
  return p;
72993
73277
  }
72994
73278
  async function claimWorktree(input, codeRepos) {
72995
73279
  const repoPath = resolveRepoPath(input.repo, codeRepos);
72996
- if (!existsSync61(repoPath)) {
73280
+ if (!existsSync62(repoPath)) {
72997
73281
  throw new Error(`Repository path does not exist: ${repoPath}`);
72998
73282
  }
72999
73283
  let concurrencyCap = DEFAULT_CONCURRENCY;
@@ -73016,7 +73300,7 @@ async function claimWorktree(input, codeRepos) {
73016
73300
  branch = `task/${taskSuffix}-${id}`;
73017
73301
  const baseDir = worktreesBaseDir();
73018
73302
  mkdirSync33(baseDir, { recursive: true });
73019
- worktreePath = join56(baseDir, `${id}-${taskSuffix}`);
73303
+ worktreePath = join57(baseDir, `${id}-${taskSuffix}`);
73020
73304
  const now = new Date().toISOString();
73021
73305
  const record2 = {
73022
73306
  id,
@@ -73033,7 +73317,7 @@ async function claimWorktree(input, codeRepos) {
73033
73317
  releaseLock();
73034
73318
  }
73035
73319
  try {
73036
- execFileSync16("git", ["worktree", "add", "-b", branch, worktreePath], {
73320
+ execFileSync17("git", ["worktree", "add", "-b", branch, worktreePath], {
73037
73321
  cwd: repoPath,
73038
73322
  stdio: "pipe"
73039
73323
  });
@@ -73046,8 +73330,8 @@ async function claimWorktree(input, codeRepos) {
73046
73330
  }
73047
73331
 
73048
73332
  // src/worktree/release.ts
73049
- import { execFileSync as execFileSync17 } from "node:child_process";
73050
- import { existsSync as existsSync62 } from "node:fs";
73333
+ import { execFileSync as execFileSync18 } from "node:child_process";
73334
+ import { existsSync as existsSync63 } from "node:fs";
73051
73335
  function releaseWorktree(input) {
73052
73336
  const { id } = input;
73053
73337
  const record2 = readRecord(id);
@@ -73055,9 +73339,9 @@ function releaseWorktree(input) {
73055
73339
  return { released: true };
73056
73340
  }
73057
73341
  let gitSuccess = true;
73058
- if (existsSync62(record2.path)) {
73342
+ if (existsSync63(record2.path)) {
73059
73343
  try {
73060
- execFileSync17("git", ["worktree", "remove", "--force", record2.path], {
73344
+ execFileSync18("git", ["worktree", "remove", "--force", record2.path], {
73061
73345
  cwd: record2.repo,
73062
73346
  stdio: "pipe"
73063
73347
  });
@@ -73093,16 +73377,16 @@ function listWorktrees() {
73093
73377
  }
73094
73378
 
73095
73379
  // src/worktree/reaper.ts
73096
- import { execFileSync as execFileSync18 } from "node:child_process";
73097
- import { existsSync as existsSync63 } from "node:fs";
73380
+ import { execFileSync as execFileSync19 } from "node:child_process";
73381
+ import { existsSync as existsSync64 } from "node:fs";
73098
73382
  var STALE_THRESHOLD_MS = 10 * 60 * 1000;
73099
73383
  function isPathInUse(path7) {
73100
73384
  try {
73101
- execFileSync18("fuser", [path7], { stdio: "pipe" });
73385
+ execFileSync19("fuser", [path7], { stdio: "pipe" });
73102
73386
  return true;
73103
73387
  } catch {}
73104
73388
  try {
73105
- const out = execFileSync18("lsof", ["-t", path7], {
73389
+ const out = execFileSync19("lsof", ["-t", path7], {
73106
73390
  stdio: ["ignore", "pipe", "ignore"]
73107
73391
  }).toString().trim();
73108
73392
  if (out.length > 0)
@@ -73112,7 +73396,7 @@ function isPathInUse(path7) {
73112
73396
  }
73113
73397
  function hasUncommittedChanges(repoPath, worktreePath) {
73114
73398
  try {
73115
- const out = execFileSync18("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" }).toString();
73399
+ const out = execFileSync19("git", ["-C", worktreePath, "status", "--porcelain"], { stdio: "pipe" }).toString();
73116
73400
  return out.trim().length > 0;
73117
73401
  } catch {
73118
73402
  return false;
@@ -73121,12 +73405,12 @@ function hasUncommittedChanges(repoPath, worktreePath) {
73121
73405
  function reapRecord(record2) {
73122
73406
  const { id, path: path7, repo, branch, ownerAgent } = record2;
73123
73407
  let warning = null;
73124
- if (existsSync63(path7)) {
73408
+ if (existsSync64(path7)) {
73125
73409
  if (hasUncommittedChanges(repo, path7)) {
73126
73410
  warning = `[worktree-reaper] Reaped worktree with uncommitted changes: ` + `id=${id} branch=${branch} agent=${ownerAgent ?? "unknown"} path=${path7}`;
73127
73411
  }
73128
73412
  try {
73129
- execFileSync18("git", ["worktree", "remove", "--force", path7], {
73413
+ execFileSync19("git", ["worktree", "remove", "--force", path7], {
73130
73414
  cwd: repo,
73131
73415
  stdio: "pipe"
73132
73416
  });
@@ -73142,7 +73426,7 @@ function runReaper(nowMs) {
73142
73426
  const warnings = [];
73143
73427
  for (const record2 of records) {
73144
73428
  const heartbeatAge = now - new Date(record2.heartbeatAt).getTime();
73145
- const worktreeExists = existsSync63(record2.path);
73429
+ const worktreeExists = existsSync64(record2.path);
73146
73430
  if (!worktreeExists) {
73147
73431
  deleteRecord(record2.id);
73148
73432
  reaped.push(record2.id);
@@ -73271,7 +73555,7 @@ import {
73271
73555
  rmSync as rmSync15,
73272
73556
  writeFileSync as writeFileSync30
73273
73557
  } from "node:fs";
73274
- import { join as join57 } from "node:path";
73558
+ import { join as join58 } from "node:path";
73275
73559
  function encodeCredentialsFilename(email) {
73276
73560
  const SAFE = new Set([
73277
73561
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -73461,16 +73745,16 @@ function resolveCredentialsDir(env2) {
73461
73745
  if (explicit && explicit.length > 0)
73462
73746
  return explicit;
73463
73747
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
73464
- return join57(stateBase, "google-workspace-mcp", "credentials");
73748
+ return join58(stateBase, "google-workspace-mcp", "credentials");
73465
73749
  }
73466
73750
  function writeSeedFile(dir, email, seed) {
73467
73751
  mkdirSync34(dir, { recursive: true, mode: 448 });
73468
73752
  chmodSync9(dir, 448);
73469
73753
  for (const name of readdirSync23(dir)) {
73470
- rmSync15(join57(dir, name), { force: true, recursive: true });
73754
+ rmSync15(join58(dir, name), { force: true, recursive: true });
73471
73755
  }
73472
73756
  const filename = encodeCredentialsFilename(email);
73473
- const filePath = join57(dir, filename);
73757
+ const filePath = join58(dir, filename);
73474
73758
  writeFileSync30(filePath, JSON.stringify(seed), { mode: 384 });
73475
73759
  chmodSync9(filePath, 384);
73476
73760
  return filePath;
@@ -73628,7 +73912,7 @@ function registerDriveMcpLauncherCommand(program3) {
73628
73912
 
73629
73913
  // src/cli/apply.ts
73630
73914
  init_source();
73631
- import { accessSync as accessSync3, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync67, mkdirSync as mkdirSync36, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync32 } from "node:fs";
73915
+ import { accessSync as accessSync3, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync68, mkdirSync as mkdirSync36, readdirSync as readdirSync25, renameSync as renameSync13, writeFileSync as writeFileSync32 } from "node:fs";
73632
73916
  import { mkdir, writeFile } from "node:fs/promises";
73633
73917
  import { spawnSync as childSpawnSync } from "node:child_process";
73634
73918
  import readline from "node:readline";
@@ -73991,16 +74275,16 @@ agents:
73991
74275
 
73992
74276
  // src/cli/apply.ts
73993
74277
  init_resolver();
73994
- import { dirname as dirname19, join as join61, resolve as resolve42 } from "node:path";
73995
- import { homedir as homedir34 } from "node:os";
73996
- import { execFileSync as execFileSync19 } from "node:child_process";
74278
+ import { dirname as dirname19, join as join62, resolve as resolve42 } from "node:path";
74279
+ import { homedir as homedir35 } from "node:os";
74280
+ import { execFileSync as execFileSync20 } from "node:child_process";
73997
74281
  init_vault();
73998
74282
  init_loader();
73999
74283
  init_loader();
74000
74284
 
74001
74285
  // src/cli/update-prompt-hook.ts
74002
- import { existsSync as existsSync64, readFileSync as readFileSync54, writeFileSync as writeFileSync31, chmodSync as chmodSync10, mkdirSync as mkdirSync35 } from "node:fs";
74003
- import { join as join58 } from "node:path";
74286
+ import { existsSync as existsSync65, readFileSync as readFileSync54, writeFileSync as writeFileSync31, chmodSync as chmodSync10, mkdirSync as mkdirSync35 } from "node:fs";
74287
+ import { join as join59 } from "node:path";
74004
74288
  var HOOK_FILENAME = "update-card-on-prompt.sh";
74005
74289
  function updatePromptHookScript() {
74006
74290
  return `#!/bin/bash
@@ -74066,12 +74350,12 @@ exit 0
74066
74350
  `;
74067
74351
  }
74068
74352
  function installUpdatePromptHook(agentDir) {
74069
- const hooksDir = join58(agentDir, ".claude", "hooks");
74353
+ const hooksDir = join59(agentDir, ".claude", "hooks");
74070
74354
  mkdirSync35(hooksDir, { recursive: true });
74071
- const scriptPath = join58(hooksDir, HOOK_FILENAME);
74355
+ const scriptPath = join59(hooksDir, HOOK_FILENAME);
74072
74356
  const desired = updatePromptHookScript();
74073
74357
  let installed = false;
74074
- const existing = existsSync64(scriptPath) ? readFileSync54(scriptPath, "utf-8") : "";
74358
+ const existing = existsSync65(scriptPath) ? readFileSync54(scriptPath, "utf-8") : "";
74075
74359
  if (existing !== desired) {
74076
74360
  writeFileSync31(scriptPath, desired, { mode: 493 });
74077
74361
  chmodSync10(scriptPath, 493);
@@ -74081,8 +74365,8 @@ function installUpdatePromptHook(agentDir) {
74081
74365
  chmodSync10(scriptPath, 493);
74082
74366
  } catch {}
74083
74367
  }
74084
- const settingsPath = join58(agentDir, ".claude", "settings.json");
74085
- if (!existsSync64(settingsPath)) {
74368
+ const settingsPath = join59(agentDir, ".claude", "settings.json");
74369
+ if (!existsSync65(settingsPath)) {
74086
74370
  return { scriptPath, settingsPath, installed };
74087
74371
  }
74088
74372
  const raw = readFileSync54(settingsPath, "utf-8");
@@ -74201,13 +74485,13 @@ function detectInstallType() {
74201
74485
  // src/cli/operator-uid.ts
74202
74486
  import {
74203
74487
  chownSync as chownSync3,
74204
- existsSync as existsSync66,
74488
+ existsSync as existsSync67,
74205
74489
  lstatSync as lstatSync7,
74206
74490
  readdirSync as readdirSync24,
74207
74491
  realpathSync as realpathSync6,
74208
- statSync as statSync26
74492
+ statSync as statSync27
74209
74493
  } from "node:fs";
74210
- import { join as join60 } from "node:path";
74494
+ import { join as join61 } from "node:path";
74211
74495
  function resolveOperatorUid() {
74212
74496
  const sudoUid = process.env.SUDO_UID;
74213
74497
  if (sudoUid !== undefined) {
@@ -74223,19 +74507,19 @@ function resolveOperatorUid() {
74223
74507
  return;
74224
74508
  }
74225
74509
  function operatorOwnedPaths(home2) {
74226
- const root = join60(home2, ".switchroom");
74510
+ const root = join61(home2, ".switchroom");
74227
74511
  return [
74228
- join60(root, "vault"),
74229
- join60(root, "vault-auto-unlock"),
74230
- join60(root, "vault-audit.log"),
74231
- join60(root, "host-control-audit.log"),
74232
- join60(root, "accounts"),
74233
- join60(root, "compose")
74512
+ join61(root, "vault"),
74513
+ join61(root, "vault-auto-unlock"),
74514
+ join61(root, "vault-audit.log"),
74515
+ join61(root, "host-control-audit.log"),
74516
+ join61(root, "accounts"),
74517
+ join61(root, "compose")
74234
74518
  ];
74235
74519
  }
74236
74520
  function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
74237
74521
  const chown = deps.chown ?? ((p, u, g) => chownSync3(p, u, g));
74238
- const exists = deps.exists ?? ((p) => existsSync66(p));
74522
+ const exists = deps.exists ?? ((p) => existsSync67(p));
74239
74523
  const isSymlink = deps.isSymlink ?? ((p) => {
74240
74524
  try {
74241
74525
  return lstatSync7(p).isSymbolicLink();
@@ -74245,7 +74529,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
74245
74529
  });
74246
74530
  const isDir = deps.isDir ?? ((p) => {
74247
74531
  try {
74248
- return statSync26(p).isDirectory();
74532
+ return statSync27(p).isDirectory();
74249
74533
  } catch {
74250
74534
  return false;
74251
74535
  }
@@ -74279,7 +74563,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
74279
74563
  } catch {}
74280
74564
  if (isDir(target)) {
74281
74565
  for (const entry of readdir2(target)) {
74282
- visit(join60(target, entry));
74566
+ visit(join61(target, entry));
74283
74567
  }
74284
74568
  }
74285
74569
  };
@@ -74293,17 +74577,17 @@ var EMBEDDED_EXAMPLES = {
74293
74577
  switchroom: switchroom_default,
74294
74578
  minimal: minimal_default
74295
74579
  };
74296
- var DEFAULT_COMPOSE_PATH2 = join61(homedir34(), ".switchroom", "compose", "docker-compose.yml");
74580
+ var DEFAULT_COMPOSE_PATH2 = join62(homedir35(), ".switchroom", "compose", "docker-compose.yml");
74297
74581
  var COMPOSE_PROJECT2 = "switchroom";
74298
74582
  function resolveVaultBindMountDir(homeDir, ctx) {
74299
74583
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
74300
74584
  if (isCustomPath && ctx.customVaultPath) {
74301
74585
  return dirname19(ctx.customVaultPath);
74302
74586
  }
74303
- return join61(homeDir, ".switchroom", "vault");
74587
+ return join62(homeDir, ".switchroom", "vault");
74304
74588
  }
74305
74589
  function inspectVaultBindMountDir(vaultDir) {
74306
- if (!existsSync67(vaultDir))
74590
+ if (!existsSync68(vaultDir))
74307
74591
  return { kind: "missing" };
74308
74592
  const entries = readdirSync25(vaultDir);
74309
74593
  const unknown = [];
@@ -74329,42 +74613,42 @@ function hasVaultRefs(value) {
74329
74613
  return false;
74330
74614
  }
74331
74615
  async function ensureHostMountSources(config) {
74332
- const home2 = homedir34();
74616
+ const home2 = homedir35();
74333
74617
  const dirs = [
74334
- join61(home2, ".switchroom", "approvals"),
74335
- join61(home2, ".switchroom", "scheduler"),
74336
- join61(home2, ".switchroom", "logs"),
74337
- join61(home2, ".switchroom", "compose"),
74338
- join61(home2, ".switchroom", "broker-operator")
74618
+ join62(home2, ".switchroom", "approvals"),
74619
+ join62(home2, ".switchroom", "scheduler"),
74620
+ join62(home2, ".switchroom", "logs"),
74621
+ join62(home2, ".switchroom", "compose"),
74622
+ join62(home2, ".switchroom", "broker-operator")
74339
74623
  ];
74340
74624
  for (const name of Object.keys(config.agents)) {
74341
- dirs.push(join61(home2, ".switchroom", "agents", name));
74342
- dirs.push(join61(home2, ".switchroom", "logs", name));
74343
- dirs.push(join61(home2, ".claude", "projects", name));
74625
+ dirs.push(join62(home2, ".switchroom", "agents", name));
74626
+ dirs.push(join62(home2, ".switchroom", "logs", name));
74627
+ dirs.push(join62(home2, ".claude", "projects", name));
74344
74628
  }
74345
74629
  for (const dir of dirs) {
74346
74630
  await mkdir(dir, { recursive: true });
74347
74631
  }
74348
- const autoUnlockPath = join61(home2, ".switchroom", "vault-auto-unlock");
74349
- if (!existsSync67(autoUnlockPath)) {
74632
+ const autoUnlockPath = join62(home2, ".switchroom", "vault-auto-unlock");
74633
+ if (!existsSync68(autoUnlockPath)) {
74350
74634
  writeFileSync32(autoUnlockPath, "", { mode: 384 });
74351
74635
  }
74352
- const auditLogPath = join61(home2, ".switchroom", "vault-audit.log");
74353
- if (!existsSync67(auditLogPath)) {
74636
+ const auditLogPath = join62(home2, ".switchroom", "vault-audit.log");
74637
+ if (!existsSync68(auditLogPath)) {
74354
74638
  writeFileSync32(auditLogPath, "", { mode: 420 });
74355
74639
  }
74356
- const grantsDbPath = join61(home2, ".switchroom", "vault-grants.db");
74357
- if (!existsSync67(grantsDbPath)) {
74640
+ const grantsDbPath = join62(home2, ".switchroom", "vault-grants.db");
74641
+ if (!existsSync68(grantsDbPath)) {
74358
74642
  writeFileSync32(grantsDbPath, "", { mode: 384 });
74359
74643
  }
74360
- const hostdAuditLogPath = join61(home2, ".switchroom", "host-control-audit.log");
74361
- if (!existsSync67(hostdAuditLogPath)) {
74644
+ const hostdAuditLogPath = join62(home2, ".switchroom", "host-control-audit.log");
74645
+ if (!existsSync68(hostdAuditLogPath)) {
74362
74646
  writeFileSync32(hostdAuditLogPath, "", { mode: 420 });
74363
74647
  }
74364
74648
  }
74365
74649
  function detectComposeV2() {
74366
74650
  try {
74367
- const out = execFileSync19("docker", ["compose", "version"], {
74651
+ const out = execFileSync20("docker", ["compose", "version"], {
74368
74652
  stdio: ["ignore", "pipe", "pipe"],
74369
74653
  encoding: "utf8"
74370
74654
  });
@@ -74379,7 +74663,7 @@ ${out.trim()}`;
74379
74663
  }
74380
74664
  function runApplyPreflight(config, opts = {}) {
74381
74665
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
74382
- if (hasVaultRefs(config) && !existsSync67(vaultPath)) {
74666
+ if (hasVaultRefs(config) && !existsSync68(vaultPath)) {
74383
74667
  throw new Error(`Config references vault keys (vault:<name>) but ${vaultPath} is missing. ` + `Run \`switchroom setup\` first to initialise the vault.`);
74384
74668
  }
74385
74669
  const detect = opts.detectComposeV2 ?? detectComposeV2;
@@ -74390,7 +74674,7 @@ function runApplyPreflight(config, opts = {}) {
74390
74674
  detectAndReportLegacyGdriveSlots(vaultPath);
74391
74675
  }
74392
74676
  function detectAndReportLegacyGdriveSlots(vaultPath) {
74393
- if (!existsSync67(vaultPath))
74677
+ if (!existsSync68(vaultPath))
74394
74678
  return;
74395
74679
  const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
74396
74680
  if (!passphrase)
@@ -74429,10 +74713,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
74429
74713
  `));
74430
74714
  }
74431
74715
  }
74432
- function writeInstallTypeCache(homeDir = homedir34()) {
74716
+ function writeInstallTypeCache(homeDir = homedir35()) {
74433
74717
  const ctx = detectInstallType();
74434
- const dir = join61(homeDir, ".switchroom");
74435
- const out = join61(dir, "install-type.json");
74718
+ const dir = join62(homeDir, ".switchroom");
74719
+ const out = join62(dir, "install-type.json");
74436
74720
  const tmp = `${out}.tmp`;
74437
74721
  mkdirSync36(dir, { recursive: true });
74438
74722
  const payload = {
@@ -74481,14 +74765,14 @@ Applying switchroom config...
74481
74765
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
74482
74766
  `));
74483
74767
  try {
74484
- installUpdatePromptHook(join61(agentsDir, name));
74768
+ installUpdatePromptHook(join62(agentsDir, name));
74485
74769
  } catch (hookErr) {
74486
74770
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
74487
74771
  `));
74488
74772
  }
74489
74773
  try {
74490
74774
  const uid = allocateAgentUid(name);
74491
- alignAgentUid(name, join61(agentsDir, name), uid, {
74775
+ alignAgentUid(name, join62(agentsDir, name), uid, {
74492
74776
  confirm: !options.nonInteractive,
74493
74777
  writeOut
74494
74778
  });
@@ -74525,7 +74809,7 @@ Applying switchroom config...
74525
74809
  for (const name of agentNames) {
74526
74810
  try {
74527
74811
  const uid = allocateAgentUid(name);
74528
- alignAgentUid(name, join61(agentsDir, name), uid, {
74812
+ alignAgentUid(name, join62(agentsDir, name), uid, {
74529
74813
  confirm: !options.nonInteractive,
74530
74814
  writeOut
74531
74815
  });
@@ -74539,7 +74823,7 @@ Applying switchroom config...
74539
74823
  }
74540
74824
  const vaultPathConfigured = config.vault?.path;
74541
74825
  const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
74542
- const migrationResult = migrateVaultLayout(homedir34(), {
74826
+ const migrationResult = migrateVaultLayout(homedir35(), {
74543
74827
  customVaultPath
74544
74828
  });
74545
74829
  switch (migrationResult.kind) {
@@ -74565,7 +74849,7 @@ Applying switchroom config...
74565
74849
  writeErr(formatDivergentRecoveryMessage(migrationResult.details));
74566
74850
  process.exit(4);
74567
74851
  }
74568
- const postMigrationInspect = inspectVaultLayout(homedir34());
74852
+ const postMigrationInspect = inspectVaultLayout(homedir35());
74569
74853
  const acceptable = [
74570
74854
  "no-vault",
74571
74855
  "already-migrated",
@@ -74580,7 +74864,7 @@ Applying switchroom config...
74580
74864
  `));
74581
74865
  process.exit(5);
74582
74866
  }
74583
- const vaultDir = resolveVaultBindMountDir(homedir34(), {
74867
+ const vaultDir = resolveVaultBindMountDir(homedir35(), {
74584
74868
  migrationKind: migrationResult.kind,
74585
74869
  customVaultPath
74586
74870
  });
@@ -74610,7 +74894,7 @@ Applying switchroom config...
74610
74894
  imageTag: composeImageTag,
74611
74895
  buildMode: options.buildLocal ? "local" : "pull",
74612
74896
  buildContext: options.buildContext,
74613
- homeDir: homedir34(),
74897
+ homeDir: homedir35(),
74614
74898
  switchroomConfigPath,
74615
74899
  operatorUid
74616
74900
  });
@@ -74630,7 +74914,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
74630
74914
  writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
74631
74915
  `));
74632
74916
  if (process.geteuid?.() === 0 && operatorUid !== undefined) {
74633
- const restored = restoreOperatorOwnership(homedir34(), operatorUid);
74917
+ const restored = restoreOperatorOwnership(homedir35(), operatorUid);
74634
74918
  if (restored.length > 0) {
74635
74919
  writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
74636
74920
  `));
@@ -74678,7 +74962,7 @@ function copyExampleConfig2(name) {
74678
74962
  throw new Error(`Invalid example name: ${name} (must match /^[a-z0-9_-]+$/)`);
74679
74963
  }
74680
74964
  const dest = resolve42(process.cwd(), "switchroom.yaml");
74681
- if (existsSync67(dest)) {
74965
+ if (existsSync68(dest)) {
74682
74966
  console.error(source_default.yellow("switchroom.yaml already exists \u2014 skipping example copy"));
74683
74967
  return;
74684
74968
  }
@@ -74689,7 +74973,7 @@ function copyExampleConfig2(name) {
74689
74973
  return;
74690
74974
  }
74691
74975
  const exampleFile = resolve42(import.meta.dirname, `../../examples/${name}.yaml`);
74692
- if (!existsSync67(exampleFile)) {
74976
+ if (!existsSync68(exampleFile)) {
74693
74977
  throw new Error(`Example config not found: ${name}.yaml (available: ${Object.keys(EMBEDDED_EXAMPLES).join(", ")})`);
74694
74978
  }
74695
74979
  copyFileSync11(exampleFile, dest);
@@ -74700,8 +74984,8 @@ function findUnwritableAgentDirs(config, opts) {
74700
74984
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
74701
74985
  const unwritable = [];
74702
74986
  for (const name of targets) {
74703
- const startSh = join61(agentsDir, name, "start.sh");
74704
- if (!existsSync67(startSh))
74987
+ const startSh = join62(agentsDir, name, "start.sh");
74988
+ if (!existsSync68(startSh))
74705
74989
  continue;
74706
74990
  try {
74707
74991
  accessSync3(startSh, fsConstants6.W_OK);
@@ -74879,9 +75163,9 @@ function runRedactStdin() {
74879
75163
  }
74880
75164
 
74881
75165
  // src/cli/status-ask.ts
74882
- import { readFileSync as readFileSync55, existsSync as existsSync68, readdirSync as readdirSync26 } from "node:fs";
74883
- import { join as join62 } from "node:path";
74884
- import { homedir as homedir35 } from "node:os";
75166
+ import { readFileSync as readFileSync55, existsSync as existsSync69, readdirSync as readdirSync26 } from "node:fs";
75167
+ import { join as join63 } from "node:path";
75168
+ import { homedir as homedir36 } from "node:os";
74885
75169
 
74886
75170
  // src/status-ask/report.ts
74887
75171
  function parseJsonl(content) {
@@ -75202,7 +75486,7 @@ function runReport(opts) {
75202
75486
  function resolveSources(explicitPath) {
75203
75487
  if (explicitPath != null && explicitPath.trim() !== "") {
75204
75488
  const trimmed = explicitPath.trim();
75205
- if (!existsSync68(trimmed)) {
75489
+ if (!existsSync69(trimmed)) {
75206
75490
  process.stderr.write(`status-ask report: ${trimmed}: file not found
75207
75491
  `);
75208
75492
  process.exit(1);
@@ -75216,9 +75500,9 @@ function resolveSources(explicitPath) {
75216
75500
  const config = loadConfig();
75217
75501
  agentsDir = resolveAgentsDir(config);
75218
75502
  } catch {
75219
- agentsDir = join62(homedir35(), ".switchroom", "agents");
75503
+ agentsDir = join63(homedir36(), ".switchroom", "agents");
75220
75504
  }
75221
- if (!existsSync68(agentsDir))
75505
+ if (!existsSync69(agentsDir))
75222
75506
  return [];
75223
75507
  const sources = [];
75224
75508
  let entries;
@@ -75228,8 +75512,8 @@ function resolveSources(explicitPath) {
75228
75512
  return [];
75229
75513
  }
75230
75514
  for (const name of entries) {
75231
- const path8 = join62(agentsDir, name, "runtime-metrics.jsonl");
75232
- if (existsSync68(path8)) {
75515
+ const path8 = join63(agentsDir, name, "runtime-metrics.jsonl");
75516
+ if (existsSync69(path8)) {
75233
75517
  sources.push({ path: path8, agent: name });
75234
75518
  }
75235
75519
  }
@@ -75250,17 +75534,17 @@ function inferAgentFromPath(p) {
75250
75534
 
75251
75535
  // src/cli/agent-config.ts
75252
75536
  init_helpers();
75253
- import { join as join63 } from "node:path";
75254
- import { homedir as homedir36 } from "node:os";
75537
+ import { join as join64 } from "node:path";
75538
+ import { homedir as homedir37 } from "node:os";
75255
75539
  import {
75256
- existsSync as existsSync69,
75540
+ existsSync as existsSync70,
75257
75541
  mkdirSync as mkdirSync37,
75258
75542
  appendFileSync as appendFileSync4,
75259
75543
  readFileSync as readFileSync56
75260
75544
  } from "node:fs";
75261
- var AUDIT_ROOT = join63(homedir36(), ".switchroom", "audit");
75545
+ var AUDIT_ROOT = join64(homedir37(), ".switchroom", "audit");
75262
75546
  function auditPathFor(agent) {
75263
- return join63(AUDIT_ROOT, agent, "agent-config.jsonl");
75547
+ return join64(AUDIT_ROOT, agent, "agent-config.jsonl");
75264
75548
  }
75265
75549
  function appendAudit(agent, cmd, args, exit, opts = {}) {
75266
75550
  const row = {
@@ -75274,7 +75558,7 @@ function appendAudit(agent, cmd, args, exit, opts = {}) {
75274
75558
  const path8 = opts.auditPath ?? auditPathFor(agent);
75275
75559
  const dir = path8.slice(0, path8.lastIndexOf("/"));
75276
75560
  try {
75277
- if (!existsSync69(dir)) {
75561
+ if (!existsSync70(dir)) {
75278
75562
  mkdirSync37(dir, { recursive: true });
75279
75563
  }
75280
75564
  appendFileSync4(path8, JSON.stringify(row) + `
@@ -75286,7 +75570,7 @@ function isContainerContext(env2 = process.env, opts = {}) {
75286
75570
  return true;
75287
75571
  const probe2 = opts.dockerEnvPath ?? "/.dockerenv";
75288
75572
  try {
75289
- if (existsSync69(probe2))
75573
+ if (existsSync70(probe2))
75290
75574
  return true;
75291
75575
  } catch {}
75292
75576
  return false;
@@ -75347,7 +75631,7 @@ function getAgentSlice(config, agent) {
75347
75631
  }
75348
75632
  function readAuditTail(agent, limit, opts = {}) {
75349
75633
  const path8 = opts.auditPath ?? auditPathFor(agent);
75350
- if (!existsSync69(path8))
75634
+ if (!existsSync70(path8))
75351
75635
  return [];
75352
75636
  let raw;
75353
75637
  try {
@@ -75507,32 +75791,32 @@ var import_yaml14 = __toESM(require_dist(), 1);
75507
75791
  init_paths();
75508
75792
  import {
75509
75793
  closeSync as closeSync13,
75510
- existsSync as existsSync70,
75794
+ existsSync as existsSync71,
75511
75795
  fsyncSync as fsyncSync6,
75512
75796
  mkdirSync as mkdirSync38,
75513
75797
  openSync as openSync13,
75514
75798
  readdirSync as readdirSync27,
75515
75799
  readFileSync as readFileSync57,
75516
75800
  renameSync as renameSync14,
75517
- statSync as statSync27,
75801
+ statSync as statSync28,
75518
75802
  unlinkSync as unlinkSync14,
75519
75803
  writeSync as writeSync8
75520
75804
  } from "node:fs";
75521
- import { join as join64, resolve as resolve43 } from "node:path";
75805
+ import { join as join65, resolve as resolve43 } from "node:path";
75522
75806
  var STAGING_SUBDIR = ".staging";
75523
75807
  function overlayPathsFor(agent, opts = {}) {
75524
75808
  const base = opts.root ? resolve43(opts.root, agent) : resolve43(resolveDualPath(`~/.switchroom/agents/${agent}`));
75525
- const scheduleDir = join64(base, "schedule.d");
75526
- const scheduleStagingDir = join64(scheduleDir, STAGING_SUBDIR);
75527
- const skillsDir = join64(base, "skills.d");
75528
- const skillsStagingDir = join64(skillsDir, STAGING_SUBDIR);
75809
+ const scheduleDir = join65(base, "schedule.d");
75810
+ const scheduleStagingDir = join65(scheduleDir, STAGING_SUBDIR);
75811
+ const skillsDir = join65(base, "skills.d");
75812
+ const skillsStagingDir = join65(skillsDir, STAGING_SUBDIR);
75529
75813
  return {
75530
75814
  agentRoot: base,
75531
75815
  scheduleDir,
75532
75816
  scheduleStagingDir,
75533
75817
  skillsDir,
75534
75818
  skillsStagingDir,
75535
- lockPath: join64(base, ".lock"),
75819
+ lockPath: join65(base, ".lock"),
75536
75820
  stagingDir: scheduleStagingDir
75537
75821
  };
75538
75822
  }
@@ -75558,7 +75842,7 @@ function withAgentLock(paths, fn) {
75558
75842
  if (e.code !== "EEXIST")
75559
75843
  throw err;
75560
75844
  try {
75561
- const age = Date.now() - statSync27(paths.lockPath).mtimeMs;
75845
+ const age = Date.now() - statSync28(paths.lockPath).mtimeMs;
75562
75846
  if (age > 30000) {
75563
75847
  unlinkSync14(paths.lockPath);
75564
75848
  continue;
@@ -75586,8 +75870,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
75586
75870
  const paths = overlayPathsFor(agent, opts);
75587
75871
  return withAgentLock(paths, () => {
75588
75872
  ensureDirs(paths);
75589
- const stagingPath = join64(paths.scheduleStagingDir, `${slug}.yaml`);
75590
- const finalPath = join64(paths.scheduleDir, `${slug}.yaml`);
75873
+ const stagingPath = join65(paths.scheduleStagingDir, `${slug}.yaml`);
75874
+ const finalPath = join65(paths.scheduleDir, `${slug}.yaml`);
75591
75875
  const fd = openSync13(stagingPath, "w", 384);
75592
75876
  try {
75593
75877
  writeSync8(fd, yamlText);
@@ -75603,8 +75887,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
75603
75887
  const paths = overlayPathsFor(agent, opts);
75604
75888
  return withAgentLock(paths, () => {
75605
75889
  ensureSkillsDirs(paths);
75606
- const stagingPath = join64(paths.skillsStagingDir, `${slug}.yaml`);
75607
- const finalPath = join64(paths.skillsDir, `${slug}.yaml`);
75890
+ const stagingPath = join65(paths.skillsStagingDir, `${slug}.yaml`);
75891
+ const finalPath = join65(paths.skillsDir, `${slug}.yaml`);
75608
75892
  const fd = openSync13(stagingPath, "w", 384);
75609
75893
  try {
75610
75894
  writeSync8(fd, yamlText);
@@ -75619,8 +75903,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
75619
75903
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
75620
75904
  const paths = overlayPathsFor(agent, opts);
75621
75905
  return withAgentLock(paths, () => {
75622
- const finalPath = join64(paths.skillsDir, `${slug}.yaml`);
75623
- if (!existsSync70(finalPath))
75906
+ const finalPath = join65(paths.skillsDir, `${slug}.yaml`);
75907
+ if (!existsSync71(finalPath))
75624
75908
  return false;
75625
75909
  unlinkSync14(finalPath);
75626
75910
  return true;
@@ -75628,13 +75912,13 @@ function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
75628
75912
  }
75629
75913
  function listSkillsOverlayEntries(agent, opts = {}) {
75630
75914
  const paths = overlayPathsFor(agent, opts);
75631
- if (!existsSync70(paths.skillsDir))
75915
+ if (!existsSync71(paths.skillsDir))
75632
75916
  return [];
75633
75917
  const out = [];
75634
75918
  for (const name of readdirSync27(paths.skillsDir)) {
75635
75919
  if (!/\.ya?ml$/i.test(name))
75636
75920
  continue;
75637
- const full = join64(paths.skillsDir, name);
75921
+ const full = join65(paths.skillsDir, name);
75638
75922
  try {
75639
75923
  const raw = readFileSync57(full, "utf-8");
75640
75924
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -75646,8 +75930,8 @@ function listSkillsOverlayEntries(agent, opts = {}) {
75646
75930
  function deleteOverlayEntry(agent, slug, opts = {}) {
75647
75931
  const paths = overlayPathsFor(agent, opts);
75648
75932
  return withAgentLock(paths, () => {
75649
- const finalPath = join64(paths.scheduleDir, `${slug}.yaml`);
75650
- if (!existsSync70(finalPath))
75933
+ const finalPath = join65(paths.scheduleDir, `${slug}.yaml`);
75934
+ if (!existsSync71(finalPath))
75651
75935
  return false;
75652
75936
  unlinkSync14(finalPath);
75653
75937
  return true;
@@ -75655,13 +75939,13 @@ function deleteOverlayEntry(agent, slug, opts = {}) {
75655
75939
  }
75656
75940
  function listOverlayEntries(agent, opts = {}) {
75657
75941
  const paths = overlayPathsFor(agent, opts);
75658
- if (!existsSync70(paths.scheduleDir))
75942
+ if (!existsSync71(paths.scheduleDir))
75659
75943
  return [];
75660
75944
  const out = [];
75661
75945
  for (const name of readdirSync27(paths.scheduleDir)) {
75662
75946
  if (!/\.ya?ml$/i.test(name))
75663
75947
  continue;
75664
- const full = join64(paths.scheduleDir, name);
75948
+ const full = join65(paths.scheduleDir, name);
75665
75949
  try {
75666
75950
  const raw = readFileSync57(full, "utf-8");
75667
75951
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -75806,7 +76090,7 @@ function reconcileAgentCronOnly(agent) {
75806
76090
  // src/cli/agent-config-pending.ts
75807
76091
  import {
75808
76092
  closeSync as closeSync14,
75809
- existsSync as existsSync71,
76093
+ existsSync as existsSync72,
75810
76094
  fsyncSync as fsyncSync7,
75811
76095
  mkdirSync as mkdirSync39,
75812
76096
  openSync as openSync14,
@@ -75817,12 +76101,12 @@ import {
75817
76101
  writeFileSync as writeFileSync33,
75818
76102
  writeSync as writeSync9
75819
76103
  } from "node:fs";
75820
- import { join as join65 } from "node:path";
76104
+ import { join as join66 } from "node:path";
75821
76105
  import { randomBytes as randomBytes13 } from "node:crypto";
75822
76106
  var STAGE_ID_PREFIX = "cap_";
75823
76107
  function pendingDir(agent, opts = {}) {
75824
76108
  const paths = overlayPathsFor(agent, opts);
75825
- return join65(paths.scheduleDir, ".pending");
76109
+ return join66(paths.scheduleDir, ".pending");
75826
76110
  }
75827
76111
  function ensurePendingDir(agent, opts = {}) {
75828
76112
  const dir = pendingDir(agent, opts);
@@ -75835,8 +76119,8 @@ function newStageId() {
75835
76119
  function stagePendingScheduleEntry(opts) {
75836
76120
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
75837
76121
  const stageId = opts.stageId ?? newStageId();
75838
- const yamlPath = join65(dir, `${stageId}.yaml`);
75839
- const metaPath = join65(dir, `${stageId}.meta.json`);
76122
+ const yamlPath = join66(dir, `${stageId}.yaml`);
76123
+ const metaPath = join66(dir, `${stageId}.meta.json`);
75840
76124
  const meta = {
75841
76125
  v: 1,
75842
76126
  stage_id: stageId,
@@ -75863,16 +76147,16 @@ function stagePendingScheduleEntry(opts) {
75863
76147
  }
75864
76148
  function listPendingScheduleEntries(agent, opts = {}) {
75865
76149
  const dir = pendingDir(agent, opts);
75866
- if (!existsSync71(dir))
76150
+ if (!existsSync72(dir))
75867
76151
  return [];
75868
76152
  const out = [];
75869
76153
  for (const name of readdirSync28(dir).sort()) {
75870
76154
  if (!name.endsWith(".meta.json"))
75871
76155
  continue;
75872
76156
  const stageId = name.slice(0, -".meta.json".length);
75873
- const metaPath = join65(dir, name);
75874
- const yamlPath = join65(dir, `${stageId}.yaml`);
75875
- if (!existsSync71(yamlPath))
76157
+ const metaPath = join66(dir, name);
76158
+ const yamlPath = join66(dir, `${stageId}.yaml`);
76159
+ if (!existsSync72(yamlPath))
75876
76160
  continue;
75877
76161
  try {
75878
76162
  const meta = JSON.parse(readFileSync58(metaPath, "utf-8"));
@@ -75890,8 +76174,8 @@ function commitPendingScheduleEntry(opts) {
75890
76174
  return { committed: false, reason: "not_found" };
75891
76175
  const slug = match.meta.entry.name ?? match.stageId;
75892
76176
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
75893
- const finalPath = join65(paths.scheduleDir, `${slug}.yaml`);
75894
- if (existsSync71(finalPath)) {
76177
+ const finalPath = join66(paths.scheduleDir, `${slug}.yaml`);
76178
+ if (existsSync72(finalPath)) {
75895
76179
  return { committed: false, reason: "slug_collision" };
75896
76180
  }
75897
76181
  renameSync15(match.yamlPath, finalPath);
@@ -75913,7 +76197,7 @@ function denyPendingScheduleEntry(opts) {
75913
76197
  }
75914
76198
 
75915
76199
  // src/cli/agent-config-write.ts
75916
- import { existsSync as existsSync72, readFileSync as readFileSync59 } from "node:fs";
76200
+ import { existsSync as existsSync73, readFileSync as readFileSync59 } from "node:fs";
75917
76201
  var MAX_ENTRIES_PER_AGENT = 20;
75918
76202
  function checkOperatorContext(verb, env2 = process.env) {
75919
76203
  if (env2.SWITCHROOM_OPERATOR === "1")
@@ -76147,7 +76431,7 @@ function scheduleRemove(opts) {
76147
76431
  }
76148
76432
  let priorContent = null;
76149
76433
  try {
76150
- if (existsSync72(match.path))
76434
+ if (existsSync73(match.path))
76151
76435
  priorContent = readFileSync59(match.path, "utf-8");
76152
76436
  } catch {}
76153
76437
  deleteOverlayEntry(agent, match.slug, { root: opts.root });
@@ -76340,10 +76624,10 @@ function registerAgentConfigWriteCommands(program3) {
76340
76624
 
76341
76625
  // src/cli/agent-config-skill-write.ts
76342
76626
  var import_yaml15 = __toESM(require_dist(), 1);
76343
- import { existsSync as existsSync73 } from "node:fs";
76627
+ import { existsSync as existsSync74 } from "node:fs";
76344
76628
  init_reconcile_default_skills();
76345
76629
  var import_yaml16 = __toESM(require_dist(), 1);
76346
- import { join as join66 } from "node:path";
76630
+ import { join as join67 } from "node:path";
76347
76631
  var MAX_SKILLS_PER_AGENT = 20;
76348
76632
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
76349
76633
  function exitCodeFor2(code) {
@@ -76418,8 +76702,8 @@ function skillInstall(opts) {
76418
76702
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
76419
76703
  }
76420
76704
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
76421
- const skillPath = join66(poolDir, skillName);
76422
- if (!existsSync73(skillPath)) {
76705
+ const skillPath = join67(poolDir, skillName);
76706
+ if (!existsSync74(skillPath)) {
76423
76707
  return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
76424
76708
  }
76425
76709
  const yamlText = import_yaml15.stringify({ skills: [skillName] });
@@ -76603,23 +76887,23 @@ init_source();
76603
76887
  init_helpers();
76604
76888
  init_loader();
76605
76889
  import {
76606
- existsSync as existsSync75,
76890
+ existsSync as existsSync76,
76607
76891
  readdirSync as readdirSync29,
76608
76892
  readFileSync as readFileSync61,
76609
76893
  renameSync as renameSync16,
76610
- statSync as statSync28,
76894
+ statSync as statSync29,
76611
76895
  unlinkSync as unlinkSync16
76612
76896
  } from "node:fs";
76613
76897
  import { createHash as createHash13 } from "node:crypto";
76614
- import { join as join67 } from "node:path";
76898
+ import { join as join68 } from "node:path";
76615
76899
  function planCronUnitRenames(agentsDir, agents) {
76616
76900
  const plans = [];
76617
76901
  for (const [agentName, agentConfig] of Object.entries(agents)) {
76618
76902
  const schedule = agentConfig.schedule ?? [];
76619
76903
  if (schedule.length === 0)
76620
76904
  continue;
76621
- const telegramDir = join67(agentsDir, agentName, "telegram");
76622
- if (!existsSync75(telegramDir))
76905
+ const telegramDir = join68(agentsDir, agentName, "telegram");
76906
+ if (!existsSync76(telegramDir))
76623
76907
  continue;
76624
76908
  let entries;
76625
76909
  try {
@@ -76640,8 +76924,8 @@ function planCronUnitRenames(agentsDir, agents) {
76640
76924
  continue;
76641
76925
  plans.push({
76642
76926
  agent: agentName,
76643
- from: join67(telegramDir, file),
76644
- to: join67(telegramDir, canonical),
76927
+ from: join68(telegramDir, file),
76928
+ to: join68(telegramDir, canonical),
76645
76929
  scheduleIdx: idx,
76646
76930
  entry
76647
76931
  });
@@ -76653,7 +76937,7 @@ function sha256File2(path8) {
76653
76937
  return createHash13("sha256").update(readFileSync61(path8)).digest("hex");
76654
76938
  }
76655
76939
  function renamePair(from, to, opts = {}) {
76656
- if (existsSync75(to)) {
76940
+ if (existsSync76(to)) {
76657
76941
  let identical = false;
76658
76942
  try {
76659
76943
  identical = sha256File2(from) === sha256File2(to);
@@ -76746,7 +77030,7 @@ function registerMigrateCommand(program3) {
76746
77030
  const fromSidecar = p.from.replace(/\.sh$/, ".source");
76747
77031
  const toSidecar = p.to.replace(/\.sh$/, ".source");
76748
77032
  let sidecarStatus = null;
76749
- if (existsSync75(fromSidecar) && statSync28(fromSidecar).isFile()) {
77033
+ if (existsSync76(fromSidecar) && statSync29(fromSidecar).isFile()) {
76750
77034
  sidecarStatus = renamePair(fromSidecar, toSidecar);
76751
77035
  }
76752
77036
  switch (status.kind) {
@@ -76778,9 +77062,9 @@ function registerMigrateCommand(program3) {
76778
77062
  // src/cli/hostd.ts
76779
77063
  init_source();
76780
77064
  init_helpers();
76781
- import { existsSync as existsSync76, mkdirSync as mkdirSync40, readdirSync as readdirSync30, readFileSync as readFileSync62, writeFileSync as writeFileSync34, statSync as statSync29, copyFileSync as copyFileSync12 } from "node:fs";
76782
- import { homedir as homedir37 } from "node:os";
76783
- import { join as join68 } from "node:path";
77065
+ import { existsSync as existsSync77, mkdirSync as mkdirSync40, readdirSync as readdirSync30, readFileSync as readFileSync62, writeFileSync as writeFileSync34, statSync as statSync30, copyFileSync as copyFileSync12 } from "node:fs";
77066
+ import { homedir as homedir38 } from "node:os";
77067
+ import { join as join69 } from "node:path";
76784
77068
  import { spawnSync as spawnSync11 } from "node:child_process";
76785
77069
  init_audit_reader();
76786
77070
  var DEFAULT_IMAGE_TAG = "latest";
@@ -76871,14 +77155,14 @@ networks:
76871
77155
  `;
76872
77156
  }
76873
77157
  function hostdDir() {
76874
- return join68(homedir37(), ".switchroom", "hostd");
77158
+ return join69(homedir38(), ".switchroom", "hostd");
76875
77159
  }
76876
77160
  function hostdComposePath() {
76877
- return join68(hostdDir(), "docker-compose.yml");
77161
+ return join69(hostdDir(), "docker-compose.yml");
76878
77162
  }
76879
77163
  function backupExistingCompose() {
76880
77164
  const p = hostdComposePath();
76881
- if (!existsSync76(p))
77165
+ if (!existsSync77(p))
76882
77166
  return null;
76883
77167
  const ts = new Date().toISOString().replace(/[:.]/g, "-");
76884
77168
  const bak = `${p}.bak-${ts}`;
@@ -76913,7 +77197,7 @@ async function doInstall(opts, program3) {
76913
77197
  const composePath = hostdComposePath();
76914
77198
  mkdirSync40(dir, { recursive: true });
76915
77199
  const yaml = renderHostdComposeFile({
76916
- hostHome: homedir37(),
77200
+ hostHome: homedir38(),
76917
77201
  imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
76918
77202
  operatorUid: resolveOperatorUid()
76919
77203
  });
@@ -76955,7 +77239,7 @@ function doStatus() {
76955
77239
  const composeYml = hostdComposePath();
76956
77240
  console.log(source_default.bold("switchroom-hostd"));
76957
77241
  console.log("");
76958
- if (!existsSync76(composeYml)) {
77242
+ if (!existsSync77(composeYml)) {
76959
77243
  console.log(source_default.yellow(" compose: not installed"));
76960
77244
  console.log(source_default.dim(" run `switchroom hostd install` to set up."));
76961
77245
  return;
@@ -76976,15 +77260,15 @@ function doStatus() {
76976
77260
  } else {
76977
77261
  console.log(source_default.green(` container: ${ps.stdout.trim()}`));
76978
77262
  }
76979
- if (existsSync76(dir)) {
77263
+ if (existsSync77(dir)) {
76980
77264
  const entries = [];
76981
77265
  try {
76982
77266
  for (const name of readdirSync30(dir)) {
76983
77267
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
76984
77268
  continue;
76985
- const sockPath = join68(dir, name, "sock");
76986
- if (existsSync76(sockPath)) {
76987
- const st = statSync29(sockPath);
77269
+ const sockPath = join69(dir, name, "sock");
77270
+ if (existsSync77(sockPath)) {
77271
+ const st = statSync30(sockPath);
76988
77272
  if ((st.mode & 61440) === 49152) {
76989
77273
  entries.push(`${name} \u2192 ${sockPath}`);
76990
77274
  }
@@ -77002,7 +77286,7 @@ function doStatus() {
77002
77286
  }
77003
77287
  function doUninstall() {
77004
77288
  const composeYml = hostdComposePath();
77005
- if (!existsSync76(composeYml)) {
77289
+ if (!existsSync77(composeYml)) {
77006
77290
  console.log(source_default.yellow(" No hostd install detected (no compose file at this path)."));
77007
77291
  return;
77008
77292
  }
@@ -77026,7 +77310,7 @@ function registerHostdCommand(program3) {
77026
77310
  hostd.command("uninstall").description("Stop the hostd container. Leaves the compose file in place for re-install.").action(() => doUninstall());
77027
77311
  hostd.command("audit").description("Tail and filter the hostd audit log (privileged-verb call history)").option("--tail <n>", "Number of matching entries to show (default: 50)", "50").option("--agent <name>", "Filter to a specific caller agent").option("--op <verb>", "Filter to a specific hostd verb (e.g. update_apply, agent_restart)").option("--error", "Show only failed (error/denied) entries").option("--verbose", "Show the captured stderr / error tail under each failed row").option("--path <file>", "Override audit log path (for debugging)").action((opts) => {
77028
77312
  const logPath = opts.path ?? defaultAuditLogPath2();
77029
- if (!existsSync76(logPath)) {
77313
+ if (!existsSync77(logPath)) {
77030
77314
  console.error(source_default.yellow(`Audit log not found at ${logPath}.`) + source_default.gray(`
77031
77315
  The log is created when hostd handles its first privileged-verb request.`));
77032
77316
  return;