switchroom 0.12.13 → 0.12.15

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.
@@ -20645,18 +20645,6 @@ function getGdriveMcpSettingsEntry(switchroomCliPath, options = {}) {
20645
20645
  }
20646
20646
  };
20647
20647
  }
20648
- function shouldEmitGdriveMcp(agentName, agentGoogleAccount, googleAccounts) {
20649
- if (!agentGoogleAccount)
20650
- return false;
20651
- const account = agentGoogleAccount.trim().toLowerCase();
20652
- if (account.length === 0)
20653
- return false;
20654
- const acctEntry = googleAccounts?.[account];
20655
- if (!acctEntry)
20656
- return false;
20657
- const enabledFor = acctEntry.enabled_for ?? [];
20658
- return enabledFor.includes(agentName);
20659
- }
20660
20648
  function getBuiltinDefaultMcpEntries() {
20661
20649
  const playwright = getPlaywrightMcpSettingsEntry();
20662
20650
  return [
@@ -20689,6 +20677,48 @@ var init_scaffold_integration = __esm(() => {
20689
20677
  init_hindsight();
20690
20678
  });
20691
20679
 
20680
+ // src/config/google-workspace-acl.ts
20681
+ function shouldEmitGdriveMcp(agentName, agentGoogleAccount, googleAccounts) {
20682
+ if (!agentGoogleAccount)
20683
+ return false;
20684
+ const account = agentGoogleAccount.trim().toLowerCase();
20685
+ if (account.length === 0)
20686
+ return false;
20687
+ const acctEntry = googleAccounts?.[account];
20688
+ if (!acctEntry)
20689
+ return false;
20690
+ const enabledFor = acctEntry.enabled_for ?? [];
20691
+ return enabledFor.includes(agentName);
20692
+ }
20693
+ function vaultRefKey(value) {
20694
+ if (typeof value !== "string" || !value.startsWith("vault:"))
20695
+ return null;
20696
+ const key = value.slice("vault:".length).split("#")[0];
20697
+ return key.length > 0 ? key : null;
20698
+ }
20699
+ function isGoogleClientCredentialKeyForAgent(config, agentName, key) {
20700
+ if (!agentName || !key)
20701
+ return false;
20702
+ const agentConfig = config.agents?.[agentName];
20703
+ if (!agentConfig)
20704
+ return false;
20705
+ if (agentConfig.mcp_servers?.["gdrive"] === false) {
20706
+ return false;
20707
+ }
20708
+ const account = agentConfig.google_workspace?.account;
20709
+ if (!shouldEmitGdriveMcp(agentName, account, config.google_accounts)) {
20710
+ return false;
20711
+ }
20712
+ const gw = config.google_workspace;
20713
+ if (!gw)
20714
+ return false;
20715
+ for (const ref of [gw.google_client_id, gw.google_client_secret]) {
20716
+ if (vaultRefKey(ref) === key)
20717
+ return true;
20718
+ }
20719
+ return false;
20720
+ }
20721
+
20692
20722
  // src/agents/reconcile-default-skills.ts
20693
20723
  import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readlinkSync as readlinkSync2, rmSync, symlinkSync } from "node:fs";
20694
20724
  import { homedir as homedir2 } from "node:os";
@@ -21379,7 +21409,7 @@ function stripWireFields(entry) {
21379
21409
  files: entry.files
21380
21410
  };
21381
21411
  }
21382
- var MAX_FRAME_BYTES, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, RequestSchema, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, ErrorResponseSchema, ResponseSchema;
21412
+ var MAX_FRAME_BYTES, GetRequestSchema, PutRequestSchema, ListRequestSchema, MintGrantRequestSchema, ListGrantsRequestSchema, RevokeGrantRequestSchema, StatusRequestSchema, LockRequestSchema, PreflightAccessRequestSchema, OkPreflightAccessResponseSchema, ApprovalRequestRequestSchema, ApprovalLookupRequestSchema, ApprovalConsumeRequestSchema, ApprovalRevokeRequestSchema, ApprovalListRequestSchema, ApprovalDecisionModeSchema, ApprovalRecordRequestSchema, ApprovalConsumeRecordRequestSchema, RequestSchema, VaultEntrySchema, ErrorCode, OkEntryResponseSchema, OkKeysResponseSchema, BrokerStatus, OkStatusResponseSchema, OkLockResponseSchema, OkPutResponseSchema, OkMintGrantResponseSchema, GrantMetaSchema, OkListGrantsResponseSchema, OkRevokeGrantResponseSchema, OkApprovalRequestResponseSchema, ApprovalDecisionMetaSchema, OkApprovalLookupResponseSchema, OkApprovalConsumeResponseSchema, OkApprovalRevokeResponseSchema, OkApprovalListResponseSchema, OkApprovalRecordResponseSchema, OkApprovalConsumeRecordResponseSchema, ErrorResponseSchema, ResponseSchema;
21383
21413
  var init_protocol = __esm(() => {
21384
21414
  init_zod();
21385
21415
  MAX_FRAME_BYTES = 64 * 1024;
@@ -21438,6 +21468,24 @@ var init_protocol = __esm(() => {
21438
21468
  v: exports_external.literal(1),
21439
21469
  op: exports_external.literal("lock")
21440
21470
  });
21471
+ PreflightAccessRequestSchema = exports_external.object({
21472
+ v: exports_external.literal(1),
21473
+ op: exports_external.literal("preflight_access"),
21474
+ agent: exports_external.string().min(1),
21475
+ keys: exports_external.array(exports_external.string().min(1)).min(1).max(128)
21476
+ });
21477
+ OkPreflightAccessResponseSchema = exports_external.object({
21478
+ ok: exports_external.literal(true),
21479
+ op: exports_external.literal("preflight_access"),
21480
+ results: exports_external.array(exports_external.object({
21481
+ key: exports_external.string(),
21482
+ exists: exports_external.boolean(),
21483
+ acl_ok: exports_external.boolean(),
21484
+ acl_reason: exports_external.string().optional(),
21485
+ scope_ok: exports_external.boolean(),
21486
+ scope_reason: exports_external.string().optional()
21487
+ }))
21488
+ });
21441
21489
  ApprovalRequestRequestSchema = exports_external.object({
21442
21490
  v: exports_external.literal(1),
21443
21491
  op: exports_external.literal("approval_request"),
@@ -21489,12 +21537,22 @@ var init_protocol = __esm(() => {
21489
21537
  granted_by_user_id: exports_external.number().int(),
21490
21538
  ttl_ms: exports_external.number().int().positive().nullable().optional()
21491
21539
  });
21540
+ ApprovalConsumeRecordRequestSchema = exports_external.object({
21541
+ v: exports_external.literal(1),
21542
+ op: exports_external.literal("approval_consume_record"),
21543
+ request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
21544
+ decision: ApprovalDecisionModeSchema,
21545
+ approver_set: exports_external.array(exports_external.string()),
21546
+ granted_by_user_id: exports_external.number().int(),
21547
+ ttl_ms: exports_external.number().int().positive().nullable().optional()
21548
+ });
21492
21549
  RequestSchema = exports_external.discriminatedUnion("op", [
21493
21550
  GetRequestSchema,
21494
21551
  PutRequestSchema,
21495
21552
  ListRequestSchema,
21496
21553
  StatusRequestSchema,
21497
21554
  LockRequestSchema,
21555
+ PreflightAccessRequestSchema,
21498
21556
  MintGrantRequestSchema,
21499
21557
  ListGrantsRequestSchema,
21500
21558
  RevokeGrantRequestSchema,
@@ -21503,7 +21561,8 @@ var init_protocol = __esm(() => {
21503
21561
  ApprovalConsumeRequestSchema,
21504
21562
  ApprovalRevokeRequestSchema,
21505
21563
  ApprovalListRequestSchema,
21506
- ApprovalRecordRequestSchema
21564
+ ApprovalRecordRequestSchema,
21565
+ ApprovalConsumeRecordRequestSchema
21507
21566
  ]);
21508
21567
  VaultEntrySchema = exports_external.union([
21509
21568
  exports_external.object({ kind: exports_external.literal("string"), value: exports_external.string() }),
@@ -21625,6 +21684,15 @@ var init_protocol = __esm(() => {
21625
21684
  ok: exports_external.literal(true),
21626
21685
  decision_id: exports_external.string()
21627
21686
  });
21687
+ OkApprovalConsumeRecordResponseSchema = exports_external.object({
21688
+ ok: exports_external.literal(true),
21689
+ consumed: exports_external.boolean(),
21690
+ decision_id: exports_external.string().optional(),
21691
+ agent_unit: exports_external.string().optional(),
21692
+ scope: exports_external.string().optional(),
21693
+ action: exports_external.string().optional(),
21694
+ why: exports_external.string().nullable().optional()
21695
+ });
21628
21696
  ErrorResponseSchema = exports_external.object({
21629
21697
  ok: exports_external.literal(false),
21630
21698
  code: ErrorCode,
@@ -21635,6 +21703,7 @@ var init_protocol = __esm(() => {
21635
21703
  OkKeysResponseSchema,
21636
21704
  OkStatusResponseSchema,
21637
21705
  OkLockResponseSchema,
21706
+ OkPreflightAccessResponseSchema,
21638
21707
  OkPutResponseSchema,
21639
21708
  OkMintGrantResponseSchema,
21640
21709
  OkListGrantsResponseSchema,
@@ -21645,6 +21714,7 @@ var init_protocol = __esm(() => {
21645
21714
  OkApprovalRevokeResponseSchema,
21646
21715
  OkApprovalListResponseSchema,
21647
21716
  OkApprovalRecordResponseSchema,
21717
+ OkApprovalConsumeRecordResponseSchema,
21648
21718
  ErrorResponseSchema
21649
21719
  ]);
21650
21720
  });
@@ -27169,6 +27239,9 @@ function checkAclByAgent(config, agentName, key) {
27169
27239
  if (googleSlot !== null) {
27170
27240
  return checkGoogleAccountAcl(config, agentName, googleSlot.account, key);
27171
27241
  }
27242
+ if (isGoogleClientCredentialKeyForAgent(config, agentName, key)) {
27243
+ return { allow: true };
27244
+ }
27172
27245
  const agentBot = agentConfig.bot_token;
27173
27246
  const botRef = agentBot && agentBot.length > 0 ? agentBot : config.telegram?.bot_token;
27174
27247
  if (typeof botRef === "string" && botRef.startsWith("vault:")) {
@@ -27226,6 +27299,7 @@ function checkGoogleAccountAcl(config, agentName, account, key) {
27226
27299
  }
27227
27300
  return { allow: true };
27228
27301
  }
27302
+ var init_acl = () => {};
27229
27303
 
27230
27304
  // src/util/audit-hashchain.ts
27231
27305
  import { createHash as createHash6 } from "node:crypto";
@@ -28228,12 +28302,240 @@ function runHostdChecks(config, deps = {}) {
28228
28302
  var HOSTD_CONTAINER = "switchroom-hostd", HOSTD_DRIFT_HOURS = 2;
28229
28303
  var init_doctor_hostd = () => {};
28230
28304
 
28305
+ // src/cli/doctor-secret-access.ts
28306
+ import {
28307
+ accessSync,
28308
+ constants as fsConstants4,
28309
+ existsSync as existsSync46,
28310
+ realpathSync as realpathSync4,
28311
+ statSync as statSync20
28312
+ } from "node:fs";
28313
+ import { userInfo, homedir as homedir20 } from "node:os";
28314
+ import { join as join38 } from "node:path";
28315
+ function resolveVaultPath2(config) {
28316
+ return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
28317
+ }
28318
+ function defaultStatVault(path4) {
28319
+ if (!existsSync46(path4)) {
28320
+ return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
28321
+ }
28322
+ let real = path4;
28323
+ try {
28324
+ real = realpathSync4(path4);
28325
+ } catch {}
28326
+ let uid = -1;
28327
+ let mode = 0;
28328
+ try {
28329
+ const s = statSync20(real);
28330
+ uid = s.uid;
28331
+ mode = s.mode & 511;
28332
+ } catch {
28333
+ return { exists: true, readable: false, uid, mode, realPath: real };
28334
+ }
28335
+ let readable = false;
28336
+ try {
28337
+ accessSync(real, fsConstants4.R_OK);
28338
+ readable = true;
28339
+ } catch {
28340
+ readable = false;
28341
+ }
28342
+ return { exists: true, readable, uid, mode, realPath: real };
28343
+ }
28344
+ function collectVaultRefs2(value, out) {
28345
+ if (typeof value === "string") {
28346
+ if (value.startsWith("vault:")) {
28347
+ const key = value.slice("vault:".length).split("#")[0].trim();
28348
+ if (key)
28349
+ out.add(key);
28350
+ }
28351
+ return;
28352
+ }
28353
+ if (Array.isArray(value)) {
28354
+ for (const v of value)
28355
+ collectVaultRefs2(v, out);
28356
+ return;
28357
+ }
28358
+ if (value && typeof value === "object") {
28359
+ for (const v of Object.values(value)) {
28360
+ collectVaultRefs2(v, out);
28361
+ }
28362
+ }
28363
+ }
28364
+ function collectNeeds(resolved) {
28365
+ const cronKeys = new Set;
28366
+ for (const entry of resolved.schedule ?? []) {
28367
+ for (const s of entry.secrets ?? [])
28368
+ cronKeys.add(s);
28369
+ }
28370
+ const refKeys = new Set;
28371
+ collectVaultRefs2(resolved, refKeys);
28372
+ return { needed: new Set([...cronKeys, ...refKeys]), cronKeys };
28373
+ }
28374
+ function keyGap(key, isCron, exists, acl, scope) {
28375
+ const isGoogleSlot = key.startsWith("google:");
28376
+ if (!isGoogleSlot && !exists)
28377
+ return `'${key}' missing from the vault`;
28378
+ if (isCron && !acl.allow) {
28379
+ return `'${key}' (cron) \u2014 no static ACL grants read (${acl.reason})`;
28380
+ }
28381
+ if (!scope.allow) {
28382
+ return `'${key}' \u2014 per-key scope denies read (${scope.reason})`;
28383
+ }
28384
+ return null;
28385
+ }
28386
+ async function defaultPreflight(socketPath, agent, keys) {
28387
+ const r = await rpcRaw({ v: 1, op: "preflight_access", agent, keys }, { socket: socketPath, timeoutMs: 5000 });
28388
+ if (r.kind === "unreachable")
28389
+ return { kind: "unreachable", msg: r.msg };
28390
+ const resp = r.resp;
28391
+ if (resp.ok === true && resp.op === "preflight_access") {
28392
+ return {
28393
+ kind: "ok",
28394
+ results: resp.results
28395
+ };
28396
+ }
28397
+ if (resp.ok === false && resp.code === "LOCKED")
28398
+ return { kind: "locked" };
28399
+ return {
28400
+ kind: "unreachable",
28401
+ msg: resp.ok === false ? `broker error ${resp.code}: ${resp.msg}` : "unexpected broker response"
28402
+ };
28403
+ }
28404
+ async function runSecretAccessChecks(config, deps = {}) {
28405
+ const results = [];
28406
+ const vaultPath = deps.vaultPath ?? resolveVaultPath2(config);
28407
+ const statVault = deps.statVault ?? defaultStatVault;
28408
+ const selfUid = deps.selfUid ?? (typeof process.getuid === "function" ? process.getuid() : -1);
28409
+ let selfUser = deps.selfUser;
28410
+ if (selfUser === undefined) {
28411
+ try {
28412
+ selfUser = userInfo().username;
28413
+ } catch {
28414
+ selfUser = "<you>";
28415
+ }
28416
+ }
28417
+ const vf = statVault(vaultPath);
28418
+ if (!vf.exists) {
28419
+ results.push({
28420
+ name: "vault: operator readable",
28421
+ status: "ok",
28422
+ detail: `vault file not present at ${vaultPath} \u2014 see the Vault section`
28423
+ });
28424
+ } else if (!vf.readable) {
28425
+ results.push({
28426
+ name: "vault: operator readable",
28427
+ status: "fail",
28428
+ detail: `${vf.realPath} is owned by uid ${vf.uid} (mode 0${vf.mode.toString(8)}) \u2014 ` + `the operator (uid ${selfUid} ${selfUser}) cannot read it, so every ` + `\`switchroom vault \u2026\` fails. The broker still works (CAP_DAC_READ_SEARCH), ` + `which masks this until you touch the vault directly.`,
28429
+ fix: `sudo chown ${selfUser}:${selfUser} ${vf.realPath}`
28430
+ });
28431
+ } else {
28432
+ results.push({
28433
+ name: "vault: operator readable",
28434
+ status: "ok",
28435
+ detail: `operator can read ${vf.realPath}`
28436
+ });
28437
+ }
28438
+ const pushAgentResult = (name, total, gaps) => {
28439
+ results.push(gaps.length === 0 ? {
28440
+ name: `secret access: ${name}`,
28441
+ status: "ok",
28442
+ detail: `${total} secret(s): all present + ACL ok`
28443
+ } : {
28444
+ name: `secret access: ${name}`,
28445
+ status: "fail",
28446
+ detail: `${gaps.length}/${total} unreachable \u2014 ${gaps.join("; ")}`,
28447
+ fix: "`switchroom vault set <key>` for missing keys; " + "`switchroom vault set <key> --allow " + name + "` to grant this agent read access"
28448
+ });
28449
+ };
28450
+ const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
28451
+ if (!passphrase) {
28452
+ const sock = deps.brokerOperatorSocket ?? join38(homedir20(), ".switchroom", "broker-operator", "sock");
28453
+ const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
28454
+ for (const name of Object.keys(config.agents ?? {})) {
28455
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
28456
+ const { needed, cronKeys } = collectNeeds(resolved);
28457
+ if (needed.size === 0) {
28458
+ results.push({
28459
+ name: `secret access: ${name}`,
28460
+ status: "ok",
28461
+ detail: "no declared vault secrets"
28462
+ });
28463
+ continue;
28464
+ }
28465
+ const out = await preflight(name, [...needed].sort());
28466
+ if (out.kind === "unreachable" || out.kind === "locked") {
28467
+ results.push({
28468
+ name: "agent secret access",
28469
+ status: "skip",
28470
+ detail: out.kind === "locked" ? "vault-broker is locked \u2014 cron-secret existence/ACL unverified (re-run after it unlocks; it auto-unlocks on boot)" : `vault-broker operator socket unreachable (${out.msg}) and SWITCHROOM_VAULT_PASSPHRASE unset \u2014 cron-secret existence/ACL unverified`,
28471
+ fix: "Ensure the broker operator socket (~/.switchroom/broker-operator/sock) is bound, or export SWITCHROOM_VAULT_PASSPHRASE and re-run `switchroom doctor`"
28472
+ });
28473
+ return results;
28474
+ }
28475
+ const byKey = new Map(out.results.map((r) => [r.key, r]));
28476
+ const gaps = [];
28477
+ for (const key of [...needed].sort()) {
28478
+ const r = byKey.get(key);
28479
+ if (r === undefined) {
28480
+ gaps.push(`'${key}' \u2014 broker returned no result`);
28481
+ continue;
28482
+ }
28483
+ const g = keyGap(key, cronKeys.has(key), r.exists, { allow: r.acl_ok, reason: r.acl_reason }, { allow: r.scope_ok, reason: r.scope_reason });
28484
+ if (g)
28485
+ gaps.push(g);
28486
+ }
28487
+ pushAgentResult(name, needed.size, gaps);
28488
+ }
28489
+ return results;
28490
+ }
28491
+ let entries;
28492
+ try {
28493
+ entries = (deps.openVault ?? openVault)(passphrase, vaultPath);
28494
+ } catch (err) {
28495
+ results.push({
28496
+ name: "agent secret access",
28497
+ status: vf.readable ? "fail" : "warn",
28498
+ detail: `cannot open the vault: ${err.message}`,
28499
+ fix: vf.readable ? "SWITCHROOM_VAULT_PASSPHRASE may be wrong, or the vault is corrupt" : "fix the vault file ownership above first (operator cannot read it)"
28500
+ });
28501
+ return results;
28502
+ }
28503
+ for (const name of Object.keys(config.agents ?? {})) {
28504
+ const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
28505
+ const { needed, cronKeys } = collectNeeds(resolved);
28506
+ if (needed.size === 0) {
28507
+ results.push({
28508
+ name: `secret access: ${name}`,
28509
+ status: "ok",
28510
+ detail: "no declared vault secrets"
28511
+ });
28512
+ continue;
28513
+ }
28514
+ const gaps = [];
28515
+ for (const key of [...needed].sort()) {
28516
+ const g = keyGap(key, cronKeys.has(key), key in entries, checkAclByAgent(config, name, key), checkEntryScope(entries[key]?.scope, name));
28517
+ if (g)
28518
+ gaps.push(g);
28519
+ }
28520
+ pushAgentResult(name, needed.size, gaps);
28521
+ }
28522
+ return results;
28523
+ }
28524
+ var init_doctor_secret_access = __esm(() => {
28525
+ init_paths();
28526
+ init_merge();
28527
+ init_vault();
28528
+ init_acl();
28529
+ init_client();
28530
+ });
28531
+
28231
28532
  // src/cli/doctor-drive.ts
28232
28533
  import {
28233
28534
  existsSync as realExistsSync,
28234
28535
  readFileSync as realReadFileSync
28235
28536
  } from "node:fs";
28236
- import { join as join38, resolve as resolve28 } from "node:path";
28537
+ import { join as join39, resolve as resolve28 } from "node:path";
28538
+ import { homedir as homedir21 } from "node:os";
28237
28539
  function resolveDeps(config, deps) {
28238
28540
  let agentsDir = deps.agentsDir;
28239
28541
  if (agentsDir === undefined) {
@@ -28356,8 +28658,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
28356
28658
  });
28357
28659
  continue;
28358
28660
  }
28359
- const mcpPath = join38(agentDir, ".mcp.json");
28360
- const claudeJsonPath = join38(agentDir, ".claude", ".claude.json");
28661
+ const mcpPath = join39(agentDir, ".mcp.json");
28662
+ const claudeJsonPath = join39(agentDir, ".claude", ".claude.json");
28361
28663
  const mcpRead = readJson(d, mcpPath);
28362
28664
  const trustRead = readJson(d, claudeJsonPath);
28363
28665
  if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
@@ -28441,16 +28743,78 @@ function runDriveChecks(config, deps = {}) {
28441
28743
  const results = [];
28442
28744
  const matrix = checkConfigMatrix(config);
28443
28745
  results.push(...matrix);
28444
- const driveAgents = Object.keys(config.agents ?? {}).filter((name) => {
28746
+ const driveAgents = computeDriveAgents(config);
28747
+ results.push(...checkOAuthClient(config, driveAgents.length > 0));
28748
+ results.push(...checkScaffoldWiring(config, driveAgents, d));
28749
+ return results;
28750
+ }
28751
+ function computeDriveAgents(config) {
28752
+ const accounts = config.google_accounts;
28753
+ return Object.keys(config.agents ?? {}).filter((name) => {
28445
28754
  const acct = agentAccount(config, name);
28446
28755
  return !!acct && !!accounts?.[acct] && (accounts[acct].enabled_for ?? []).includes(name);
28447
28756
  });
28448
- results.push(...checkOAuthClient(config, driveAgents.length > 0));
28449
- results.push(...checkScaffoldWiring(config, driveAgents, d));
28757
+ }
28758
+ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
28759
+ const driveAgents = computeDriveAgents(config);
28760
+ if (driveAgents.length === 0)
28761
+ return [];
28762
+ const gw = config.google_workspace;
28763
+ const idKey = vaultRefKey(gw?.google_client_id);
28764
+ const secretKey = vaultRefKey(gw?.google_client_secret);
28765
+ const vaultKeys = [idKey, secretKey].filter((k) => k !== null);
28766
+ if (vaultKeys.length === 0) {
28767
+ return [
28768
+ {
28769
+ name: "drive: OAuth client broker-reachable",
28770
+ status: "ok",
28771
+ detail: "client credential is a literal (not a vault: ref) \u2014 the launcher reads it from config; no broker grant needed"
28772
+ }
28773
+ ];
28774
+ }
28775
+ const sock = deps.brokerOperatorSocket ?? join39(homedir21(), ".switchroom", "broker-operator", "sock");
28776
+ const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
28777
+ const results = [];
28778
+ for (const agent of driveAgents) {
28779
+ const outcome = await preflight(agent, vaultKeys);
28780
+ if (outcome.kind === "locked") {
28781
+ results.push({
28782
+ name: `drive: ${agent} client-cred broker-reachable`,
28783
+ status: "skip",
28784
+ detail: "vault-broker is locked \u2014 cannot verify; the fleet's launchers/crons are dead while locked anyway (not a Drive-specific fault)"
28785
+ });
28786
+ continue;
28787
+ }
28788
+ if (outcome.kind === "unreachable") {
28789
+ results.push({
28790
+ name: `drive: ${agent} client-cred broker-reachable`,
28791
+ status: "skip",
28792
+ detail: `broker operator socket unreachable (${outcome.msg}) \u2014 cannot verify; not a false fail`
28793
+ });
28794
+ continue;
28795
+ }
28796
+ const denied = outcome.results.filter((r) => !r.acl_ok);
28797
+ if (denied.length === 0) {
28798
+ results.push({
28799
+ name: `drive: ${agent} client-cred broker-reachable`,
28800
+ status: "ok",
28801
+ detail: `vault-broker will serve the OAuth client credential (${vaultKeys.join(", ")}) to '${agent}'`
28802
+ });
28803
+ continue;
28804
+ }
28805
+ const d0 = denied[0];
28806
+ results.push({
28807
+ name: `drive: ${agent} client-cred broker-reachable`,
28808
+ status: "fail",
28809
+ detail: `vault-broker DENIES '${agent}' the OAuth client key '${d0.key}' (${d0.acl_reason ?? "no ACL grant"}) \u2014 the gdrive MCP launcher exits before spawning and Drive silently never works for this agent`,
28810
+ fix: `confirm '${agent}' is in google_accounts[<account>].enabled_for[] and has agents.${agent}.google_workspace.account set (v0.12.14+ broker grants the client credential off that gate automatically); then ensure the broker is running the v0.12.14+ image`
28811
+ });
28812
+ }
28450
28813
  return results;
28451
28814
  }
28452
28815
  var init_doctor_drive = __esm(() => {
28453
28816
  init_loader();
28817
+ init_doctor_secret_access();
28454
28818
  });
28455
28819
 
28456
28820
  // src/cli/doctor-credentials-migration.ts
@@ -28459,11 +28823,11 @@ import {
28459
28823
  readdirSync as realReaddirSync,
28460
28824
  statSync as realStatSync
28461
28825
  } from "node:fs";
28462
- import { homedir as homedir20 } from "node:os";
28463
- import { join as join39 } from "node:path";
28826
+ import { homedir as homedir22 } from "node:os";
28827
+ import { join as join40 } from "node:path";
28464
28828
  function runCredentialsMigrationChecks(config, deps = {}) {
28465
- const credDir = deps.credentialsDir ?? join39(homedir20(), ".switchroom", "credentials");
28466
- const existsSync46 = deps.existsSync ?? ((p) => realExistsSync2(p));
28829
+ const credDir = deps.credentialsDir ?? join40(homedir22(), ".switchroom", "credentials");
28830
+ const existsSync47 = deps.existsSync ?? ((p) => realExistsSync2(p));
28467
28831
  const readdirSync17 = deps.readdirSync ?? ((p) => realReaddirSync(p));
28468
28832
  const isDirectory = deps.isDirectory ?? ((p) => {
28469
28833
  try {
@@ -28472,7 +28836,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
28472
28836
  return false;
28473
28837
  }
28474
28838
  });
28475
- if (!existsSync46(credDir))
28839
+ if (!existsSync47(credDir))
28476
28840
  return [];
28477
28841
  const agentNames = new Set(Object.keys(config.agents ?? {}));
28478
28842
  let entries;
@@ -28490,7 +28854,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
28490
28854
  const flat = [];
28491
28855
  const perAgentDirs = [];
28492
28856
  for (const e of entries) {
28493
- const full = join39(credDir, e);
28857
+ const full = join40(credDir, e);
28494
28858
  if (isDirectory(full) && agentNames.has(e)) {
28495
28859
  perAgentDirs.push(e);
28496
28860
  } else {
@@ -28517,181 +28881,6 @@ function runCredentialsMigrationChecks(config, deps = {}) {
28517
28881
  }
28518
28882
  var init_doctor_credentials_migration = () => {};
28519
28883
 
28520
- // src/cli/doctor-secret-access.ts
28521
- import {
28522
- accessSync,
28523
- constants as fsConstants4,
28524
- existsSync as existsSync46,
28525
- realpathSync as realpathSync4,
28526
- statSync as statSync20
28527
- } from "node:fs";
28528
- import { userInfo } from "node:os";
28529
- function resolveVaultPath2(config) {
28530
- return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
28531
- }
28532
- function defaultStatVault(path4) {
28533
- if (!existsSync46(path4)) {
28534
- return { exists: false, readable: false, uid: -1, mode: 0, realPath: path4 };
28535
- }
28536
- let real = path4;
28537
- try {
28538
- real = realpathSync4(path4);
28539
- } catch {}
28540
- let uid = -1;
28541
- let mode = 0;
28542
- try {
28543
- const s = statSync20(real);
28544
- uid = s.uid;
28545
- mode = s.mode & 511;
28546
- } catch {
28547
- return { exists: true, readable: false, uid, mode, realPath: real };
28548
- }
28549
- let readable = false;
28550
- try {
28551
- accessSync(real, fsConstants4.R_OK);
28552
- readable = true;
28553
- } catch {
28554
- readable = false;
28555
- }
28556
- return { exists: true, readable, uid, mode, realPath: real };
28557
- }
28558
- function collectVaultRefs2(value, out) {
28559
- if (typeof value === "string") {
28560
- if (value.startsWith("vault:")) {
28561
- const key = value.slice("vault:".length).split("#")[0].trim();
28562
- if (key)
28563
- out.add(key);
28564
- }
28565
- return;
28566
- }
28567
- if (Array.isArray(value)) {
28568
- for (const v of value)
28569
- collectVaultRefs2(v, out);
28570
- return;
28571
- }
28572
- if (value && typeof value === "object") {
28573
- for (const v of Object.values(value)) {
28574
- collectVaultRefs2(v, out);
28575
- }
28576
- }
28577
- }
28578
- function runSecretAccessChecks(config, deps = {}) {
28579
- const results = [];
28580
- const vaultPath = deps.vaultPath ?? resolveVaultPath2(config);
28581
- const statVault = deps.statVault ?? defaultStatVault;
28582
- const selfUid = deps.selfUid ?? (typeof process.getuid === "function" ? process.getuid() : -1);
28583
- let selfUser = deps.selfUser;
28584
- if (selfUser === undefined) {
28585
- try {
28586
- selfUser = userInfo().username;
28587
- } catch {
28588
- selfUser = "<you>";
28589
- }
28590
- }
28591
- const vf = statVault(vaultPath);
28592
- if (!vf.exists) {
28593
- results.push({
28594
- name: "vault: operator readable",
28595
- status: "ok",
28596
- detail: `vault file not present at ${vaultPath} \u2014 see the Vault section`
28597
- });
28598
- } else if (!vf.readable) {
28599
- results.push({
28600
- name: "vault: operator readable",
28601
- status: "fail",
28602
- detail: `${vf.realPath} is owned by uid ${vf.uid} (mode 0${vf.mode.toString(8)}) \u2014 ` + `the operator (uid ${selfUid} ${selfUser}) cannot read it, so every ` + `\`switchroom vault \u2026\` fails. The broker still works (CAP_DAC_READ_SEARCH), ` + `which masks this until you touch the vault directly.`,
28603
- fix: `sudo chown ${selfUser}:${selfUser} ${vf.realPath}`
28604
- });
28605
- } else {
28606
- results.push({
28607
- name: "vault: operator readable",
28608
- status: "ok",
28609
- detail: `operator can read ${vf.realPath}`
28610
- });
28611
- }
28612
- const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
28613
- if (!passphrase) {
28614
- results.push({
28615
- name: "agent secret access",
28616
- status: "skip",
28617
- detail: "SWITCHROOM_VAULT_PASSPHRASE not set \u2014 cannot enumerate vault keys/ACLs " + "to verify per-agent secret access",
28618
- fix: "Export SWITCHROOM_VAULT_PASSPHRASE and re-run `switchroom doctor`"
28619
- });
28620
- return results;
28621
- }
28622
- let entries;
28623
- try {
28624
- entries = (deps.openVault ?? openVault)(passphrase, vaultPath);
28625
- } catch (err) {
28626
- results.push({
28627
- name: "agent secret access",
28628
- status: vf.readable ? "fail" : "warn",
28629
- detail: `cannot open the vault: ${err.message}`,
28630
- fix: vf.readable ? "SWITCHROOM_VAULT_PASSPHRASE may be wrong, or the vault is corrupt" : "fix the vault file ownership above first (operator cannot read it)"
28631
- });
28632
- return results;
28633
- }
28634
- const agents = Object.keys(config.agents ?? {});
28635
- for (const name of agents) {
28636
- const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
28637
- const cronKeys = new Set;
28638
- for (const entry of resolved.schedule ?? []) {
28639
- for (const s of entry.secrets ?? [])
28640
- cronKeys.add(s);
28641
- }
28642
- const refKeys = new Set;
28643
- collectVaultRefs2(resolved, refKeys);
28644
- const needed = new Set([...cronKeys, ...refKeys]);
28645
- if (needed.size === 0) {
28646
- results.push({
28647
- name: `secret access: ${name}`,
28648
- status: "ok",
28649
- detail: "no declared vault secrets"
28650
- });
28651
- continue;
28652
- }
28653
- const gaps = [];
28654
- for (const key of [...needed].sort()) {
28655
- const isGoogleSlot = key.startsWith("google:");
28656
- if (!isGoogleSlot && !(key in entries)) {
28657
- gaps.push(`'${key}' missing from the vault`);
28658
- continue;
28659
- }
28660
- const byScope = checkEntryScope(entries[key]?.scope, name);
28661
- if (cronKeys.has(key)) {
28662
- const byAgent = checkAclByAgent(config, name, key);
28663
- if (!byAgent.allow) {
28664
- gaps.push(`'${key}' (cron) \u2014 no static ACL grants read (${byAgent.reason})`);
28665
- continue;
28666
- }
28667
- }
28668
- if (!byScope.allow) {
28669
- gaps.push(`'${key}' \u2014 per-key scope denies read (${byScope.reason})`);
28670
- }
28671
- }
28672
- if (gaps.length === 0) {
28673
- results.push({
28674
- name: `secret access: ${name}`,
28675
- status: "ok",
28676
- detail: `${needed.size} secret(s): all present + ACL ok`
28677
- });
28678
- } else {
28679
- results.push({
28680
- name: `secret access: ${name}`,
28681
- status: "fail",
28682
- detail: `${gaps.length}/${needed.size} unreachable \u2014 ${gaps.join("; ")}`,
28683
- fix: "`switchroom vault set <key>` for missing keys; " + "`switchroom vault set <key> --allow " + name + "` to grant this agent read access"
28684
- });
28685
- }
28686
- }
28687
- return results;
28688
- }
28689
- var init_doctor_secret_access = __esm(() => {
28690
- init_paths();
28691
- init_merge();
28692
- init_vault();
28693
- });
28694
-
28695
28884
  // src/cli/doctor-inlined-secrets.ts
28696
28885
  import { readFileSync as fsReadFileSync } from "node:fs";
28697
28886
  function isSecretShapedKey(key) {
@@ -28788,19 +28977,19 @@ var init_doctor_inlined_secrets = __esm(() => {
28788
28977
 
28789
28978
  // src/cli/doctor-audit-integrity.ts
28790
28979
  import { readFileSync as fsReadFileSync2 } from "node:fs";
28791
- import { homedir as homedir21 } from "node:os";
28792
- import { join as join40 } from "node:path";
28980
+ import { homedir as homedir23 } from "node:os";
28981
+ import { join as join41 } from "node:path";
28793
28982
  function rootWrittenLogs(home2) {
28794
28983
  return [
28795
- { label: "vault-broker", path: join40(home2, ".switchroom", "vault-audit.log") },
28984
+ { label: "vault-broker", path: join41(home2, ".switchroom", "vault-audit.log") },
28796
28985
  {
28797
28986
  label: "hostd",
28798
- path: join40(home2, ".switchroom", "host-control-audit.log")
28987
+ path: join41(home2, ".switchroom", "host-control-audit.log")
28799
28988
  }
28800
28989
  ];
28801
28990
  }
28802
28991
  function runAuditIntegrityChecks(deps = {}) {
28803
- const home2 = deps.homeDir ?? homedir21();
28992
+ const home2 = deps.homeDir ?? homedir23();
28804
28993
  const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
28805
28994
  const results = [];
28806
28995
  for (const { label, path: path4 } of rootWrittenLogs(home2)) {
@@ -29077,14 +29266,14 @@ var init_client4 = __esm(() => {
29077
29266
 
29078
29267
  // src/cli/doctor-agent-smoke.ts
29079
29268
  import { existsSync as existsSync47 } from "node:fs";
29080
- import { homedir as homedir22 } from "node:os";
29081
- import { join as join41 } from "node:path";
29269
+ import { homedir as homedir24 } from "node:os";
29270
+ import { join as join42 } from "node:path";
29082
29271
  import { randomUUID as randomUUID4 } from "node:crypto";
29083
29272
  async function runAgentSmokeChecks(config, deps = {}) {
29084
29273
  if (deps.fast)
29085
29274
  return [];
29086
- const home2 = deps.homeDir ?? homedir22();
29087
- const sock = deps.operatorSockPath ?? join41(home2, ".switchroom", "hostd", "operator", "sock");
29275
+ const home2 = deps.homeDir ?? homedir24();
29276
+ const sock = deps.operatorSockPath ?? join42(home2, ".switchroom", "hostd", "operator", "sock");
29088
29277
  if (!deps.hostdRequestImpl && !existsSync47(sock)) {
29089
29278
  return [
29090
29279
  {
@@ -29211,16 +29400,16 @@ import {
29211
29400
  readdirSync as readdirSync17,
29212
29401
  statSync as statSync21
29213
29402
  } from "node:fs";
29214
- import { dirname as dirname12, join as join42, resolve as resolve29 } from "node:path";
29403
+ import { dirname as dirname12, join as join43, resolve as resolve29 } from "node:path";
29215
29404
  import { createPublicKey, createPrivateKey } from "node:crypto";
29216
29405
  function findInNvm(bin) {
29217
- const nvmRoot = join42(process.env.HOME ?? "", ".nvm", "versions", "node");
29406
+ const nvmRoot = join43(process.env.HOME ?? "", ".nvm", "versions", "node");
29218
29407
  if (!existsSync48(nvmRoot))
29219
29408
  return null;
29220
29409
  try {
29221
29410
  const versions = readdirSync17(nvmRoot).sort().reverse();
29222
29411
  for (const v of versions) {
29223
- const candidate = join42(nvmRoot, v, "bin", bin);
29412
+ const candidate = join43(nvmRoot, v, "bin", bin);
29224
29413
  try {
29225
29414
  const s = statSync21(candidate);
29226
29415
  if (s.isFile() || s.isSymbolicLink()) {
@@ -29385,7 +29574,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
29385
29574
  if (envBrowsersPath && envBrowsersPath.length > 0) {
29386
29575
  cacheLocations.push(envBrowsersPath);
29387
29576
  }
29388
- cacheLocations.push(join42(homeDir, ".cache", "ms-playwright"));
29577
+ cacheLocations.push(join43(homeDir, ".cache", "ms-playwright"));
29389
29578
  for (const cacheDir of cacheLocations) {
29390
29579
  if (!existsSync48(cacheDir))
29391
29580
  continue;
@@ -29393,10 +29582,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
29393
29582
  const entries = readdirSync17(cacheDir).filter((e) => e.startsWith("chromium"));
29394
29583
  for (const entry of entries) {
29395
29584
  const candidates2 = [
29396
- join42(cacheDir, entry, "chrome-linux64", "chrome"),
29397
- join42(cacheDir, entry, "chrome-linux", "chrome"),
29398
- join42(cacheDir, entry, "chrome-linux64", "headless_shell"),
29399
- join42(cacheDir, entry, "chrome-linux", "headless_shell")
29585
+ join43(cacheDir, entry, "chrome-linux64", "chrome"),
29586
+ join43(cacheDir, entry, "chrome-linux", "chrome"),
29587
+ join43(cacheDir, entry, "chrome-linux64", "headless_shell"),
29588
+ join43(cacheDir, entry, "chrome-linux", "headless_shell")
29400
29589
  ];
29401
29590
  for (const path4 of candidates2) {
29402
29591
  if (existsSync48(path4))
@@ -29479,7 +29668,7 @@ function checkConfig(config, configPath) {
29479
29668
  function checkLegacyState() {
29480
29669
  const results = [];
29481
29670
  const h = process.env.HOME ?? "/root";
29482
- const clerkDir = join42(h, LEGACY_STATE_DIR);
29671
+ const clerkDir = join43(h, LEGACY_STATE_DIR);
29483
29672
  const clerkPresent = existsSync48(clerkDir);
29484
29673
  results.push({
29485
29674
  name: "legacy ~/.clerk state",
@@ -29489,7 +29678,7 @@ function checkLegacyState() {
29489
29678
  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."
29490
29679
  } : {}
29491
29680
  });
29492
- const legacySock = join42(h, ".switchroom", "vault-broker.sock");
29681
+ const legacySock = join43(h, ".switchroom", "vault-broker.sock");
29493
29682
  let sockStat = null;
29494
29683
  try {
29495
29684
  sockStat = lstatSync5(legacySock);
@@ -29693,7 +29882,7 @@ async function checkHindsight(config) {
29693
29882
  }
29694
29883
  function checkPendingRetainsQueue(dir) {
29695
29884
  const home2 = process.env.HOME ?? "";
29696
- const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join42(home2, ".hindsight", "pending-retains");
29885
+ const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join43(home2, ".hindsight", "pending-retains");
29697
29886
  if (!existsSync48(pendingDir)) {
29698
29887
  return {
29699
29888
  name: "pending-retains queue",
@@ -29824,7 +30013,7 @@ async function checkTelegram(config) {
29824
30013
  const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
29825
30014
  if (plugin !== "switchroom")
29826
30015
  continue;
29827
- const envPath = join42(agentsDir, name, "telegram", ".env");
30016
+ const envPath = join43(agentsDir, name, "telegram", ".env");
29828
30017
  const read = tryReadHostFile(envPath);
29829
30018
  if (read.kind === "eacces") {
29830
30019
  results.push({
@@ -29907,7 +30096,7 @@ function checkStartShStale(agentName, startShPath) {
29907
30096
  }
29908
30097
  function checkLeakedHomeSwitchroom(agentName, agentDir) {
29909
30098
  const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
29910
- const path4 = join42(agentDir, "home", ".switchroom");
30099
+ const path4 = join43(agentDir, "home", ".switchroom");
29911
30100
  let stats;
29912
30101
  try {
29913
30102
  stats = lstatSync5(path4);
@@ -29944,7 +30133,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
29944
30133
  }
29945
30134
  function checkRepoHygiene(repoRoot) {
29946
30135
  const results = [];
29947
- const exportDir = join42(repoRoot, "clerk-export");
30136
+ const exportDir = join43(repoRoot, "clerk-export");
29948
30137
  if (existsSync48(exportDir)) {
29949
30138
  results.push({
29950
30139
  name: "repo hygiene: clerk-export/ on disk (#1072)",
@@ -29953,7 +30142,7 @@ function checkRepoHygiene(repoRoot) {
29953
30142
  fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
29954
30143
  });
29955
30144
  }
29956
- const knownTarball = join42(repoRoot, "clerk-export-with-secrets.tar.gz");
30145
+ const knownTarball = join43(repoRoot, "clerk-export-with-secrets.tar.gz");
29957
30146
  if (existsSync48(knownTarball)) {
29958
30147
  results.push({
29959
30148
  name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
@@ -29971,7 +30160,7 @@ function checkRepoHygiene(repoRoot) {
29971
30160
  results.push({
29972
30161
  name: `repo hygiene: ${name} on disk (#1072)`,
29973
30162
  status: "warn",
29974
- detail: `${join42(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
30163
+ detail: `${join43(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
29975
30164
  fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
29976
30165
  });
29977
30166
  }
@@ -29994,9 +30183,9 @@ function checkRepoHygiene(repoRoot) {
29994
30183
  }
29995
30184
  function isSwitchroomCheckout(dir) {
29996
30185
  try {
29997
- if (!existsSync48(join42(dir, ".git")))
30186
+ if (!existsSync48(join43(dir, ".git")))
29998
30187
  return false;
29999
- const pkgPath = join42(dir, "package.json");
30188
+ const pkgPath = join43(dir, "package.json");
30000
30189
  if (!existsSync48(pkgPath))
30001
30190
  return false;
30002
30191
  const pkg = JSON.parse(readFileSync44(pkgPath, "utf-8"));
@@ -30033,7 +30222,7 @@ function checkAgents(config, configPath) {
30033
30222
  fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
30034
30223
  });
30035
30224
  }
30036
- results.push(checkStartShStale(name, join42(agentDir, "start.sh")));
30225
+ results.push(checkStartShStale(name, join43(agentDir, "start.sh")));
30037
30226
  results.push(checkLeakedHomeSwitchroom(name, agentDir));
30038
30227
  const status = statuses[name];
30039
30228
  const active = status?.active ?? "unknown";
@@ -30110,7 +30299,7 @@ function checkAgents(config, configPath) {
30110
30299
  }
30111
30300
  }
30112
30301
  if (agentConfig.channels?.telegram?.plugin === "switchroom") {
30113
- const mcpJsonPath = join42(agentDir, ".mcp.json");
30302
+ const mcpJsonPath = join43(agentDir, ".mcp.json");
30114
30303
  if (!existsSync48(mcpJsonPath)) {
30115
30304
  results.push({
30116
30305
  name: `${name}: .mcp.json`,
@@ -30411,7 +30600,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
30411
30600
  };
30412
30601
  }
30413
30602
  const credDir = dirname12(envPath);
30414
- const authScript = join42(credDir, "claude-auth.py");
30603
+ const authScript = join43(credDir, "claude-auth.py");
30415
30604
  if (!existsSync48(authScript)) {
30416
30605
  return {
30417
30606
  name: "mff: auth flow",
@@ -30690,7 +30879,7 @@ function registerDoctorCommand(program3) {
30690
30879
  },
30691
30880
  { title: "Legacy State", results: checkLegacyState() },
30692
30881
  { title: "Vault", results: checkVault(config) },
30693
- { title: "Vault access", results: runSecretAccessChecks(config) },
30882
+ { title: "Vault access", results: await runSecretAccessChecks(config) },
30694
30883
  { title: "Memory (Hindsight)", results: await checkHindsight(config) },
30695
30884
  { title: "Telegram", results: await checkTelegram(config) },
30696
30885
  { title: "Agents", results: checkAgents(config, configPath) },
@@ -30703,7 +30892,13 @@ function registerDoctorCommand(program3) {
30703
30892
  title: "Agent liveness (in-agent via hostd)",
30704
30893
  results: await runAgentSmokeChecks(config, { fast: opts.fast })
30705
30894
  },
30706
- { title: "Google Drive", results: runDriveChecks(config) },
30895
+ {
30896
+ title: "Google Drive",
30897
+ results: [
30898
+ ...runDriveChecks(config),
30899
+ ...await runDriveBrokerReachabilityChecks(config)
30900
+ ]
30901
+ },
30707
30902
  { title: "MFF Skill", results: await checkMff(passphrase, vaultPath, config) }
30708
30903
  ];
30709
30904
  const cwd = process.cwd();
@@ -47047,8 +47242,8 @@ var {
47047
47242
  } = import__.default;
47048
47243
 
47049
47244
  // src/build-info.ts
47050
- var VERSION = "0.12.13";
47051
- var COMMIT_SHA = "fc96015d";
47245
+ var VERSION = "0.12.15";
47246
+ var COMMIT_SHA = "dc508a92";
47052
47247
 
47053
47248
  // src/cli/agent.ts
47054
47249
  init_source();
@@ -55869,6 +56064,7 @@ var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
55869
56064
 
55870
56065
  // src/vault/broker/server.ts
55871
56066
  init_peercred();
56067
+ init_acl();
55872
56068
  init_protocol();
55873
56069
 
55874
56070
  // src/vault/broker/audit-log.ts
@@ -58199,7 +58395,9 @@ class VaultBroker {
58199
58395
  this._writePidFile();
58200
58396
  this._sdNotify(`READY=1
58201
58397
  `);
58202
- this._tryAutoUnlock();
58398
+ if (this.testOpts._testSecrets === undefined) {
58399
+ this._tryAutoUnlock();
58400
+ }
58203
58401
  if (process.platform !== "linux") {
58204
58402
  process.stderr.write(`[vault-broker] WARNING: running on ${process.platform} with ` + `SWITCHROOM_BROKER_ALLOW_NON_LINUX=1 \u2014 peercred ACL is disabled. ` + `Access control is socket file mode 0600 ONLY. Do not use this configuration for production secrets.
58205
58403
  `);
@@ -58510,6 +58708,49 @@ class VaultBroker {
58510
58708
  socket.write(encodeResponse({ ok: true, locked: true }));
58511
58709
  return;
58512
58710
  }
58711
+ if (req.op === "preflight_access") {
58712
+ if (!isOperator) {
58713
+ writeAudit({
58714
+ ts: new Date().toISOString(),
58715
+ op: "preflight_access",
58716
+ caller: auditCaller,
58717
+ pid: auditPid,
58718
+ cgroup: auditCgroup,
58719
+ result: "denied:operator-only"
58720
+ });
58721
+ socket.write(encodeResponse(errorResponse("DENIED", "preflight_access is operator-only")));
58722
+ return;
58723
+ }
58724
+ if (this.secrets === null) {
58725
+ socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
58726
+ return;
58727
+ }
58728
+ const pfSecrets = this.secrets;
58729
+ const pfConfig = this.config;
58730
+ const results = req.keys.map((key) => {
58731
+ const exists = Object.prototype.hasOwnProperty.call(pfSecrets, key);
58732
+ const acl = pfConfig !== null ? checkAclByAgent(pfConfig, req.agent, key) : { allow: false, reason: "broker has no config loaded" };
58733
+ const scope = checkEntryScope(pfSecrets[key]?.scope, req.agent);
58734
+ return {
58735
+ key,
58736
+ exists,
58737
+ acl_ok: acl.allow,
58738
+ ...acl.allow ? {} : { acl_reason: acl.reason },
58739
+ scope_ok: scope.allow,
58740
+ ...scope.allow ? {} : { scope_reason: scope.reason }
58741
+ };
58742
+ });
58743
+ writeAudit({
58744
+ ts: new Date().toISOString(),
58745
+ op: "preflight_access",
58746
+ caller: auditCaller,
58747
+ pid: auditPid,
58748
+ cgroup: auditCgroup,
58749
+ result: `allowed:agent=${req.agent},keys=${req.keys.length}`
58750
+ });
58751
+ socket.write(encodeResponse({ ok: true, op: "preflight_access", results }));
58752
+ return;
58753
+ }
58513
58754
  if (req.op === "list") {
58514
58755
  if (this.secrets === null) {
58515
58756
  socket.write(encodeResponse(errorResponse("LOCKED", "Vault is locked")));
@@ -68972,15 +69213,15 @@ init_loader();
68972
69213
  init_lifecycle();
68973
69214
  import { cpSync as cpSync2, existsSync as existsSync49, mkdirSync as mkdirSync27, readFileSync as readFileSync45, realpathSync as realpathSync5, rmSync as rmSync12, statSync as statSync22 } from "node:fs";
68974
69215
  import { spawnSync as spawnSync8 } from "node:child_process";
68975
- import { join as join43, dirname as dirname13, resolve as resolve30 } from "node:path";
68976
- import { homedir as homedir23 } from "node:os";
68977
- var DEFAULT_COMPOSE_PATH = join43(homedir23(), ".switchroom", "compose", "docker-compose.yml");
69216
+ import { join as join44, dirname as dirname13, resolve as resolve30 } from "node:path";
69217
+ import { homedir as homedir25 } from "node:os";
69218
+ var DEFAULT_COMPOSE_PATH = join44(homedir25(), ".switchroom", "compose", "docker-compose.yml");
68978
69219
  function runningFromSwitchroomCheckout(scriptPath) {
68979
69220
  let dir = dirname13(scriptPath);
68980
69221
  for (let i = 0;i < 12; i++) {
68981
- if (existsSync49(join43(dir, ".git"))) {
69222
+ if (existsSync49(join44(dir, ".git"))) {
68982
69223
  try {
68983
- const pkg = JSON.parse(readFileSync45(join43(dir, "package.json"), "utf-8"));
69224
+ const pkg = JSON.parse(readFileSync45(join44(dir, "package.json"), "utf-8"));
68984
69225
  if (pkg.name === "switchroom")
68985
69226
  return true;
68986
69227
  } catch {}
@@ -69111,7 +69352,7 @@ function planUpdate(opts) {
69111
69352
  return;
69112
69353
  }
69113
69354
  const source = resolve30(import.meta.dirname, "../../skills");
69114
- const dest = join43(homedir23(), ".switchroom", "skills", "_bundled");
69355
+ const dest = join44(homedir25(), ".switchroom", "skills", "_bundled");
69115
69356
  if (!existsSync49(source)) {
69116
69357
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
69117
69358
  `);
@@ -69225,7 +69466,7 @@ function defaultStatusProbe(composePath) {
69225
69466
  } catch {}
69226
69467
  let dir = dirname13(scriptPath);
69227
69468
  for (let i = 0;i < 8; i++) {
69228
- const pkgPath = join43(dir, "package.json");
69469
+ const pkgPath = join44(dir, "package.json");
69229
69470
  if (existsSync49(pkgPath)) {
69230
69471
  try {
69231
69472
  const pkg = JSON.parse(readFileSync45(pkgPath, "utf-8"));
@@ -69445,7 +69686,7 @@ init_helpers();
69445
69686
  init_lifecycle();
69446
69687
  import { execSync as execSync4 } from "node:child_process";
69447
69688
  import { existsSync as existsSync50, readFileSync as readFileSync46 } from "node:fs";
69448
- import { dirname as dirname14, join as join44 } from "node:path";
69689
+ import { dirname as dirname14, join as join45 } from "node:path";
69449
69690
  function getClaudeCodeVersion() {
69450
69691
  try {
69451
69692
  const out = execSync4("claude --version 2>/dev/null", {
@@ -69495,11 +69736,11 @@ function formatUptime3(timestamp) {
69495
69736
  function locateSwitchroomInstallDir() {
69496
69737
  let dir = import.meta.dirname;
69497
69738
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
69498
- const pkgPath = join44(dir, "package.json");
69739
+ const pkgPath = join45(dir, "package.json");
69499
69740
  if (existsSync50(pkgPath)) {
69500
69741
  try {
69501
69742
  const pkg = JSON.parse(readFileSync46(pkgPath, "utf-8"));
69502
- if (pkg.name === "switchroom" && existsSync50(join44(dir, ".git"))) {
69743
+ if (pkg.name === "switchroom" && existsSync50(join45(dir, ".git"))) {
69503
69744
  return dir;
69504
69745
  }
69505
69746
  } catch {}
@@ -69729,7 +69970,7 @@ import {
69729
69970
  writeFileSync as writeFileSync25,
69730
69971
  writeSync as writeSync6
69731
69972
  } from "node:fs";
69732
- import { join as join45 } from "node:path";
69973
+ import { join as join46 } from "node:path";
69733
69974
  import { randomBytes as randomBytes10 } from "node:crypto";
69734
69975
  import { execSync as execSync5 } from "node:child_process";
69735
69976
 
@@ -70049,7 +70290,7 @@ function redactedMarker(ruleId) {
70049
70290
  var ISSUES_FILE = "issues.jsonl";
70050
70291
  var ISSUES_LOCK = "issues.lock";
70051
70292
  function readAll(stateDir) {
70052
- const path4 = join45(stateDir, ISSUES_FILE);
70293
+ const path4 = join46(stateDir, ISSUES_FILE);
70053
70294
  if (!existsSync51(path4))
70054
70295
  return [];
70055
70296
  let raw;
@@ -70127,7 +70368,7 @@ function record(stateDir, input, nowFn = Date.now) {
70127
70368
  });
70128
70369
  }
70129
70370
  function resolve33(stateDir, fingerprint, nowFn = Date.now) {
70130
- if (!existsSync51(join45(stateDir, ISSUES_FILE)))
70371
+ if (!existsSync51(join46(stateDir, ISSUES_FILE)))
70131
70372
  return 0;
70132
70373
  return withLock(stateDir, () => {
70133
70374
  const all = readAll(stateDir);
@@ -70145,7 +70386,7 @@ function resolve33(stateDir, fingerprint, nowFn = Date.now) {
70145
70386
  });
70146
70387
  }
70147
70388
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
70148
- if (!existsSync51(join45(stateDir, ISSUES_FILE)))
70389
+ if (!existsSync51(join46(stateDir, ISSUES_FILE)))
70149
70390
  return 0;
70150
70391
  return withLock(stateDir, () => {
70151
70392
  const all = readAll(stateDir);
@@ -70163,7 +70404,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
70163
70404
  });
70164
70405
  }
70165
70406
  function prune(stateDir, opts = {}) {
70166
- if (!existsSync51(join45(stateDir, ISSUES_FILE)))
70407
+ if (!existsSync51(join46(stateDir, ISSUES_FILE)))
70167
70408
  return 0;
70168
70409
  return withLock(stateDir, () => {
70169
70410
  const all = readAll(stateDir);
@@ -70196,7 +70437,7 @@ function ensureDir(stateDir) {
70196
70437
  mkdirSync28(stateDir, { recursive: true });
70197
70438
  }
70198
70439
  function writeAll(stateDir, events) {
70199
- const path4 = join45(stateDir, ISSUES_FILE);
70440
+ const path4 = join46(stateDir, ISSUES_FILE);
70200
70441
  sweepOrphanTmpFiles(stateDir);
70201
70442
  const tmp = `${path4}.tmp-${process.pid}-${randomBytes10(4).toString("hex")}`;
70202
70443
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -70218,7 +70459,7 @@ function sweepOrphanTmpFiles(stateDir) {
70218
70459
  for (const entry of entries) {
70219
70460
  if (!entry.startsWith(TMP_PREFIX))
70220
70461
  continue;
70221
- const tmpPath = join45(stateDir, entry);
70462
+ const tmpPath = join46(stateDir, entry);
70222
70463
  try {
70223
70464
  const stat = statSync23(tmpPath);
70224
70465
  if (stat.mtimeMs < cutoff) {
@@ -70230,7 +70471,7 @@ function sweepOrphanTmpFiles(stateDir) {
70230
70471
  var LOCK_RETRY_MS = 25;
70231
70472
  var LOCK_TIMEOUT_MS = 1e4;
70232
70473
  function withLock(stateDir, fn) {
70233
- const lockPath = join45(stateDir, ISSUES_LOCK);
70474
+ const lockPath = join46(stateDir, ISSUES_LOCK);
70234
70475
  const startedAt = Date.now();
70235
70476
  let fd = null;
70236
70477
  while (fd === null) {
@@ -70514,8 +70755,8 @@ function relTime(deltaMs) {
70514
70755
  // src/cli/deps.ts
70515
70756
  init_source();
70516
70757
  import { existsSync as existsSync54 } from "node:fs";
70517
- import { homedir as homedir26 } from "node:os";
70518
- import { join as join48, resolve as resolve34 } from "node:path";
70758
+ import { homedir as homedir28 } from "node:os";
70759
+ import { join as join49, resolve as resolve34 } from "node:path";
70519
70760
 
70520
70761
  // src/deps/python.ts
70521
70762
  import { createHash as createHash9 } from "node:crypto";
@@ -70526,8 +70767,8 @@ import {
70526
70767
  rmSync as rmSync13,
70527
70768
  writeFileSync as writeFileSync26
70528
70769
  } from "node:fs";
70529
- import { dirname as dirname15, join as join46 } from "node:path";
70530
- import { homedir as homedir24 } from "node:os";
70770
+ import { dirname as dirname15, join as join47 } from "node:path";
70771
+ import { homedir as homedir26 } from "node:os";
70531
70772
  import { execFileSync as execFileSync14 } from "node:child_process";
70532
70773
 
70533
70774
  class PythonEnvError extends Error {
@@ -70539,7 +70780,7 @@ class PythonEnvError extends Error {
70539
70780
  }
70540
70781
  }
70541
70782
  function defaultPythonCacheRoot() {
70542
- return join46(homedir24(), ".switchroom", "deps", "python");
70783
+ return join47(homedir26(), ".switchroom", "deps", "python");
70543
70784
  }
70544
70785
  function hashFile(path4) {
70545
70786
  return createHash9("sha256").update(readFileSync48(path4)).digest("hex");
@@ -70551,11 +70792,11 @@ function ensurePythonEnv(opts) {
70551
70792
  if (!existsSync52(requirementsPath)) {
70552
70793
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
70553
70794
  }
70554
- const venvDir = join46(cacheRoot, skillName);
70555
- const stampPath = join46(venvDir, ".requirements.sha256");
70556
- const binDir = join46(venvDir, "bin");
70557
- const pythonBin = join46(binDir, "python");
70558
- const pipBin = join46(binDir, "pip");
70795
+ const venvDir = join47(cacheRoot, skillName);
70796
+ const stampPath = join47(venvDir, ".requirements.sha256");
70797
+ const binDir = join47(venvDir, "bin");
70798
+ const pythonBin = join47(binDir, "python");
70799
+ const pipBin = join47(binDir, "pip");
70559
70800
  const targetHash = hashFile(requirementsPath);
70560
70801
  if (!force && existsSync52(stampPath) && existsSync52(pythonBin)) {
70561
70802
  const existingHash = readFileSync48(stampPath, "utf8").trim();
@@ -70614,8 +70855,8 @@ import {
70614
70855
  rmSync as rmSync14,
70615
70856
  writeFileSync as writeFileSync27
70616
70857
  } from "node:fs";
70617
- import { dirname as dirname16, join as join47 } from "node:path";
70618
- import { homedir as homedir25 } from "node:os";
70858
+ import { dirname as dirname16, join as join48 } from "node:path";
70859
+ import { homedir as homedir27 } from "node:os";
70619
70860
  import { execFileSync as execFileSync15 } from "node:child_process";
70620
70861
 
70621
70862
  class NodeEnvError extends Error {
@@ -70638,7 +70879,7 @@ var LOCKFILES_FOR = {
70638
70879
  npm: ["package-lock.json"]
70639
70880
  };
70640
70881
  function defaultNodeCacheRoot() {
70641
- return join47(homedir25(), ".switchroom", "deps", "node");
70882
+ return join48(homedir27(), ".switchroom", "deps", "node");
70642
70883
  }
70643
70884
  function hashDepInputs(packageJsonPath) {
70644
70885
  const sourceDir = dirname16(packageJsonPath);
@@ -70647,7 +70888,7 @@ function hashDepInputs(packageJsonPath) {
70647
70888
  `);
70648
70889
  hasher.update(readFileSync49(packageJsonPath));
70649
70890
  for (const lockName of ALL_LOCKFILES) {
70650
- const lockPath = join47(sourceDir, lockName);
70891
+ const lockPath = join48(sourceDir, lockName);
70651
70892
  if (existsSync53(lockPath)) {
70652
70893
  hasher.update(`
70653
70894
  `);
@@ -70667,10 +70908,10 @@ function ensureNodeEnv(opts) {
70667
70908
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
70668
70909
  }
70669
70910
  const sourceDir = dirname16(packageJsonPath);
70670
- const envDir = join47(cacheRoot, skillName);
70671
- const stampPath = join47(envDir, ".package.sha256");
70672
- const nodeModulesDir = join47(envDir, "node_modules");
70673
- const binDir = join47(nodeModulesDir, ".bin");
70911
+ const envDir = join48(cacheRoot, skillName);
70912
+ const stampPath = join48(envDir, ".package.sha256");
70913
+ const nodeModulesDir = join48(envDir, "node_modules");
70914
+ const binDir = join48(nodeModulesDir, ".bin");
70674
70915
  const targetHash = hashDepInputs(packageJsonPath);
70675
70916
  if (!force && existsSync53(stampPath) && existsSync53(nodeModulesDir)) {
70676
70917
  const existingHash = readFileSync49(stampPath, "utf8").trim();
@@ -70688,12 +70929,12 @@ function ensureNodeEnv(opts) {
70688
70929
  rmSync14(envDir, { recursive: true, force: true });
70689
70930
  }
70690
70931
  mkdirSync30(envDir, { recursive: true });
70691
- copyFileSync9(packageJsonPath, join47(envDir, "package.json"));
70932
+ copyFileSync9(packageJsonPath, join48(envDir, "package.json"));
70692
70933
  let copiedLockfile = false;
70693
70934
  for (const lockName of LOCKFILES_FOR[installer]) {
70694
- const lockPath = join47(sourceDir, lockName);
70935
+ const lockPath = join48(sourceDir, lockName);
70695
70936
  if (existsSync53(lockPath)) {
70696
- copyFileSync9(lockPath, join47(envDir, lockName));
70937
+ copyFileSync9(lockPath, join48(envDir, lockName));
70697
70938
  copiedLockfile = true;
70698
70939
  }
70699
70940
  }
@@ -70722,7 +70963,7 @@ function ensureNodeEnv(opts) {
70722
70963
 
70723
70964
  // src/cli/deps.ts
70724
70965
  function builtinSkillsRoot() {
70725
- return resolve34(homedir26(), ".switchroom/skills/_bundled");
70966
+ return resolve34(homedir28(), ".switchroom/skills/_bundled");
70726
70967
  }
70727
70968
  function registerDepsCommand(program3) {
70728
70969
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
@@ -70732,13 +70973,13 @@ function registerDepsCommand(program3) {
70732
70973
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
70733
70974
  process.exit(1);
70734
70975
  }
70735
- const skillDir = join48(skillsRoot, skill);
70976
+ const skillDir = join49(skillsRoot, skill);
70736
70977
  if (!existsSync54(skillDir)) {
70737
70978
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
70738
70979
  process.exit(1);
70739
70980
  }
70740
- const requirementsPath = join48(skillDir, "requirements.txt");
70741
- const packageJsonPath = join48(skillDir, "package.json");
70981
+ const requirementsPath = join49(skillDir, "requirements.txt");
70982
+ const packageJsonPath = join49(skillDir, "package.json");
70742
70983
  const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync54(requirementsPath));
70743
70984
  const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync54(packageJsonPath));
70744
70985
  let did = 0;
@@ -71693,7 +71934,7 @@ init_helpers();
71693
71934
  init_loader();
71694
71935
  init_merge();
71695
71936
  import { copyFileSync as copyFileSync10, existsSync as existsSync56, readFileSync as readFileSync50, writeFileSync as writeFileSync28 } from "node:fs";
71696
- import { join as join49, resolve as resolve36 } from "node:path";
71937
+ import { join as join50, resolve as resolve36 } from "node:path";
71697
71938
  init_schema();
71698
71939
  function resolveSoulTargetOrExit(program3, agentName) {
71699
71940
  const config = getConfig(program3);
@@ -71717,7 +71958,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
71717
71958
  profileName,
71718
71959
  profilePath,
71719
71960
  workspaceDir,
71720
- soulPath: join49(workspaceDir, "SOUL.md"),
71961
+ soulPath: join50(workspaceDir, "SOUL.md"),
71721
71962
  soul: merged.soul
71722
71963
  };
71723
71964
  }
@@ -71784,7 +72025,7 @@ function registerSoulCommand(program3) {
71784
72025
  init_helpers();
71785
72026
  init_loader();
71786
72027
  import { existsSync as existsSync57, readFileSync as readFileSync51, readdirSync as readdirSync19, statSync as statSync24 } from "node:fs";
71787
- import { resolve as resolve37, join as join50 } from "node:path";
72028
+ import { resolve as resolve37, join as join51 } from "node:path";
71788
72029
  import { createHash as createHash11 } from "node:crypto";
71789
72030
  init_merge();
71790
72031
  init_hindsight();
@@ -71798,7 +72039,7 @@ function sha256(content) {
71798
72039
  return createHash11("sha256").update(content).digest("hex").slice(0, 16);
71799
72040
  }
71800
72041
  function findLatestTranscriptJsonl(claudeConfigDir) {
71801
- const projectsDir = join50(claudeConfigDir, "projects");
72042
+ const projectsDir = join51(claudeConfigDir, "projects");
71802
72043
  if (!existsSync57(projectsDir))
71803
72044
  return;
71804
72045
  try {
@@ -71807,8 +72048,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
71807
72048
  for (const entry of entries) {
71808
72049
  if (!entry.isDirectory())
71809
72050
  continue;
71810
- const projectPath = join50(projectsDir, entry.name);
71811
- const transcriptPath = join50(projectPath, "transcript.jsonl");
72051
+ const projectPath = join51(projectsDir, entry.name);
72052
+ const transcriptPath = join51(projectPath, "transcript.jsonl");
71812
72053
  if (!existsSync57(transcriptPath))
71813
72054
  continue;
71814
72055
  const stat3 = statSync24(transcriptPath);
@@ -71877,11 +72118,11 @@ function registerDebugCommand(program3) {
71877
72118
  process.exit(1);
71878
72119
  }
71879
72120
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
71880
- const claudeConfigDir = join50(agentDir, ".claude");
71881
- const claudeMdPath = join50(agentDir, "CLAUDE.md");
71882
- const soulMdPath = join50(agentDir, "SOUL.md");
71883
- const workspaceSoulMdPath = join50(workspaceDir, "SOUL.md");
71884
- const handoffPath = join50(agentDir, ".handoff.md");
72121
+ const claudeConfigDir = join51(agentDir, ".claude");
72122
+ const claudeMdPath = join51(agentDir, "CLAUDE.md");
72123
+ const soulMdPath = join51(agentDir, "SOUL.md");
72124
+ const workspaceSoulMdPath = join51(workspaceDir, "SOUL.md");
72125
+ const handoffPath = join51(agentDir, ".handoff.md");
71885
72126
  const lastN = parseInt(opts.last, 10);
71886
72127
  if (isNaN(lastN) || lastN < 1) {
71887
72128
  console.error("--last must be a positive integer");
@@ -72031,8 +72272,8 @@ init_source();
72031
72272
  // src/worktree/claim.ts
72032
72273
  import { execFileSync as execFileSync16 } from "node:child_process";
72033
72274
  import { closeSync as closeSync11, mkdirSync as mkdirSync32, openSync as openSync11, existsSync as existsSync59, unlinkSync as unlinkSync12 } from "node:fs";
72034
- import { join as join52, resolve as resolve39 } from "node:path";
72035
- import { homedir as homedir28 } from "node:os";
72275
+ import { join as join53, resolve as resolve39 } from "node:path";
72276
+ import { homedir as homedir30 } from "node:os";
72036
72277
  import { randomBytes as randomBytes11 } from "node:crypto";
72037
72278
 
72038
72279
  // src/worktree/registry.ts
@@ -72045,13 +72286,13 @@ import {
72045
72286
  existsSync as existsSync58,
72046
72287
  renameSync as renameSync11
72047
72288
  } from "node:fs";
72048
- import { join as join51, resolve as resolve38 } from "node:path";
72049
- import { homedir as homedir27 } from "node:os";
72289
+ import { join as join52, resolve as resolve38 } from "node:path";
72290
+ import { homedir as homedir29 } from "node:os";
72050
72291
  function registryDir() {
72051
- return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join51(homedir27(), ".switchroom", "worktrees"));
72292
+ return resolve38(process.env.SWITCHROOM_WORKTREE_DIR ?? join52(homedir29(), ".switchroom", "worktrees"));
72052
72293
  }
72053
72294
  function recordPath(id) {
72054
- return join51(registryDir(), `${id}.json`);
72295
+ return join52(registryDir(), `${id}.json`);
72055
72296
  }
72056
72297
  function ensureDir2() {
72057
72298
  mkdirSync31(registryDir(), { recursive: true });
@@ -72102,7 +72343,7 @@ function acquireRepoLock(repoPath) {
72102
72343
  const lockDir = registryDir();
72103
72344
  mkdirSync32(lockDir, { recursive: true });
72104
72345
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
72105
- const lockPath = join52(lockDir, `.lock-${lockName}`);
72346
+ const lockPath = join53(lockDir, `.lock-${lockName}`);
72106
72347
  const deadline = Date.now() + 5000;
72107
72348
  let fd = null;
72108
72349
  while (fd === null) {
@@ -72129,7 +72370,7 @@ function acquireRepoLock(repoPath) {
72129
72370
  }
72130
72371
  var DEFAULT_CONCURRENCY = 5;
72131
72372
  function worktreesBaseDir() {
72132
- return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join52(homedir28(), ".switchroom", "worktree-checkouts"));
72373
+ return resolve39(process.env.SWITCHROOM_WORKTREE_BASE ?? join53(homedir30(), ".switchroom", "worktree-checkouts"));
72133
72374
  }
72134
72375
  function shortId() {
72135
72376
  return randomBytes11(4).toString("hex");
@@ -72151,7 +72392,7 @@ function resolveRepoPath(repo, codeRepos) {
72151
72392
  }
72152
72393
  function expandHome(p) {
72153
72394
  if (p.startsWith("~/"))
72154
- return join52(homedir28(), p.slice(2));
72395
+ return join53(homedir30(), p.slice(2));
72155
72396
  return p;
72156
72397
  }
72157
72398
  async function claimWorktree(input, codeRepos) {
@@ -72179,7 +72420,7 @@ async function claimWorktree(input, codeRepos) {
72179
72420
  branch = `task/${taskSuffix}-${id}`;
72180
72421
  const baseDir = worktreesBaseDir();
72181
72422
  mkdirSync32(baseDir, { recursive: true });
72182
- worktreePath = join52(baseDir, `${id}-${taskSuffix}`);
72423
+ worktreePath = join53(baseDir, `${id}-${taskSuffix}`);
72183
72424
  const now = new Date().toISOString();
72184
72425
  const record2 = {
72185
72426
  id,
@@ -72434,7 +72675,7 @@ import {
72434
72675
  rmSync as rmSync15,
72435
72676
  writeFileSync as writeFileSync30
72436
72677
  } from "node:fs";
72437
- import { join as join53 } from "node:path";
72678
+ import { join as join54 } from "node:path";
72438
72679
  function encodeCredentialsFilename(email) {
72439
72680
  const SAFE = new Set([
72440
72681
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -72598,16 +72839,16 @@ function resolveCredentialsDir(env2) {
72598
72839
  if (explicit && explicit.length > 0)
72599
72840
  return explicit;
72600
72841
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
72601
- return join53(stateBase, "google-workspace-mcp", "credentials");
72842
+ return join54(stateBase, "google-workspace-mcp", "credentials");
72602
72843
  }
72603
72844
  function writeSeedFile(dir, email, seed) {
72604
72845
  mkdirSync33(dir, { recursive: true, mode: 448 });
72605
72846
  chmodSync9(dir, 448);
72606
72847
  for (const name of readdirSync21(dir)) {
72607
- rmSync15(join53(dir, name), { force: true, recursive: true });
72848
+ rmSync15(join54(dir, name), { force: true, recursive: true });
72608
72849
  }
72609
72850
  const filename = encodeCredentialsFilename(email);
72610
- const filePath = join53(dir, filename);
72851
+ const filePath = join54(dir, filename);
72611
72852
  writeFileSync30(filePath, JSON.stringify(seed), { mode: 384 });
72612
72853
  chmodSync9(filePath, 384);
72613
72854
  return filePath;
@@ -73105,8 +73346,8 @@ agents:
73105
73346
 
73106
73347
  // src/cli/apply.ts
73107
73348
  init_resolver();
73108
- import { dirname as dirname19, join as join57, resolve as resolve41 } from "node:path";
73109
- import { homedir as homedir30 } from "node:os";
73349
+ import { dirname as dirname19, join as join58, resolve as resolve41 } from "node:path";
73350
+ import { homedir as homedir32 } from "node:os";
73110
73351
  import { execFileSync as execFileSync19 } from "node:child_process";
73111
73352
  init_vault();
73112
73353
  init_loader();
@@ -73114,7 +73355,7 @@ init_loader();
73114
73355
 
73115
73356
  // src/cli/update-prompt-hook.ts
73116
73357
  import { existsSync as existsSync62, readFileSync as readFileSync53, writeFileSync as writeFileSync31, chmodSync as chmodSync10, mkdirSync as mkdirSync34 } from "node:fs";
73117
- import { join as join54 } from "node:path";
73358
+ import { join as join55 } from "node:path";
73118
73359
  var HOOK_FILENAME = "update-card-on-prompt.sh";
73119
73360
  function updatePromptHookScript() {
73120
73361
  return `#!/bin/bash
@@ -73180,9 +73421,9 @@ exit 0
73180
73421
  `;
73181
73422
  }
73182
73423
  function installUpdatePromptHook(agentDir) {
73183
- const hooksDir = join54(agentDir, ".claude", "hooks");
73424
+ const hooksDir = join55(agentDir, ".claude", "hooks");
73184
73425
  mkdirSync34(hooksDir, { recursive: true });
73185
- const scriptPath = join54(hooksDir, HOOK_FILENAME);
73426
+ const scriptPath = join55(hooksDir, HOOK_FILENAME);
73186
73427
  const desired = updatePromptHookScript();
73187
73428
  let installed = false;
73188
73429
  const existing = existsSync62(scriptPath) ? readFileSync53(scriptPath, "utf-8") : "";
@@ -73195,7 +73436,7 @@ function installUpdatePromptHook(agentDir) {
73195
73436
  chmodSync10(scriptPath, 493);
73196
73437
  } catch {}
73197
73438
  }
73198
- const settingsPath = join54(agentDir, ".claude", "settings.json");
73439
+ const settingsPath = join55(agentDir, ".claude", "settings.json");
73199
73440
  if (!existsSync62(settingsPath)) {
73200
73441
  return { scriptPath, settingsPath, installed };
73201
73442
  }
@@ -73321,7 +73562,7 @@ import {
73321
73562
  realpathSync as realpathSync6,
73322
73563
  statSync as statSync25
73323
73564
  } from "node:fs";
73324
- import { join as join56 } from "node:path";
73565
+ import { join as join57 } from "node:path";
73325
73566
  function resolveOperatorUid() {
73326
73567
  const sudoUid = process.env.SUDO_UID;
73327
73568
  if (sudoUid !== undefined) {
@@ -73337,14 +73578,14 @@ function resolveOperatorUid() {
73337
73578
  return;
73338
73579
  }
73339
73580
  function operatorOwnedPaths(home2) {
73340
- const root = join56(home2, ".switchroom");
73581
+ const root = join57(home2, ".switchroom");
73341
73582
  return [
73342
- join56(root, "vault"),
73343
- join56(root, "vault-auto-unlock"),
73344
- join56(root, "vault-audit.log"),
73345
- join56(root, "host-control-audit.log"),
73346
- join56(root, "accounts"),
73347
- join56(root, "compose")
73583
+ join57(root, "vault"),
73584
+ join57(root, "vault-auto-unlock"),
73585
+ join57(root, "vault-audit.log"),
73586
+ join57(root, "host-control-audit.log"),
73587
+ join57(root, "accounts"),
73588
+ join57(root, "compose")
73348
73589
  ];
73349
73590
  }
73350
73591
  function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
@@ -73393,7 +73634,7 @@ function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
73393
73634
  } catch {}
73394
73635
  if (isDir(target)) {
73395
73636
  for (const entry of readdir2(target)) {
73396
- visit(join56(target, entry));
73637
+ visit(join57(target, entry));
73397
73638
  }
73398
73639
  }
73399
73640
  };
@@ -73407,14 +73648,14 @@ var EMBEDDED_EXAMPLES = {
73407
73648
  switchroom: switchroom_default,
73408
73649
  minimal: minimal_default
73409
73650
  };
73410
- var DEFAULT_COMPOSE_PATH2 = join57(homedir30(), ".switchroom", "compose", "docker-compose.yml");
73651
+ var DEFAULT_COMPOSE_PATH2 = join58(homedir32(), ".switchroom", "compose", "docker-compose.yml");
73411
73652
  var COMPOSE_PROJECT2 = "switchroom";
73412
73653
  function resolveVaultBindMountDir(homeDir, ctx) {
73413
73654
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
73414
73655
  if (isCustomPath && ctx.customVaultPath) {
73415
73656
  return dirname19(ctx.customVaultPath);
73416
73657
  }
73417
- return join57(homeDir, ".switchroom", "vault");
73658
+ return join58(homeDir, ".switchroom", "vault");
73418
73659
  }
73419
73660
  function inspectVaultBindMountDir(vaultDir) {
73420
73661
  if (!existsSync65(vaultDir))
@@ -73443,31 +73684,31 @@ function hasVaultRefs(value) {
73443
73684
  return false;
73444
73685
  }
73445
73686
  async function ensureHostMountSources(config) {
73446
- const home2 = homedir30();
73687
+ const home2 = homedir32();
73447
73688
  const dirs = [
73448
- join57(home2, ".switchroom", "approvals"),
73449
- join57(home2, ".switchroom", "scheduler"),
73450
- join57(home2, ".switchroom", "logs"),
73451
- join57(home2, ".switchroom", "compose"),
73452
- join57(home2, ".switchroom", "broker-operator")
73689
+ join58(home2, ".switchroom", "approvals"),
73690
+ join58(home2, ".switchroom", "scheduler"),
73691
+ join58(home2, ".switchroom", "logs"),
73692
+ join58(home2, ".switchroom", "compose"),
73693
+ join58(home2, ".switchroom", "broker-operator")
73453
73694
  ];
73454
73695
  for (const name of Object.keys(config.agents)) {
73455
- dirs.push(join57(home2, ".switchroom", "agents", name));
73456
- dirs.push(join57(home2, ".switchroom", "logs", name));
73457
- dirs.push(join57(home2, ".claude", "projects", name));
73696
+ dirs.push(join58(home2, ".switchroom", "agents", name));
73697
+ dirs.push(join58(home2, ".switchroom", "logs", name));
73698
+ dirs.push(join58(home2, ".claude", "projects", name));
73458
73699
  }
73459
73700
  for (const dir of dirs) {
73460
73701
  await mkdir(dir, { recursive: true });
73461
73702
  }
73462
- const autoUnlockPath = join57(home2, ".switchroom", "vault-auto-unlock");
73703
+ const autoUnlockPath = join58(home2, ".switchroom", "vault-auto-unlock");
73463
73704
  if (!existsSync65(autoUnlockPath)) {
73464
73705
  writeFileSync32(autoUnlockPath, "", { mode: 384 });
73465
73706
  }
73466
- const auditLogPath = join57(home2, ".switchroom", "vault-audit.log");
73707
+ const auditLogPath = join58(home2, ".switchroom", "vault-audit.log");
73467
73708
  if (!existsSync65(auditLogPath)) {
73468
73709
  writeFileSync32(auditLogPath, "", { mode: 420 });
73469
73710
  }
73470
- const hostdAuditLogPath = join57(home2, ".switchroom", "host-control-audit.log");
73711
+ const hostdAuditLogPath = join58(home2, ".switchroom", "host-control-audit.log");
73471
73712
  if (!existsSync65(hostdAuditLogPath)) {
73472
73713
  writeFileSync32(hostdAuditLogPath, "", { mode: 420 });
73473
73714
  }
@@ -73539,10 +73780,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
73539
73780
  `));
73540
73781
  }
73541
73782
  }
73542
- function writeInstallTypeCache(homeDir = homedir30()) {
73783
+ function writeInstallTypeCache(homeDir = homedir32()) {
73543
73784
  const ctx = detectInstallType();
73544
- const dir = join57(homeDir, ".switchroom");
73545
- const out = join57(dir, "install-type.json");
73785
+ const dir = join58(homeDir, ".switchroom");
73786
+ const out = join58(dir, "install-type.json");
73546
73787
  const tmp = `${out}.tmp`;
73547
73788
  mkdirSync35(dir, { recursive: true });
73548
73789
  const payload = {
@@ -73591,14 +73832,14 @@ Applying switchroom config...
73591
73832
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
73592
73833
  `));
73593
73834
  try {
73594
- installUpdatePromptHook(join57(agentsDir, name));
73835
+ installUpdatePromptHook(join58(agentsDir, name));
73595
73836
  } catch (hookErr) {
73596
73837
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
73597
73838
  `));
73598
73839
  }
73599
73840
  try {
73600
73841
  const uid = allocateAgentUid(name);
73601
- alignAgentUid(name, join57(agentsDir, name), uid, {
73842
+ alignAgentUid(name, join58(agentsDir, name), uid, {
73602
73843
  confirm: !options.nonInteractive,
73603
73844
  writeOut
73604
73845
  });
@@ -73635,7 +73876,7 @@ Applying switchroom config...
73635
73876
  for (const name of agentNames) {
73636
73877
  try {
73637
73878
  const uid = allocateAgentUid(name);
73638
- alignAgentUid(name, join57(agentsDir, name), uid, {
73879
+ alignAgentUid(name, join58(agentsDir, name), uid, {
73639
73880
  confirm: !options.nonInteractive,
73640
73881
  writeOut
73641
73882
  });
@@ -73649,7 +73890,7 @@ Applying switchroom config...
73649
73890
  }
73650
73891
  const vaultPathConfigured = config.vault?.path;
73651
73892
  const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
73652
- const migrationResult = migrateVaultLayout(homedir30(), {
73893
+ const migrationResult = migrateVaultLayout(homedir32(), {
73653
73894
  customVaultPath
73654
73895
  });
73655
73896
  switch (migrationResult.kind) {
@@ -73675,7 +73916,7 @@ Applying switchroom config...
73675
73916
  writeErr(formatDivergentRecoveryMessage(migrationResult.details));
73676
73917
  process.exit(4);
73677
73918
  }
73678
- const postMigrationInspect = inspectVaultLayout(homedir30());
73919
+ const postMigrationInspect = inspectVaultLayout(homedir32());
73679
73920
  const acceptable = [
73680
73921
  "no-vault",
73681
73922
  "already-migrated",
@@ -73690,7 +73931,7 @@ Applying switchroom config...
73690
73931
  `));
73691
73932
  process.exit(5);
73692
73933
  }
73693
- const vaultDir = resolveVaultBindMountDir(homedir30(), {
73934
+ const vaultDir = resolveVaultBindMountDir(homedir32(), {
73694
73935
  migrationKind: migrationResult.kind,
73695
73936
  customVaultPath
73696
73937
  });
@@ -73720,7 +73961,7 @@ Applying switchroom config...
73720
73961
  imageTag: composeImageTag,
73721
73962
  buildMode: options.buildLocal ? "local" : "pull",
73722
73963
  buildContext: options.buildContext,
73723
- homeDir: homedir30(),
73964
+ homeDir: homedir32(),
73724
73965
  switchroomConfigPath,
73725
73966
  operatorUid
73726
73967
  });
@@ -73740,7 +73981,7 @@ Wrote `) + composePath + source_default.gray(` (${composeBytes} bytes)
73740
73981
  writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
73741
73982
  `));
73742
73983
  if (process.geteuid?.() === 0 && operatorUid !== undefined) {
73743
- const restored = restoreOperatorOwnership(homedir30(), operatorUid);
73984
+ const restored = restoreOperatorOwnership(homedir32(), operatorUid);
73744
73985
  if (restored.length > 0) {
73745
73986
  writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
73746
73987
  `));
@@ -73810,7 +74051,7 @@ function findUnwritableAgentDirs(config, opts) {
73810
74051
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
73811
74052
  const unwritable = [];
73812
74053
  for (const name of targets) {
73813
- const startSh = join57(agentsDir, name, "start.sh");
74054
+ const startSh = join58(agentsDir, name, "start.sh");
73814
74055
  if (!existsSync65(startSh))
73815
74056
  continue;
73816
74057
  try {
@@ -73990,8 +74231,8 @@ function runRedactStdin() {
73990
74231
 
73991
74232
  // src/cli/status-ask.ts
73992
74233
  import { readFileSync as readFileSync54, existsSync as existsSync66, readdirSync as readdirSync24 } from "node:fs";
73993
- import { join as join58 } from "node:path";
73994
- import { homedir as homedir31 } from "node:os";
74234
+ import { join as join59 } from "node:path";
74235
+ import { homedir as homedir33 } from "node:os";
73995
74236
 
73996
74237
  // src/status-ask/report.ts
73997
74238
  function parseJsonl(content) {
@@ -74326,7 +74567,7 @@ function resolveSources(explicitPath) {
74326
74567
  const config = loadConfig();
74327
74568
  agentsDir = resolveAgentsDir(config);
74328
74569
  } catch {
74329
- agentsDir = join58(homedir31(), ".switchroom", "agents");
74570
+ agentsDir = join59(homedir33(), ".switchroom", "agents");
74330
74571
  }
74331
74572
  if (!existsSync66(agentsDir))
74332
74573
  return [];
@@ -74338,7 +74579,7 @@ function resolveSources(explicitPath) {
74338
74579
  return [];
74339
74580
  }
74340
74581
  for (const name of entries) {
74341
- const path8 = join58(agentsDir, name, "runtime-metrics.jsonl");
74582
+ const path8 = join59(agentsDir, name, "runtime-metrics.jsonl");
74342
74583
  if (existsSync66(path8)) {
74343
74584
  sources.push({ path: path8, agent: name });
74344
74585
  }
@@ -74360,17 +74601,17 @@ function inferAgentFromPath(p) {
74360
74601
 
74361
74602
  // src/cli/agent-config.ts
74362
74603
  init_helpers();
74363
- import { join as join59 } from "node:path";
74364
- import { homedir as homedir32 } from "node:os";
74604
+ import { join as join60 } from "node:path";
74605
+ import { homedir as homedir34 } from "node:os";
74365
74606
  import {
74366
74607
  existsSync as existsSync67,
74367
74608
  mkdirSync as mkdirSync36,
74368
74609
  appendFileSync as appendFileSync3,
74369
74610
  readFileSync as readFileSync55
74370
74611
  } from "node:fs";
74371
- var AUDIT_ROOT = join59(homedir32(), ".switchroom", "audit");
74612
+ var AUDIT_ROOT = join60(homedir34(), ".switchroom", "audit");
74372
74613
  function auditPathFor(agent) {
74373
- return join59(AUDIT_ROOT, agent, "agent-config.jsonl");
74614
+ return join60(AUDIT_ROOT, agent, "agent-config.jsonl");
74374
74615
  }
74375
74616
  function appendAudit(agent, cmd, args, exit, opts = {}) {
74376
74617
  const row = {
@@ -74628,21 +74869,21 @@ import {
74628
74869
  unlinkSync as unlinkSync13,
74629
74870
  writeSync as writeSync7
74630
74871
  } from "node:fs";
74631
- import { join as join60, resolve as resolve42 } from "node:path";
74872
+ import { join as join61, resolve as resolve42 } from "node:path";
74632
74873
  var STAGING_SUBDIR = ".staging";
74633
74874
  function overlayPathsFor(agent, opts = {}) {
74634
74875
  const base = opts.root ? resolve42(opts.root, agent) : resolve42(resolveDualPath(`~/.switchroom/agents/${agent}`));
74635
- const scheduleDir = join60(base, "schedule.d");
74636
- const scheduleStagingDir = join60(scheduleDir, STAGING_SUBDIR);
74637
- const skillsDir = join60(base, "skills.d");
74638
- const skillsStagingDir = join60(skillsDir, STAGING_SUBDIR);
74876
+ const scheduleDir = join61(base, "schedule.d");
74877
+ const scheduleStagingDir = join61(scheduleDir, STAGING_SUBDIR);
74878
+ const skillsDir = join61(base, "skills.d");
74879
+ const skillsStagingDir = join61(skillsDir, STAGING_SUBDIR);
74639
74880
  return {
74640
74881
  agentRoot: base,
74641
74882
  scheduleDir,
74642
74883
  scheduleStagingDir,
74643
74884
  skillsDir,
74644
74885
  skillsStagingDir,
74645
- lockPath: join60(base, ".lock"),
74886
+ lockPath: join61(base, ".lock"),
74646
74887
  stagingDir: scheduleStagingDir
74647
74888
  };
74648
74889
  }
@@ -74696,8 +74937,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
74696
74937
  const paths = overlayPathsFor(agent, opts);
74697
74938
  return withAgentLock(paths, () => {
74698
74939
  ensureDirs(paths);
74699
- const stagingPath = join60(paths.scheduleStagingDir, `${slug}.yaml`);
74700
- const finalPath = join60(paths.scheduleDir, `${slug}.yaml`);
74940
+ const stagingPath = join61(paths.scheduleStagingDir, `${slug}.yaml`);
74941
+ const finalPath = join61(paths.scheduleDir, `${slug}.yaml`);
74701
74942
  const fd = openSync12(stagingPath, "w", 384);
74702
74943
  try {
74703
74944
  writeSync7(fd, yamlText);
@@ -74713,8 +74954,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
74713
74954
  const paths = overlayPathsFor(agent, opts);
74714
74955
  return withAgentLock(paths, () => {
74715
74956
  ensureSkillsDirs(paths);
74716
- const stagingPath = join60(paths.skillsStagingDir, `${slug}.yaml`);
74717
- const finalPath = join60(paths.skillsDir, `${slug}.yaml`);
74957
+ const stagingPath = join61(paths.skillsStagingDir, `${slug}.yaml`);
74958
+ const finalPath = join61(paths.skillsDir, `${slug}.yaml`);
74718
74959
  const fd = openSync12(stagingPath, "w", 384);
74719
74960
  try {
74720
74961
  writeSync7(fd, yamlText);
@@ -74729,7 +74970,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
74729
74970
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
74730
74971
  const paths = overlayPathsFor(agent, opts);
74731
74972
  return withAgentLock(paths, () => {
74732
- const finalPath = join60(paths.skillsDir, `${slug}.yaml`);
74973
+ const finalPath = join61(paths.skillsDir, `${slug}.yaml`);
74733
74974
  if (!existsSync68(finalPath))
74734
74975
  return false;
74735
74976
  unlinkSync13(finalPath);
@@ -74744,7 +74985,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
74744
74985
  for (const name of readdirSync25(paths.skillsDir)) {
74745
74986
  if (!/\.ya?ml$/i.test(name))
74746
74987
  continue;
74747
- const full = join60(paths.skillsDir, name);
74988
+ const full = join61(paths.skillsDir, name);
74748
74989
  try {
74749
74990
  const raw = readFileSync56(full, "utf-8");
74750
74991
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -74756,7 +74997,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
74756
74997
  function deleteOverlayEntry(agent, slug, opts = {}) {
74757
74998
  const paths = overlayPathsFor(agent, opts);
74758
74999
  return withAgentLock(paths, () => {
74759
- const finalPath = join60(paths.scheduleDir, `${slug}.yaml`);
75000
+ const finalPath = join61(paths.scheduleDir, `${slug}.yaml`);
74760
75001
  if (!existsSync68(finalPath))
74761
75002
  return false;
74762
75003
  unlinkSync13(finalPath);
@@ -74771,7 +75012,7 @@ function listOverlayEntries(agent, opts = {}) {
74771
75012
  for (const name of readdirSync25(paths.scheduleDir)) {
74772
75013
  if (!/\.ya?ml$/i.test(name))
74773
75014
  continue;
74774
- const full = join60(paths.scheduleDir, name);
75015
+ const full = join61(paths.scheduleDir, name);
74775
75016
  try {
74776
75017
  const raw = readFileSync56(full, "utf-8");
74777
75018
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -74927,12 +75168,12 @@ import {
74927
75168
  writeFileSync as writeFileSync33,
74928
75169
  writeSync as writeSync8
74929
75170
  } from "node:fs";
74930
- import { join as join61 } from "node:path";
75171
+ import { join as join62 } from "node:path";
74931
75172
  import { randomBytes as randomBytes12 } from "node:crypto";
74932
75173
  var STAGE_ID_PREFIX = "cap_";
74933
75174
  function pendingDir(agent, opts = {}) {
74934
75175
  const paths = overlayPathsFor(agent, opts);
74935
- return join61(paths.scheduleDir, ".pending");
75176
+ return join62(paths.scheduleDir, ".pending");
74936
75177
  }
74937
75178
  function ensurePendingDir(agent, opts = {}) {
74938
75179
  const dir = pendingDir(agent, opts);
@@ -74945,8 +75186,8 @@ function newStageId() {
74945
75186
  function stagePendingScheduleEntry(opts) {
74946
75187
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
74947
75188
  const stageId = opts.stageId ?? newStageId();
74948
- const yamlPath = join61(dir, `${stageId}.yaml`);
74949
- const metaPath = join61(dir, `${stageId}.meta.json`);
75189
+ const yamlPath = join62(dir, `${stageId}.yaml`);
75190
+ const metaPath = join62(dir, `${stageId}.meta.json`);
74950
75191
  const meta = {
74951
75192
  v: 1,
74952
75193
  stage_id: stageId,
@@ -74980,8 +75221,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
74980
75221
  if (!name.endsWith(".meta.json"))
74981
75222
  continue;
74982
75223
  const stageId = name.slice(0, -".meta.json".length);
74983
- const metaPath = join61(dir, name);
74984
- const yamlPath = join61(dir, `${stageId}.yaml`);
75224
+ const metaPath = join62(dir, name);
75225
+ const yamlPath = join62(dir, `${stageId}.yaml`);
74985
75226
  if (!existsSync69(yamlPath))
74986
75227
  continue;
74987
75228
  try {
@@ -75000,7 +75241,7 @@ function commitPendingScheduleEntry(opts) {
75000
75241
  return { committed: false, reason: "not_found" };
75001
75242
  const slug = match.meta.entry.name ?? match.stageId;
75002
75243
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
75003
- const finalPath = join61(paths.scheduleDir, `${slug}.yaml`);
75244
+ const finalPath = join62(paths.scheduleDir, `${slug}.yaml`);
75004
75245
  if (existsSync69(finalPath)) {
75005
75246
  return { committed: false, reason: "slug_collision" };
75006
75247
  }
@@ -75453,7 +75694,7 @@ var import_yaml15 = __toESM(require_dist(), 1);
75453
75694
  import { existsSync as existsSync71 } from "node:fs";
75454
75695
  init_reconcile_default_skills();
75455
75696
  var import_yaml16 = __toESM(require_dist(), 1);
75456
- import { join as join62 } from "node:path";
75697
+ import { join as join63 } from "node:path";
75457
75698
  var MAX_SKILLS_PER_AGENT = 20;
75458
75699
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
75459
75700
  function exitCodeFor2(code) {
@@ -75528,7 +75769,7 @@ function skillInstall(opts) {
75528
75769
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
75529
75770
  }
75530
75771
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
75531
- const skillPath = join62(poolDir, skillName);
75772
+ const skillPath = join63(poolDir, skillName);
75532
75773
  if (!existsSync71(skillPath)) {
75533
75774
  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.`);
75534
75775
  }
@@ -75721,14 +75962,14 @@ import {
75721
75962
  unlinkSync as unlinkSync15
75722
75963
  } from "node:fs";
75723
75964
  import { createHash as createHash12 } from "node:crypto";
75724
- import { join as join63 } from "node:path";
75965
+ import { join as join64 } from "node:path";
75725
75966
  function planCronUnitRenames(agentsDir, agents) {
75726
75967
  const plans = [];
75727
75968
  for (const [agentName, agentConfig] of Object.entries(agents)) {
75728
75969
  const schedule = agentConfig.schedule ?? [];
75729
75970
  if (schedule.length === 0)
75730
75971
  continue;
75731
- const telegramDir = join63(agentsDir, agentName, "telegram");
75972
+ const telegramDir = join64(agentsDir, agentName, "telegram");
75732
75973
  if (!existsSync73(telegramDir))
75733
75974
  continue;
75734
75975
  let entries;
@@ -75750,8 +75991,8 @@ function planCronUnitRenames(agentsDir, agents) {
75750
75991
  continue;
75751
75992
  plans.push({
75752
75993
  agent: agentName,
75753
- from: join63(telegramDir, file),
75754
- to: join63(telegramDir, canonical),
75994
+ from: join64(telegramDir, file),
75995
+ to: join64(telegramDir, canonical),
75755
75996
  scheduleIdx: idx,
75756
75997
  entry
75757
75998
  });
@@ -75889,8 +76130,8 @@ function registerMigrateCommand(program3) {
75889
76130
  init_source();
75890
76131
  init_helpers();
75891
76132
  import { existsSync as existsSync74, mkdirSync as mkdirSync39, readdirSync as readdirSync28, readFileSync as readFileSync61, writeFileSync as writeFileSync34, statSync as statSync28, copyFileSync as copyFileSync12 } from "node:fs";
75892
- import { homedir as homedir33 } from "node:os";
75893
- import { join as join64 } from "node:path";
76133
+ import { homedir as homedir35 } from "node:os";
76134
+ import { join as join65 } from "node:path";
75894
76135
  import { spawnSync as spawnSync11 } from "node:child_process";
75895
76136
  init_audit_reader();
75896
76137
  var DEFAULT_IMAGE_TAG = "latest";
@@ -75981,10 +76222,10 @@ networks:
75981
76222
  `;
75982
76223
  }
75983
76224
  function hostdDir() {
75984
- return join64(homedir33(), ".switchroom", "hostd");
76225
+ return join65(homedir35(), ".switchroom", "hostd");
75985
76226
  }
75986
76227
  function hostdComposePath() {
75987
- return join64(hostdDir(), "docker-compose.yml");
76228
+ return join65(hostdDir(), "docker-compose.yml");
75988
76229
  }
75989
76230
  function backupExistingCompose() {
75990
76231
  const p = hostdComposePath();
@@ -76023,7 +76264,7 @@ async function doInstall(opts, program3) {
76023
76264
  const composePath = hostdComposePath();
76024
76265
  mkdirSync39(dir, { recursive: true });
76025
76266
  const yaml = renderHostdComposeFile({
76026
- hostHome: homedir33(),
76267
+ hostHome: homedir35(),
76027
76268
  imageTag: opts.tag ?? DEFAULT_IMAGE_TAG,
76028
76269
  operatorUid: resolveOperatorUid()
76029
76270
  });
@@ -76092,7 +76333,7 @@ function doStatus() {
76092
76333
  for (const name of readdirSync28(dir)) {
76093
76334
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
76094
76335
  continue;
76095
- const sockPath = join64(dir, name, "sock");
76336
+ const sockPath = join65(dir, name, "sock");
76096
76337
  if (existsSync74(sockPath)) {
76097
76338
  const st = statSync28(sockPath);
76098
76339
  if ((st.mode & 61440) === 49152) {