switchroom 0.18.29 → 0.18.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/bin/handoff-briefing.sh +8 -2
  2. package/dist/agent-scheduler/index.js +111 -7
  3. package/dist/auth-broker/index.js +154 -16
  4. package/dist/cli/autoaccept-poll.js +8 -3
  5. package/dist/cli/drive-write-pretool.mjs +8 -3
  6. package/dist/cli/ms-365-write-pretool.mjs +158 -11
  7. package/dist/cli/notion-write-pretool.mjs +103 -4
  8. package/dist/cli/switchroom.js +2074 -1585
  9. package/dist/host-control/main.js +110 -13
  10. package/dist/vault/approvals/kernel-server.js +116 -13
  11. package/dist/vault/broker/server.js +314 -145
  12. package/package.json +3 -3
  13. package/profiles/_base/start.sh.hbs +73 -20
  14. package/telegram-plugin/dist/bridge/bridge.js +71 -47
  15. package/telegram-plugin/dist/gateway/gateway.js +560 -96
  16. package/telegram-plugin/dist/server.js +89 -64
  17. package/telegram-plugin/gateway/gateway.ts +212 -17
  18. package/telegram-plugin/gateway/model-command.ts +104 -0
  19. package/telegram-plugin/gateway/session-model-file.ts +40 -0
  20. package/telegram-plugin/gateway/unhandled-message.ts +177 -0
  21. package/telegram-plugin/llm-error-present.ts +24 -0
  22. package/telegram-plugin/model-unavailable.ts +55 -0
  23. package/telegram-plugin/operator-events.ts +113 -0
  24. package/telegram-plugin/pending-user-notice.ts +88 -0
  25. package/telegram-plugin/shared/local-time.ts +43 -0
  26. package/telegram-plugin/tests/catch-all-forwarded-history.test.ts +103 -0
  27. package/telegram-plugin/tests/catch-all-unhandled-message.test.ts +264 -0
  28. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +26 -2
  29. package/telegram-plugin/tests/litellm-proxy-auth-misconfig.test.ts +278 -0
  30. package/telegram-plugin/tests/local-time.test.ts +68 -1
  31. package/telegram-plugin/tests/model-command.test.ts +133 -0
  32. package/telegram-plugin/tests/session-model-file.test.ts +23 -0
  33. package/vendor/hindsight-memory/scripts/backfill_transcripts.py +399 -2
  34. package/vendor/hindsight-memory/scripts/lib/client.py +47 -0
  35. package/vendor/hindsight-memory/scripts/lib/content.py +53 -1
  36. package/vendor/hindsight-memory/scripts/lib/turnlog.py +450 -0
  37. package/vendor/hindsight-memory/scripts/tests/test_backfill_from_logs.py +467 -0
  38. package/vendor/hindsight-memory/tests/test_content.py +35 -0
@@ -4013,7 +4013,7 @@ var init_zod = __esm(() => {
4013
4013
  });
4014
4014
 
4015
4015
  // src/config/schema.ts
4016
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4016
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4017
4017
  var init_schema = __esm(() => {
4018
4018
  init_zod();
4019
4019
  CodeRepoEntrySchema = exports_external.object({
@@ -4411,11 +4411,52 @@ var init_schema = __esm(() => {
4411
4411
  approvers: exports_external.array(ApproverIdSchema).min(1).optional().describe("Per-agent approver override. When set, replaces (does not extend) " + "the top-level drive.approvers list for this agent's onboarding card."),
4412
4412
  tier: GoogleWorkspaceTierSchema.optional().describe("Per-agent tier override (RFC G Phase 1). When set, replaces the " + "top-level google_workspace.tier for this agent. Common case: most " + "agents on `core`, one specialist on `extended` for Slides access.")
4413
4413
  }).optional();
4414
+ MicrosoftAccountEmailSchema = exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
4415
+ message: "microsoft_workspace.account must be a Microsoft account email like " + "'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
4416
+ }).transform((v) => v.trim().toLowerCase());
4417
+ MicrosoftToolTokenSchema = exports_external.string().min(1).regex(/^[a-z0-9-]+$/, {
4418
+ message: "microsoft_workspace tools[] tokens are forwarded to softeria's " + "--enabled-tools regex; each must be lowercase kebab-case " + "([a-z0-9-]+, e.g. 'mail', 'calendar', 'send-mail') so an unescaped " + "regex metacharacter can't crash the launcher"
4419
+ });
4420
+ MicrosoftAccountBindingSchema = exports_external.object({
4421
+ account: MicrosoftAccountEmailSchema.describe("The Microsoft account this binding uses. Must be a key in top-level " + "`microsoft_accounts:` with this agent in its `enabled_for[]`."),
4422
+ tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist → softeria `--enabled-tools <regex>` " + "(tokens joined with `|`). Omitted = all tools exposed for this account."),
4423
+ org_mode: exports_external.boolean().optional().describe("Per-binding org_mode override (RFC #1873 §6.4).")
4424
+ });
4414
4425
  AgentMicrosoftWorkspaceConfigSchema = exports_external.object({
4415
- account: exports_external.string().regex(/^[^@\s:]+@[^@\s:]+\.[^@\s:]+$/, {
4416
- message: "microsoft_workspace.account must be a Microsoft account email like " + "'alice@outlook.com' or 'alice@contoso.com' (colons not allowed)"
4417
- }).transform((v) => v.trim().toLowerCase()).optional().describe("RFC #1873: the Microsoft account this agent uses for the M365 MCP. " + "Must be a key in top-level `microsoft_accounts:` with this agent " + "listed in its `enabled_for[]`. Read by the auth-broker " + "(get-credentials, provider=microsoft) and by the scaffold to " + "decide whether to emit the `ms-365` MCP entry. Normalized to " + "lowercase so it matches the microsoft_accounts key (which is " + "also normalized)."),
4418
- org_mode: exports_external.boolean().optional().describe("Per-agent org_mode override (RFC #1873 §6.4). When set, replaces " + "the top-level microsoft_workspace.org_mode for this agent. " + "Defaults to top-level value (which defaults to false).")
4426
+ account: MicrosoftAccountEmailSchema.optional().describe("RFC #1873: the Microsoft account this agent uses for the M365 MCP. " + "Must be a key in top-level `microsoft_accounts:` with this agent " + "listed in its `enabled_for[]`. Read by the auth-broker " + "(get-credentials, provider=microsoft) and by the scaffold to " + "decide whether to emit the `ms-365` MCP entry. Normalized to " + "lowercase so it matches the microsoft_accounts key (which is " + "also normalized). Mutually exclusive with `accounts` (plural)."),
4427
+ tools: exports_external.array(MicrosoftToolTokenSchema).min(1).optional().describe("Per-account tool allowlist for the SINGULAR `account` form " + "softeria `--enabled-tools <regex>`. Omitted = all tools. Only " + "valid with the singular `account`; using it together with the " + "plural `accounts` (which carries per-binding `tools`) is an error."),
4428
+ org_mode: exports_external.boolean().optional().describe("Per-agent org_mode override (RFC #1873 §6.4). When set, replaces " + "the top-level microsoft_workspace.org_mode for this agent. " + "Defaults to top-level value (which defaults to false)."),
4429
+ accounts: exports_external.array(MicrosoftAccountBindingSchema).min(1).optional().describe("Plural multi-account form: bind MULTIPLE Microsoft accounts to " + "this agent, each with its own tool scope. Mutually exclusive with " + "the singular `account`. Each account gets its own `ms-365-<slug>` " + "MCP server.")
4430
+ }).superRefine((v, ctx) => {
4431
+ const hasSingular = v.account !== undefined;
4432
+ const hasPlural = v.accounts !== undefined;
4433
+ if (hasSingular && hasPlural) {
4434
+ ctx.addIssue({
4435
+ code: exports_external.ZodIssueCode.custom,
4436
+ message: "microsoft_workspace: use EITHER `account` (singular) OR " + "`accounts` (plural array), not both",
4437
+ path: ["accounts"]
4438
+ });
4439
+ }
4440
+ if (hasPlural && v.tools !== undefined) {
4441
+ ctx.addIssue({
4442
+ code: exports_external.ZodIssueCode.custom,
4443
+ message: "microsoft_workspace: block-level `tools` applies to the singular " + "`account` only; with `accounts` put `tools` inside each binding",
4444
+ path: ["tools"]
4445
+ });
4446
+ }
4447
+ if (hasPlural) {
4448
+ const seen = new Set;
4449
+ for (const b of v.accounts) {
4450
+ if (seen.has(b.account)) {
4451
+ ctx.addIssue({
4452
+ code: exports_external.ZodIssueCode.custom,
4453
+ message: `microsoft_workspace: duplicate account '${b.account}' in accounts[]`,
4454
+ path: ["accounts"]
4455
+ });
4456
+ }
4457
+ seen.add(b.account);
4458
+ }
4459
+ }
4419
4460
  }).optional();
4420
4461
  AgentNotionWorkspaceConfigSchema = exports_external.object({
4421
4462
  databases: exports_external.array(exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,62}$/, {
@@ -4790,6 +4831,26 @@ var init_schema = __esm(() => {
4790
4831
  for (const [name, a] of Object.entries(cfg.agents ?? {})) {
4791
4832
  checkServes(a.serves, ["agents", name, "serves"]);
4792
4833
  }
4834
+ const microsoftAccounts = cfg.microsoft_accounts;
4835
+ for (const [name, a] of Object.entries(cfg.agents ?? {})) {
4836
+ const mw = a.microsoft_workspace;
4837
+ const accounts = mw?.accounts;
4838
+ if (!accounts)
4839
+ continue;
4840
+ accounts.forEach((b, i) => {
4841
+ const acct = b?.account?.trim().toLowerCase();
4842
+ if (!acct)
4843
+ return;
4844
+ const enabledFor = microsoftAccounts?.[acct]?.enabled_for ?? [];
4845
+ if (!enabledFor.includes(name)) {
4846
+ ctx.addIssue({
4847
+ code: exports_external.ZodIssueCode.custom,
4848
+ message: `agent '${name}' binds Microsoft account '${acct}' but is not in ` + `microsoft_accounts['${acct}'].enabled_for[] — operator must run ` + `\`switchroom auth microsoft enable ${acct} ${name}\``,
4849
+ path: ["agents", name, "microsoft_workspace", "accounts", i, "account"]
4850
+ });
4851
+ }
4852
+ });
4853
+ }
4793
4854
  });
4794
4855
  });
4795
4856
 
@@ -5184,6 +5245,25 @@ var init_merge = __esm(() => {
5184
5245
  })(mergeAgentConfig ||= {});
5185
5246
  });
5186
5247
 
5248
+ // src/config/timezone.ts
5249
+ function isResolvableTimezone(zone) {
5250
+ try {
5251
+ new Intl.DateTimeFormat("en-US", { timeZone: zone });
5252
+ return true;
5253
+ } catch {
5254
+ return false;
5255
+ }
5256
+ }
5257
+ var CONTAINER_DEFAULT_UTC_ZONES;
5258
+ var init_timezone = __esm(() => {
5259
+ CONTAINER_DEFAULT_UTC_ZONES = new Set([
5260
+ "UTC",
5261
+ "Etc/UTC",
5262
+ "Etc/Universal",
5263
+ "Universal"
5264
+ ]);
5265
+ });
5266
+
5187
5267
  // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
5188
5268
  var require_identity = __commonJS((exports) => {
5189
5269
  var ALIAS = Symbol.for("yaml.alias");
@@ -12492,8 +12572,30 @@ function loadConfig(configPath) {
12492
12572
  if (notionIssues.length > 0) {
12493
12573
  throw new ConfigError(`Invalid notion_workspace configuration in ${filePath}`, notionIssues);
12494
12574
  }
12575
+ validateAllTimezones(config, filePath);
12495
12576
  return config;
12496
12577
  }
12578
+ function validateAllTimezones(config, filePath) {
12579
+ const issues = [];
12580
+ const check = (zone, where) => {
12581
+ if (zone == null)
12582
+ return;
12583
+ if (!isResolvableTimezone(zone)) {
12584
+ issues.push(` ${where}: "${zone}" is not a resolvable IANA timezone ` + `(shape is valid but no such zone exists — check for a typo, ` + `e.g. "Australia/Melbourne", "America/New_York", "UTC").`);
12585
+ }
12586
+ };
12587
+ check(config.switchroom?.timezone, "switchroom.timezone");
12588
+ check(config.defaults?.timezone, "defaults.timezone");
12589
+ for (const [profileName, profile] of Object.entries(config.profiles ?? {})) {
12590
+ check(profile?.timezone, `profiles.${profileName}.timezone`);
12591
+ }
12592
+ for (const [agentName, agentRaw] of Object.entries(config.agents)) {
12593
+ check(agentRaw?.timezone, `agents.${agentName}.timezone`);
12594
+ }
12595
+ if (issues.length > 0) {
12596
+ throw new ConfigError(`Invalid timezone configuration in ${filePath}`, issues);
12597
+ }
12598
+ }
12497
12599
  function validateAllCronTopicAliases(config, filePath) {
12498
12600
  const issues = [];
12499
12601
  for (const [agentName, agentRaw] of Object.entries(config.agents)) {
@@ -12536,6 +12638,7 @@ var init_loader = __esm(() => {
12536
12638
  init_paths();
12537
12639
  init_overlay_loader();
12538
12640
  init_merge();
12641
+ init_timezone();
12539
12642
  import_yaml2 = __toESM(require_dist(), 1);
12540
12643
  ConfigError = class ConfigError extends Error {
12541
12644
  details;
@@ -18109,7 +18212,7 @@ var require_lib = __commonJS((exports, module) => {
18109
18212
 
18110
18213
  // src/vault/broker/server.ts
18111
18214
  import * as net from "node:net";
18112
- import { mkdirSync as mkdirSync6, chmodSync as chmodSync4, chownSync, existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync6, unlinkSync as unlinkSync6, writeFileSync as writeFileSync3, renameSync as renameSync7 } from "node:fs";
18215
+ import { mkdirSync as mkdirSync7, chmodSync as chmodSync5, chownSync, existsSync as existsSync12, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync6, unlinkSync as unlinkSync6, writeFileSync as writeFileSync3, renameSync as renameSync8 } from "node:fs";
18113
18216
 
18114
18217
  // src/agents/compose.ts
18115
18218
  init_schema();
@@ -18473,14 +18576,7 @@ init_merge();
18473
18576
 
18474
18577
  // src/agents/scaffold.ts
18475
18578
  init_merge();
18476
-
18477
- // src/config/timezone.ts
18478
- var CONTAINER_DEFAULT_UTC_ZONES = new Set([
18479
- "UTC",
18480
- "Etc/UTC",
18481
- "Etc/Universal",
18482
- "Universal"
18483
- ]);
18579
+ init_timezone();
18484
18580
 
18485
18581
  // src/cli/agent-config.ts
18486
18582
  import { join } from "node:path";
@@ -19531,42 +19627,108 @@ var DOCKER_HOOKS_PATH = `${DOCKER_TELEGRAM_PLUGIN_PATH}/hooks`;
19531
19627
 
19532
19628
  // src/agents/compose.ts
19533
19629
  init_merge();
19630
+ init_timezone();
19534
19631
 
19535
19632
  // src/setup/host-capabilities.ts
19536
19633
  init_paths();
19537
19634
 
19635
+ // src/vault/grants-db-path.ts
19636
+ import * as os from "node:os";
19637
+ import * as path from "node:path";
19638
+ import * as fs from "node:fs";
19639
+ var GRANTS_DB_DIRNAME = "vault-broker";
19640
+ var GRANTS_DB_FILENAME = "vault-grants.db";
19641
+ var GRANTS_DB_CONTAINER_DIR = `/root/.switchroom/${GRANTS_DB_DIRNAME}`;
19642
+ var GRANTS_DB_CONTAINER_PATH = `${GRANTS_DB_CONTAINER_DIR}/${GRANTS_DB_FILENAME}`;
19643
+ function getGrantsDbDir(home2 = os.homedir()) {
19644
+ return path.join(home2, ".switchroom", GRANTS_DB_DIRNAME);
19645
+ }
19646
+ function getGrantsDbPath(home2 = os.homedir()) {
19647
+ return path.join(getGrantsDbDir(home2), GRANTS_DB_FILENAME);
19648
+ }
19649
+ function getLegacyGrantsDbPath(newDbPath = getGrantsDbPath()) {
19650
+ return path.join(path.dirname(path.dirname(newDbPath)), GRANTS_DB_FILENAME);
19651
+ }
19652
+ var WAL_SIDECAR_SUFFIXES = ["-wal", "-shm"];
19653
+ function migrateLegacyGrantsDbLocation(newDbPath = getGrantsDbPath(), deps) {
19654
+ const rename = deps?.rename ?? fs.renameSync;
19655
+ const legacyPath = getLegacyGrantsDbPath(newDbPath);
19656
+ if (path.resolve(legacyPath) === path.resolve(newDbPath)) {
19657
+ return { migrated: false };
19658
+ }
19659
+ if (!fs.existsSync(legacyPath))
19660
+ return { migrated: false };
19661
+ if (fs.existsSync(newDbPath))
19662
+ return { migrated: false };
19663
+ fs.mkdirSync(path.dirname(newDbPath), { recursive: true, mode: 448 });
19664
+ const movedSidecars = [];
19665
+ const rollbackSidecars = () => {
19666
+ for (const m of [...movedSidecars].reverse()) {
19667
+ try {
19668
+ rename(m.to, m.from);
19669
+ } catch {}
19670
+ }
19671
+ };
19672
+ for (const suffix of WAL_SIDECAR_SUFFIXES) {
19673
+ const from = `${legacyPath}${suffix}`;
19674
+ const to = `${newDbPath}${suffix}`;
19675
+ if (!fs.existsSync(from))
19676
+ continue;
19677
+ try {
19678
+ rename(from, to);
19679
+ movedSidecars.push({ from, to });
19680
+ } catch (err) {
19681
+ rollbackSidecars();
19682
+ throw new Error(`grants-db migration: failed to move WAL sidecar ${from} -> ${to} ` + `(${err.message}); aborted with the legacy DB intact — ` + `will retry on the next apply/boot`);
19683
+ }
19684
+ }
19685
+ try {
19686
+ rename(legacyPath, newDbPath);
19687
+ } catch (err) {
19688
+ if (fs.existsSync(newDbPath) && !fs.existsSync(legacyPath)) {
19689
+ return { migrated: false };
19690
+ }
19691
+ rollbackSidecars();
19692
+ throw err;
19693
+ }
19694
+ try {
19695
+ fs.chmodSync(newDbPath, 384);
19696
+ } catch {}
19697
+ return { migrated: true, from: legacyPath, to: newDbPath };
19698
+ }
19699
+
19538
19700
  // src/agents/compose.ts
19539
19701
  var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
19540
19702
 
19541
19703
  // src/vault/broker/server.ts
19542
- import { dirname as dirname5, resolve as resolve8, basename as basename4 } from "node:path";
19704
+ import { dirname as dirname6, resolve as resolve9, basename as basename4 } from "node:path";
19543
19705
  import * as os3 from "node:os";
19544
- import * as path3 from "node:path";
19706
+ import * as path4 from "node:path";
19545
19707
 
19546
19708
  // src/vault/migrate-layout.ts
19547
19709
  import {
19548
19710
  copyFileSync as copyFileSync2,
19549
- chmodSync,
19550
- existsSync as existsSync7,
19711
+ chmodSync as chmodSync2,
19712
+ existsSync as existsSync8,
19551
19713
  fsyncSync as fsyncSync4,
19552
19714
  lstatSync as lstatSync2,
19553
- mkdirSync as mkdirSync3,
19715
+ mkdirSync as mkdirSync4,
19554
19716
  openSync as openSync4,
19555
19717
  closeSync as closeSync4,
19556
19718
  readFileSync as readFileSync7,
19557
- renameSync as renameSync3,
19719
+ renameSync as renameSync4,
19558
19720
  statSync as statSync4,
19559
19721
  symlinkSync,
19560
19722
  unlinkSync as unlinkSync3
19561
19723
  } from "node:fs";
19562
19724
  import { createHash as createHash2 } from "node:crypto";
19563
- import { basename as basename3, dirname as dirname2, join as join5 } from "node:path";
19725
+ import { basename as basename3, dirname as dirname3, join as join6 } from "node:path";
19564
19726
  function vaultLayoutPaths(home2) {
19565
- const switchroomRoot = join5(home2, ".switchroom");
19727
+ const switchroomRoot = join6(home2, ".switchroom");
19566
19728
  return {
19567
- oldPath: join5(switchroomRoot, "vault.enc"),
19568
- newPath: join5(switchroomRoot, "vault", "vault.enc"),
19569
- parent: join5(switchroomRoot, "vault"),
19729
+ oldPath: join6(switchroomRoot, "vault.enc"),
19730
+ newPath: join6(switchroomRoot, "vault", "vault.enc"),
19731
+ parent: join6(switchroomRoot, "vault"),
19570
19732
  switchroomRoot
19571
19733
  };
19572
19734
  }
@@ -19575,11 +19737,11 @@ function inspectVaultLayout(home2) {
19575
19737
  }
19576
19738
  function runMigration(home2, opts) {
19577
19739
  const { oldPath, newPath, parent, switchroomRoot } = vaultLayoutPaths(home2);
19578
- const lockTarget = existsSync7(newPath) ? newPath : existsSync7(oldPath) ? oldPath : null;
19740
+ const lockTarget = existsSync8(newPath) ? newPath : existsSync8(oldPath) ? oldPath : null;
19579
19741
  const release = !opts.dryRun && lockTarget !== null ? acquireVaultLock(lockTarget) : null;
19580
19742
  try {
19581
19743
  const oldStat = lstatSyncOrNull(oldPath);
19582
- const newExists = existsSync7(newPath);
19744
+ const newExists = existsSync8(newPath);
19583
19745
  if (oldStat === null && !newExists) {
19584
19746
  return { kind: "no-vault" };
19585
19747
  }
@@ -19615,12 +19777,12 @@ function runMigration(home2, opts) {
19615
19777
  if (oldStat?.isFile() && !newExists) {
19616
19778
  if (opts.dryRun)
19617
19779
  return { kind: "migrated" };
19618
- mkdirSync3(parent, { recursive: true, mode: 448 });
19780
+ mkdirSync4(parent, { recursive: true, mode: 448 });
19619
19781
  const tempNew = `${newPath}.tmp`;
19620
19782
  copyFileSync2(oldPath, tempNew);
19621
- chmodSync(tempNew, 384);
19783
+ chmodSync2(tempNew, 384);
19622
19784
  fsyncFile(tempNew);
19623
- renameSync3(tempNew, newPath);
19785
+ renameSync4(tempNew, newPath);
19624
19786
  fsyncDir(parent);
19625
19787
  atomicReplaceWithSymlink(oldPath, "vault/vault.enc");
19626
19788
  fsyncDir(switchroomRoot);
@@ -19635,37 +19797,37 @@ function runMigration(home2, opts) {
19635
19797
  }
19636
19798
  }
19637
19799
  }
19638
- function lstatSyncOrNull(path) {
19800
+ function lstatSyncOrNull(path2) {
19639
19801
  try {
19640
- return lstatSync2(path);
19802
+ return lstatSync2(path2);
19641
19803
  } catch {
19642
19804
  return null;
19643
19805
  }
19644
19806
  }
19645
- function sha256File(path) {
19646
- const data = readFileSync7(path);
19807
+ function sha256File(path2) {
19808
+ const data = readFileSync7(path2);
19647
19809
  return createHash2("sha256").update(data).digest("hex");
19648
19810
  }
19649
19811
  function atomicReplaceWithSymlink(target, linkTarget) {
19650
- const tmp = join5(dirname2(target), `.${basename3(target)}.symlink-tmp`);
19651
- if (existsSync7(tmp)) {
19812
+ const tmp = join6(dirname3(target), `.${basename3(target)}.symlink-tmp`);
19813
+ if (existsSync8(tmp)) {
19652
19814
  try {
19653
19815
  unlinkSync3(tmp);
19654
19816
  } catch {}
19655
19817
  }
19656
19818
  symlinkSync(linkTarget, tmp);
19657
- renameSync3(tmp, target);
19819
+ renameSync4(tmp, target);
19658
19820
  }
19659
- function fsyncFile(path) {
19660
- const fd = openSync4(path, "r+");
19821
+ function fsyncFile(path2) {
19822
+ const fd = openSync4(path2, "r+");
19661
19823
  try {
19662
19824
  fsyncSync4(fd);
19663
19825
  } finally {
19664
19826
  closeSync4(fd);
19665
19827
  }
19666
19828
  }
19667
- function fsyncDir(path) {
19668
- const fd = openSync4(path, "r");
19829
+ function fsyncDir(path2) {
19830
+ const fd = openSync4(path2, "r");
19669
19831
  try {
19670
19832
  fsyncSync4(fd);
19671
19833
  } finally {
@@ -19679,14 +19841,14 @@ init_loader();
19679
19841
  // src/vault/auto-unlock.ts
19680
19842
  import { createHmac, randomBytes as randomBytes2, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "node:crypto";
19681
19843
  import {
19682
- chmodSync as chmodSync2,
19844
+ chmodSync as chmodSync3,
19683
19845
  closeSync as closeSync5,
19684
- existsSync as existsSync8,
19846
+ existsSync as existsSync9,
19685
19847
  fsyncSync as fsyncSync5,
19686
- mkdirSync as mkdirSync4,
19848
+ mkdirSync as mkdirSync5,
19687
19849
  openSync as openSync5,
19688
19850
  readFileSync as readFileSync8,
19689
- renameSync as renameSync4,
19851
+ renameSync as renameSync5,
19690
19852
  unlinkSync as unlinkSync4,
19691
19853
  writeSync as writeSync4
19692
19854
  } from "node:fs";
@@ -19722,9 +19884,9 @@ function readMachineId() {
19722
19884
  if (override && override.length > 0)
19723
19885
  return override;
19724
19886
  }
19725
- for (const path of [MACHINE_ID_PRIMARY, MACHINE_ID_FALLBACK]) {
19887
+ for (const path2 of [MACHINE_ID_PRIMARY, MACHINE_ID_FALLBACK]) {
19726
19888
  try {
19727
- const id = readFileSync8(path, "utf8").trim();
19889
+ const id = readFileSync8(path2, "utf8").trim();
19728
19890
  if (id.length > 0)
19729
19891
  return id;
19730
19892
  } catch {}
@@ -19761,7 +19923,7 @@ function decryptAutoUnlock(blob, machineId) {
19761
19923
  }
19762
19924
  }
19763
19925
  function readAutoUnlockFile(filePath) {
19764
- if (!existsSync8(filePath)) {
19926
+ if (!existsSync9(filePath)) {
19765
19927
  throw new AutoUnlockDecryptError("io");
19766
19928
  }
19767
19929
  let blob;
@@ -19775,9 +19937,9 @@ function readAutoUnlockFile(filePath) {
19775
19937
  var DEFAULT_AUTO_UNLOCK_PATH = "~/.switchroom/vault-auto-unlock";
19776
19938
 
19777
19939
  // src/vault/broker/audit-log.ts
19778
- import * as fs from "node:fs";
19779
- import * as os from "node:os";
19780
- import * as path from "node:path";
19940
+ import * as fs2 from "node:fs";
19941
+ import * as os2 from "node:os";
19942
+ import * as path2 from "node:path";
19781
19943
 
19782
19944
  // src/util/audit-hashchain.ts
19783
19945
  import { createHash as createHash4 } from "node:crypto";
@@ -19799,10 +19961,10 @@ function chainRow(state, entry) {
19799
19961
  next: { seq, lastHash: hash }
19800
19962
  };
19801
19963
  }
19802
- function readTail(path, tailBytes = 64 * 1024) {
19964
+ function readTail(path2, tailBytes = 64 * 1024) {
19803
19965
  let fd;
19804
19966
  try {
19805
- fd = openSync6(path, "r");
19967
+ fd = openSync6(path2, "r");
19806
19968
  } catch {
19807
19969
  return null;
19808
19970
  }
@@ -19822,8 +19984,8 @@ function readTail(path, tailBytes = 64 * 1024) {
19822
19984
  } catch {}
19823
19985
  }
19824
19986
  }
19825
- function seedChain(path) {
19826
- const tail = readTail(path);
19987
+ function seedChain(path2) {
19988
+ const tail = readTail(path2);
19827
19989
  if (tail === null || tail === "") {
19828
19990
  return { seq: 0, lastHash: CHAIN_GENESIS };
19829
19991
  }
@@ -19846,8 +20008,8 @@ function seedChain(path) {
19846
20008
 
19847
20009
  // src/vault/broker/test-isolation-guard.ts
19848
20010
  import { mkdtempSync } from "node:fs";
19849
- import { homedir as homedir4, tmpdir } from "node:os";
19850
- import { join as join6, resolve as resolve7, sep } from "node:path";
20011
+ import { homedir as homedir5, tmpdir } from "node:os";
20012
+ import { join as join7, resolve as resolve8, sep } from "node:path";
19851
20013
  function isTestRuntime() {
19852
20014
  return process.env.VITEST !== undefined;
19853
20015
  }
@@ -19855,11 +20017,11 @@ function escapeHatchSet() {
19855
20017
  return process.env.SWITCHROOM_ALLOW_PROD_VAULT_IN_TEST === "1";
19856
20018
  }
19857
20019
  function realSwitchroomHome() {
19858
- return resolve7(homedir4(), ".switchroom");
20020
+ return resolve8(homedir5(), ".switchroom");
19859
20021
  }
19860
20022
  function isUnderRealSwitchroomHome(p) {
19861
20023
  const home2 = realSwitchroomHome();
19862
- const r = resolve7(p);
20024
+ const r = resolve8(p);
19863
20025
  return r === home2 || r.startsWith(home2 + sep);
19864
20026
  }
19865
20027
  var warnedVault = false;
@@ -19868,7 +20030,7 @@ function safeVaultPath(requestedPath) {
19868
20030
  return requestedPath;
19869
20031
  if (!isUnderRealSwitchroomHome(requestedPath))
19870
20032
  return requestedPath;
19871
- const redirected = join6(mkdtempSync(join6(tmpdir(), "sw-test-vault-")), "vault.enc");
20033
+ const redirected = join7(mkdtempSync(join7(tmpdir(), "sw-test-vault-")), "vault.enc");
19872
20034
  if (!warnedVault) {
19873
20035
  warnedVault = true;
19874
20036
  process.stderr.write(`[test-isolation guard] vault path redirected away from the ` + `production tree to an isolated tmpdir — a vault/broker test ` + `resolved its vault path inside ~/.switchroom. See CLAUDE.md ` + `"Vault & shared-state test discipline".
@@ -19884,7 +20046,7 @@ function safeAuditLogPath(requestedPath) {
19884
20046
  if (!isUnderRealSwitchroomHome(requestedPath))
19885
20047
  return requestedPath;
19886
20048
  if (!cachedTmpAuditLog) {
19887
- cachedTmpAuditLog = join6(mkdtempSync(join6(tmpdir(), "sw-test-audit-")), "vault-audit.log");
20049
+ cachedTmpAuditLog = join7(mkdtempSync(join7(tmpdir(), "sw-test-audit-")), "vault-audit.log");
19888
20050
  }
19889
20051
  if (!warnedAudit) {
19890
20052
  warnedAudit = true;
@@ -19900,7 +20062,7 @@ function failOpenStatePath(logPath) {
19900
20062
  }
19901
20063
  function readFailOpenState(statePath) {
19902
20064
  try {
19903
- const raw = fs.readFileSync(statePath, "utf8");
20065
+ const raw = fs2.readFileSync(statePath, "utf8");
19904
20066
  const parsed = JSON.parse(raw);
19905
20067
  const count = Number(parsed.failOpenCount);
19906
20068
  return {
@@ -19923,9 +20085,9 @@ function resolveRotation(opts) {
19923
20085
  }
19924
20086
  function rotateAuditLog(logPath, maxFiles) {
19925
20087
  const oldest = `${logPath}.${maxFiles}`;
19926
- if (fs.existsSync(oldest)) {
20088
+ if (fs2.existsSync(oldest)) {
19927
20089
  try {
19928
- fs.unlinkSync(oldest);
20090
+ fs2.unlinkSync(oldest);
19929
20091
  } catch (err) {
19930
20092
  process.stderr.write(`[vault-audit] ERROR: could not drop oldest rotation ${oldest}: ${err.message}
19931
20093
  `);
@@ -19934,10 +20096,10 @@ function rotateAuditLog(logPath, maxFiles) {
19934
20096
  for (let n = maxFiles - 1;n >= 1; n--) {
19935
20097
  const from = `${logPath}.${n}`;
19936
20098
  const to = `${logPath}.${n + 1}`;
19937
- if (!fs.existsSync(from))
20099
+ if (!fs2.existsSync(from))
19938
20100
  continue;
19939
20101
  try {
19940
- fs.renameSync(from, to);
20102
+ fs2.renameSync(from, to);
19941
20103
  } catch (err) {
19942
20104
  process.stderr.write(`[vault-audit] ERROR: could not rotate ${from} → ${to}: ${err.message}
19943
20105
  `);
@@ -19946,18 +20108,18 @@ function rotateAuditLog(logPath, maxFiles) {
19946
20108
  }
19947
20109
  const snapshotPath = `${logPath}.1`;
19948
20110
  try {
19949
- fs.copyFileSync(logPath, snapshotPath);
20111
+ fs2.copyFileSync(logPath, snapshotPath);
19950
20112
  } catch (err) {
19951
20113
  process.stderr.write(`[vault-audit] ERROR: could not snapshot active audit log ${logPath} → ${snapshotPath}: ${err.message}
19952
20114
  `);
19953
20115
  return;
19954
20116
  }
19955
20117
  try {
19956
- const fd = fs.openSync(snapshotPath, "r");
20118
+ const fd = fs2.openSync(snapshotPath, "r");
19957
20119
  try {
19958
- fs.fsyncSync(fd);
20120
+ fs2.fsyncSync(fd);
19959
20121
  } finally {
19960
- fs.closeSync(fd);
20122
+ fs2.closeSync(fd);
19961
20123
  }
19962
20124
  } catch (err) {
19963
20125
  process.stderr.write(`[vault-audit] ERROR: could not fsync audit snapshot ${snapshotPath}; leaving active log intact to avoid data loss: ${err.message}
@@ -19965,22 +20127,22 @@ function rotateAuditLog(logPath, maxFiles) {
19965
20127
  return;
19966
20128
  }
19967
20129
  try {
19968
- const dirFd = fs.openSync(path.dirname(snapshotPath), "r");
20130
+ const dirFd = fs2.openSync(path2.dirname(snapshotPath), "r");
19969
20131
  try {
19970
- fs.fsyncSync(dirFd);
20132
+ fs2.fsyncSync(dirFd);
19971
20133
  } finally {
19972
- fs.closeSync(dirFd);
20134
+ fs2.closeSync(dirFd);
19973
20135
  }
19974
20136
  } catch {}
19975
20137
  try {
19976
- fs.truncateSync(logPath, 0);
20138
+ fs2.truncateSync(logPath, 0);
19977
20139
  } catch (err) {
19978
20140
  process.stderr.write(`[vault-audit] ERROR: could not truncate active audit log ${logPath}: ${err.message}
19979
20141
  `);
19980
20142
  }
19981
20143
  }
19982
20144
  function defaultAuditLogPath() {
19983
- return path.join(os.homedir(), ".switchroom", "vault-audit.log");
20145
+ return path2.join(os2.homedir(), ".switchroom", "vault-audit.log");
19984
20146
  }
19985
20147
  function callerFromPeer(peer) {
19986
20148
  if (peer.systemdUnit !== null && peer.systemdUnit.length > 0) {
@@ -20002,7 +20164,7 @@ function createAuditLogger(opts = {}) {
20002
20164
  lastError: String(err.message).slice(0, 300)
20003
20165
  };
20004
20166
  try {
20005
- fs.writeFileSync(statePath, `${JSON.stringify(state)}
20167
+ fs2.writeFileSync(statePath, `${JSON.stringify(state)}
20006
20168
  `, { mode: 384 });
20007
20169
  } catch (persistErr) {
20008
20170
  process.stderr.write(`[vault-audit] ERROR: could not persist fail-open counter ${statePath}: ${persistErr.message}
@@ -20016,7 +20178,7 @@ function createAuditLogger(opts = {}) {
20016
20178
  write(entry) {
20017
20179
  if (rotation.maxBytes > 0) {
20018
20180
  try {
20019
- const size = fs.statSync(logPath).size;
20181
+ const size = fs2.statSync(logPath).size;
20020
20182
  if (size >= rotation.maxBytes) {
20021
20183
  rotateAuditLog(logPath, rotation.maxFiles);
20022
20184
  }
@@ -20025,7 +20187,7 @@ function createAuditLogger(opts = {}) {
20025
20187
  const { line, next } = chainRow(chain, entry);
20026
20188
  let fd;
20027
20189
  try {
20028
- fd = fs.openSync(logPath, "a", 384);
20190
+ fd = fs2.openSync(logPath, "a", 384);
20029
20191
  } catch (err) {
20030
20192
  process.stderr.write(`[vault-audit] ERROR: could not open audit log ${logPath}: ${err.message}
20031
20193
  `);
@@ -20034,7 +20196,7 @@ function createAuditLogger(opts = {}) {
20034
20196
  }
20035
20197
  let durable = false;
20036
20198
  try {
20037
- fs.writeSync(fd, line);
20199
+ fs2.writeSync(fd, line);
20038
20200
  chain = next;
20039
20201
  durable = true;
20040
20202
  } catch (err) {
@@ -20043,7 +20205,7 @@ function createAuditLogger(opts = {}) {
20043
20205
  recordFailOpen(err);
20044
20206
  } finally {
20045
20207
  try {
20046
- fs.closeSync(fd);
20208
+ fs2.closeSync(fd);
20047
20209
  } catch (closeErr) {
20048
20210
  process.stderr.write(`[vault-audit] ERROR: could not close audit log fd: ${closeErr.message}
20049
20211
  `);
@@ -20112,13 +20274,13 @@ function genSalt(rounds, seed_length, callback) {
20112
20274
  throw Error("Illegal callback: " + typeof callback);
20113
20275
  _async(callback);
20114
20276
  } else
20115
- return new Promise(function(resolve8, reject) {
20277
+ return new Promise(function(resolve9, reject) {
20116
20278
  _async(function(err, res) {
20117
20279
  if (err) {
20118
20280
  reject(err);
20119
20281
  return;
20120
20282
  }
20121
- resolve8(res);
20283
+ resolve9(res);
20122
20284
  });
20123
20285
  });
20124
20286
  }
@@ -20138,13 +20300,13 @@ function hash(password, salt, callback, progressCallback) {
20138
20300
  throw Error("Illegal callback: " + typeof callback);
20139
20301
  _async(callback);
20140
20302
  } else
20141
- return new Promise(function(resolve8, reject) {
20303
+ return new Promise(function(resolve9, reject) {
20142
20304
  _async(function(err, res) {
20143
20305
  if (err) {
20144
20306
  reject(err);
20145
20307
  return;
20146
20308
  }
20147
- resolve8(res);
20309
+ resolve9(res);
20148
20310
  });
20149
20311
  });
20150
20312
  }
@@ -20177,13 +20339,13 @@ function compare(password, hashValue, callback, progressCallback) {
20177
20339
  throw Error("Illegal callback: " + typeof callback);
20178
20340
  _async(callback);
20179
20341
  } else
20180
- return new Promise(function(resolve8, reject) {
20342
+ return new Promise(function(resolve9, reject) {
20181
20343
  _async(function(err, res) {
20182
20344
  if (err) {
20183
20345
  reject(err);
20184
20346
  return;
20185
20347
  }
20186
- resolve8(res);
20348
+ resolve9(res);
20187
20349
  });
20188
20350
  });
20189
20351
  }
@@ -21777,6 +21939,11 @@ function rowToGrant(row) {
21777
21939
  description: row.description ?? null
21778
21940
  };
21779
21941
  }
21942
+ function checkpointGrantsWal(db) {
21943
+ try {
21944
+ db.run("PRAGMA wal_checkpoint(TRUNCATE)");
21945
+ } catch {}
21946
+ }
21780
21947
  function keyMatchesPatterns(patterns, key) {
21781
21948
  for (const pat of patterns) {
21782
21949
  if (pat.endsWith("*")) {
@@ -21807,6 +21974,7 @@ async function mintGrant(db, agent_slug, key_allow, ttl_seconds, description, wr
21807
21974
  now,
21808
21975
  description ?? null
21809
21976
  ]);
21977
+ checkpointGrantsWal(db);
21810
21978
  return { token, id, expires_at };
21811
21979
  }
21812
21980
  async function validateGrant(db, token, key) {
@@ -21885,6 +22053,7 @@ async function validateGrantForWrite(db, token, key) {
21885
22053
  function revokeGrant(db, id) {
21886
22054
  const now = Math.floor(Date.now() / 1000);
21887
22055
  const result = db.run(`UPDATE vault_grants SET revoked_at = ? WHERE id = ? AND revoked_at IS NULL`, [now, id]);
22056
+ checkpointGrantsWal(db);
21888
22057
  return (result.changes ?? 0) > 0;
21889
22058
  }
21890
22059
  function listGrants(db, agent_slug) {
@@ -21927,9 +22096,8 @@ function adminOnlyKeysBeingAdded(requestedKeys, existingKeys, patterns) {
21927
22096
  }
21928
22097
 
21929
22098
  // src/vault/grants-db.ts
21930
- import * as os2 from "node:os";
21931
- import * as path2 from "node:path";
21932
- import * as fs2 from "node:fs";
22099
+ import * as path3 from "node:path";
22100
+ import * as fs3 from "node:fs";
21933
22101
  import { Database } from "bun:sqlite";
21934
22102
 
21935
22103
  // src/vault/approvals/schema.ts
@@ -22007,8 +22175,8 @@ function migrateApprovalSchema(db) {
22007
22175
  var DEFAULT_MAX_TTL_LIFETIME_MS = 7 * 24 * 60 * 60 * 1000;
22008
22176
 
22009
22177
  // src/vault/grants-db.ts
22010
- var DEFAULT_GRANTS_DB_PATH = path2.join(os2.homedir(), ".switchroom", "vault-grants.db");
22011
- var WAL_SIDECAR_SUFFIXES = ["-wal", "-shm"];
22178
+ var DEFAULT_GRANTS_DB_PATH = getGrantsDbPath();
22179
+ var WAL_SIDECAR_SUFFIXES2 = ["-wal", "-shm"];
22012
22180
  function isGrantsDbCorruption(err) {
22013
22181
  if (!err || typeof err !== "object")
22014
22182
  return false;
@@ -22022,12 +22190,12 @@ function isGrantsDbCorruption(err) {
22022
22190
  function quarantineGrantsDb(dbPath) {
22023
22191
  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
22024
22192
  const quarantinePath = `${dbPath}.corrupt-${stamp}`;
22025
- fs2.renameSync(dbPath, quarantinePath);
22026
- for (const suffix of WAL_SIDECAR_SUFFIXES) {
22193
+ fs3.renameSync(dbPath, quarantinePath);
22194
+ for (const suffix of WAL_SIDECAR_SUFFIXES2) {
22027
22195
  const sidecar = `${dbPath}${suffix}`;
22028
- if (fs2.existsSync(sidecar)) {
22196
+ if (fs3.existsSync(sidecar)) {
22029
22197
  try {
22030
- fs2.renameSync(sidecar, `${quarantinePath}${suffix}`);
22198
+ fs3.renameSync(sidecar, `${quarantinePath}${suffix}`);
22031
22199
  } catch {}
22032
22200
  }
22033
22201
  }
@@ -22037,7 +22205,7 @@ function openAndMigrate(dbPath) {
22037
22205
  const db = new Database(dbPath, { create: true });
22038
22206
  try {
22039
22207
  try {
22040
- fs2.chmodSync(dbPath, 384);
22208
+ fs3.chmodSync(dbPath, 384);
22041
22209
  } catch {}
22042
22210
  db.run("PRAGMA journal_mode=WAL");
22043
22211
  db.run("PRAGMA busy_timeout=5000");
@@ -22053,8 +22221,9 @@ function openAndMigrate(dbPath) {
22053
22221
  return db;
22054
22222
  }
22055
22223
  function openGrantsDb(dbPath = DEFAULT_GRANTS_DB_PATH, opener = openAndMigrate) {
22056
- const dir = path2.dirname(dbPath);
22057
- fs2.mkdirSync(dir, { recursive: true });
22224
+ const dir = path3.dirname(dbPath);
22225
+ fs3.mkdirSync(dir, { recursive: true });
22226
+ migrateLegacyGrantsDbLocation(dbPath);
22058
22227
  try {
22059
22228
  return opener(dbPath);
22060
22229
  } catch (err) {
@@ -22405,7 +22574,7 @@ class VaultBroker {
22405
22574
  if (process.platform !== "linux" && process.env.SWITCHROOM_BROKER_ALLOW_NON_LINUX !== "1") {
22406
22575
  throw new Error(`vault-broker is Linux-only (running on ${process.platform}). ` + `The broker's ACL relies on cgroup-based systemd unit identification, ` + `which is not available on this platform. ` + `Use 'switchroom vault get --no-broker' for direct vault access. ` + `If you need to run the broker for development on this platform, ` + `set SWITCHROOM_BROKER_ALLOW_NON_LINUX=1 — but understand that the ` + `broker will accept any same-user caller without per-cron ACL enforcement.`);
22407
22576
  }
22408
- this.socketPath = resolve8(socketPath);
22577
+ this.socketPath = resolve9(socketPath);
22409
22578
  this.unlockSocketPath = unlockSocketFor(this.socketPath);
22410
22579
  this.startedAt = Date.now();
22411
22580
  if (this.testOpts._testConfig) {
@@ -22415,7 +22584,7 @@ class VaultBroker {
22415
22584
  this.config = loadConfig2(configPath);
22416
22585
  }
22417
22586
  if (vaultPath) {
22418
- this.vaultPath = resolve8(vaultPath);
22587
+ this.vaultPath = resolve9(vaultPath);
22419
22588
  } else {
22420
22589
  this.vaultPath = resolvePath(this.config.vault?.path ?? "~/.switchroom/vault.enc");
22421
22590
  }
@@ -22427,13 +22596,13 @@ class VaultBroker {
22427
22596
  this.passphrase = this.testOpts._testPassphrase;
22428
22597
  }
22429
22598
  process.umask(63);
22430
- const parentDir = dirname5(this.socketPath);
22431
- mkdirSync6(parentDir, { recursive: true, mode: 448 });
22599
+ const parentDir = dirname6(this.socketPath);
22600
+ mkdirSync7(parentDir, { recursive: true, mode: 448 });
22432
22601
  try {
22433
- chmodSync4(parentDir, 448);
22602
+ chmodSync5(parentDir, 448);
22434
22603
  } catch {}
22435
22604
  for (const p of [this.socketPath, this.unlockSocketPath]) {
22436
- if (existsSync11(p)) {
22605
+ if (existsSync12(p)) {
22437
22606
  try {
22438
22607
  unlinkSync6(p);
22439
22608
  } catch {}
@@ -22469,7 +22638,7 @@ class VaultBroker {
22469
22638
  try {
22470
22639
  if (ready) {
22471
22640
  writeFileSync3(p, "", { mode: 384 });
22472
- } else if (existsSync11(p)) {
22641
+ } else if (existsSync12(p)) {
22473
22642
  unlinkSync6(p);
22474
22643
  }
22475
22644
  } catch {}
@@ -22502,7 +22671,7 @@ class VaultBroker {
22502
22671
  try {
22503
22672
  entry.server.close();
22504
22673
  } catch {}
22505
- if (existsSync11(sockPath)) {
22674
+ if (existsSync12(sockPath)) {
22506
22675
  try {
22507
22676
  unlinkSync6(sockPath);
22508
22677
  } catch {}
@@ -22510,7 +22679,7 @@ class VaultBroker {
22510
22679
  }
22511
22680
  this.agentServers.clear();
22512
22681
  for (const p of [this.socketPath, this.unlockSocketPath]) {
22513
- if (p && existsSync11(p)) {
22682
+ if (p && existsSync12(p)) {
22514
22683
  try {
22515
22684
  unlinkSync6(p);
22516
22685
  } catch {}
@@ -22518,7 +22687,7 @@ class VaultBroker {
22518
22687
  }
22519
22688
  try {
22520
22689
  const pidPath = resolvePath(PID_FILE_DEFAULT);
22521
- if (existsSync11(pidPath))
22690
+ if (existsSync12(pidPath))
22522
22691
  unlinkSync6(pidPath);
22523
22692
  } catch {}
22524
22693
  }
@@ -22536,7 +22705,7 @@ class VaultBroker {
22536
22705
  return this.config;
22537
22706
  }
22538
22707
  bindAgentSocket(socketPath) {
22539
- const abs = resolve8(socketPath);
22708
+ const abs = resolve9(socketPath);
22540
22709
  const agentName = socketPathToAgent(abs);
22541
22710
  if (agentName === null) {
22542
22711
  return Promise.reject(new Error(`bindAgentSocket: socket path '${abs}' does not match the canonical ` + `/run/switchroom/broker/<agent>.sock shape — refusing to bind without ` + `a verifiable agent identity`));
@@ -22544,16 +22713,16 @@ class VaultBroker {
22544
22713
  return new Promise((resolveP, rejectP) => {
22545
22714
  if (abs.endsWith("/sock")) {
22546
22715
  const dir = abs.slice(0, -"/sock".length);
22547
- if (existsSync11(dir)) {
22716
+ if (existsSync12(dir)) {
22548
22717
  try {
22549
22718
  chownSync(dir, 0, 0);
22550
22719
  } catch {}
22551
22720
  try {
22552
- chmodSync4(dir, 448);
22721
+ chmodSync5(dir, 448);
22553
22722
  } catch {}
22554
22723
  }
22555
22724
  }
22556
- if (existsSync11(abs)) {
22725
+ if (existsSync12(abs)) {
22557
22726
  try {
22558
22727
  unlinkSync6(abs);
22559
22728
  } catch (err) {
@@ -22568,7 +22737,7 @@ class VaultBroker {
22568
22737
  server.on("error", (err) => rejectP(err));
22569
22738
  server.listen(abs, () => {
22570
22739
  try {
22571
- chmodSync4(abs, 432);
22740
+ chmodSync5(abs, 432);
22572
22741
  } catch {}
22573
22742
  let agentUid = null;
22574
22743
  try {
@@ -22585,7 +22754,7 @@ class VaultBroker {
22585
22754
  } catch {}
22586
22755
  this.agentServers.set(abs, { server, agentName });
22587
22756
  const unlockAbs = unlockSocketFor(abs);
22588
- if (existsSync11(unlockAbs)) {
22757
+ if (existsSync12(unlockAbs)) {
22589
22758
  try {
22590
22759
  unlinkSync6(unlockAbs);
22591
22760
  } catch {}
@@ -22602,7 +22771,7 @@ class VaultBroker {
22602
22771
  });
22603
22772
  unlockServer.listen(unlockAbs, () => {
22604
22773
  try {
22605
- chmodSync4(unlockAbs, 432);
22774
+ chmodSync5(unlockAbs, 432);
22606
22775
  } catch {}
22607
22776
  if (agentUid !== null) {
22608
22777
  try {
@@ -22616,7 +22785,7 @@ class VaultBroker {
22616
22785
  });
22617
22786
  }
22618
22787
  _bindDataSocket() {
22619
- return new Promise((resolve9, reject) => {
22788
+ return new Promise((resolve10, reject) => {
22620
22789
  const server = net.createServer((socket) => {
22621
22790
  this._handleDataConnection(socket);
22622
22791
  });
@@ -22625,10 +22794,10 @@ class VaultBroker {
22625
22794
  });
22626
22795
  server.listen(this.socketPath, () => {
22627
22796
  try {
22628
- chmodSync4(this.socketPath, 384);
22797
+ chmodSync5(this.socketPath, 384);
22629
22798
  } catch {}
22630
22799
  this.server = server;
22631
- resolve9();
22800
+ resolve10();
22632
22801
  });
22633
22802
  });
22634
22803
  }
@@ -22640,12 +22809,12 @@ class VaultBroker {
22640
22809
  if (!Number.isFinite(uid) || uid <= 0)
22641
22810
  return;
22642
22811
  try {
22643
- if (existsSync11(this.vaultPath))
22812
+ if (existsSync12(this.vaultPath))
22644
22813
  chownSync(this.vaultPath, uid, uid);
22645
22814
  } catch {}
22646
22815
  }
22647
22816
  bindOperatorListener(socketPath, operatorUid) {
22648
- const abs = resolve8(socketPath);
22817
+ const abs = resolve9(socketPath);
22649
22818
  const identity = socketPathToIdentity(abs);
22650
22819
  if (identity?.kind !== "operator") {
22651
22820
  return Promise.reject(new Error(`bindOperatorListener: socket path '${abs}' does not match the canonical ` + `/run/switchroom/broker/operator/sock shape — refusing to bind`));
@@ -22653,17 +22822,17 @@ class VaultBroker {
22653
22822
  const unlockAbs = unlockSocketFor(abs);
22654
22823
  if (abs.endsWith("/sock")) {
22655
22824
  const dir = abs.slice(0, -"/sock".length);
22656
- if (existsSync11(dir)) {
22825
+ if (existsSync12(dir)) {
22657
22826
  try {
22658
22827
  chownSync(dir, 0, 0);
22659
22828
  } catch {}
22660
22829
  try {
22661
- chmodSync4(dir, 448);
22830
+ chmodSync5(dir, 448);
22662
22831
  } catch {}
22663
22832
  }
22664
22833
  }
22665
22834
  for (const p of [abs, unlockAbs]) {
22666
- if (existsSync11(p)) {
22835
+ if (existsSync12(p)) {
22667
22836
  try {
22668
22837
  unlinkSync6(p);
22669
22838
  } catch {}
@@ -22676,7 +22845,7 @@ class VaultBroker {
22676
22845
  dataServer.on("error", (err) => rejectP(err));
22677
22846
  dataServer.listen(abs, () => {
22678
22847
  try {
22679
- chmodSync4(abs, 384);
22848
+ chmodSync5(abs, 384);
22680
22849
  } catch {}
22681
22850
  try {
22682
22851
  chownSync(abs, operatorUid, operatorUid);
@@ -22687,7 +22856,7 @@ class VaultBroker {
22687
22856
  unlockServer.on("error", (err) => rejectP(err));
22688
22857
  unlockServer.listen(unlockAbs, () => {
22689
22858
  try {
22690
- chmodSync4(unlockAbs, 384);
22859
+ chmodSync5(unlockAbs, 384);
22691
22860
  } catch {}
22692
22861
  try {
22693
22862
  chownSync(unlockAbs, operatorUid, operatorUid);
@@ -22698,7 +22867,7 @@ class VaultBroker {
22698
22867
  chownSync(dir, operatorUid, operatorUid);
22699
22868
  } catch {}
22700
22869
  try {
22701
- chmodSync4(dir, 448);
22870
+ chmodSync5(dir, 448);
22702
22871
  } catch {}
22703
22872
  }
22704
22873
  this.agentServers.set(abs, { server: dataServer, agentName: "__operator__" });
@@ -22709,7 +22878,7 @@ class VaultBroker {
22709
22878
  });
22710
22879
  }
22711
22880
  _bindUnlockSocket() {
22712
- return new Promise((resolve9, reject) => {
22881
+ return new Promise((resolve10, reject) => {
22713
22882
  const server = net.createServer((socket) => {
22714
22883
  this._handleUnlockConnection(socket);
22715
22884
  });
@@ -22718,10 +22887,10 @@ class VaultBroker {
22718
22887
  });
22719
22888
  server.listen(this.unlockSocketPath, () => {
22720
22889
  try {
22721
- chmodSync4(this.unlockSocketPath, 384);
22890
+ chmodSync5(this.unlockSocketPath, 384);
22722
22891
  } catch {}
22723
22892
  this.unlockServer = server;
22724
- resolve9();
22893
+ resolve10();
22725
22894
  });
22726
22895
  });
22727
22896
  }
@@ -23560,13 +23729,13 @@ class VaultBroker {
23560
23729
  return;
23561
23730
  }
23562
23731
  try {
23563
- const agentsDir = this.config ? resolveAgentsDir(this.config) : path3.join(os3.homedir(), ".switchroom", "agents");
23564
- const tokenDir = path3.join(agentsDir, agent);
23565
- mkdirSync6(tokenDir, { recursive: true });
23566
- const tokenPath = path3.join(tokenDir, ".vault-token");
23732
+ const agentsDir = this.config ? resolveAgentsDir(this.config) : path4.join(os3.homedir(), ".switchroom", "agents");
23733
+ const tokenDir = path4.join(agentsDir, agent);
23734
+ mkdirSync7(tokenDir, { recursive: true });
23735
+ const tokenPath = path4.join(tokenDir, ".vault-token");
23567
23736
  const tmpPath = `${tokenPath}.tmp.${process.pid}`;
23568
23737
  writeFileSync3(tmpPath, mintResult.token, { mode: 384 });
23569
- renameSync7(tmpPath, tokenPath);
23738
+ renameSync8(tmpPath, tokenPath);
23570
23739
  try {
23571
23740
  const uid = allocateAgentUid(agent);
23572
23741
  chownSync(tokenPath, uid, uid);
@@ -23625,9 +23794,9 @@ class VaultBroker {
23625
23794
  try {
23626
23795
  const row = this.grantsDb.query("SELECT agent_slug FROM vault_grants WHERE id = ?").get(id);
23627
23796
  if (row && AgentNameSchema.safeParse(row.agent_slug).success) {
23628
- const agentsDir = this.config ? resolveAgentsDir(this.config) : path3.join(os3.homedir(), ".switchroom", "agents");
23629
- const tokenPath = path3.join(agentsDir, row.agent_slug, ".vault-token");
23630
- if (existsSync11(tokenPath)) {
23797
+ const agentsDir = this.config ? resolveAgentsDir(this.config) : path4.join(os3.homedir(), ".switchroom", "agents");
23798
+ const tokenPath = path4.join(agentsDir, row.agent_slug, ".vault-token");
23799
+ if (existsSync12(tokenPath)) {
23631
23800
  try {
23632
23801
  unlinkSync6(tokenPath);
23633
23802
  } catch {}
@@ -23865,7 +24034,7 @@ class VaultBroker {
23865
24034
  const envPath = process.env.SWITCHROOM_VAULT_BROKER_AUTO_UNLOCK_PATH;
23866
24035
  const configuredPath = (envPath && envPath.length > 0 ? envPath : undefined) ?? this.config?.vault?.broker?.autoUnlockCredentialPath ?? DEFAULT_AUTO_UNLOCK_PATH;
23867
24036
  const filePath = resolvePath(configuredPath);
23868
- if (!existsSync11(filePath))
24037
+ if (!existsSync12(filePath))
23869
24038
  return false;
23870
24039
  try {
23871
24040
  if (statSync6(filePath).size === 0)
@@ -23946,15 +24115,15 @@ class VaultBroker {
23946
24115
  }
23947
24116
  }
23948
24117
  function detectVaultLayoutDrift(vaultPath) {
23949
- const dir = dirname5(vaultPath);
24118
+ const dir = dirname6(vaultPath);
23950
24119
  if (basename4(dir) !== "vault")
23951
24120
  return;
23952
24121
  if (basename4(vaultPath) !== "vault.enc")
23953
24122
  return;
23954
- const switchroomDir = dirname5(dir);
24123
+ const switchroomDir = dirname6(dir);
23955
24124
  if (basename4(switchroomDir) !== ".switchroom")
23956
24125
  return;
23957
- const home2 = dirname5(switchroomDir);
24126
+ const home2 = dirname6(switchroomDir);
23958
24127
  const result = inspectVaultLayout(home2);
23959
24128
  if (result.kind === "divergent") {
23960
24129
  throw new VaultError(`Vault layout divergence detected at boot: ${result.details.oldPath} and ${result.details.newPath} are both regular files with different content. An older switchroom CLI may have written to the legacy path after migration ran. Run \`switchroom apply\` from the host to surface the recovery recipe (state E refusal with literal \`mv\` commands). See docs/operators/state-e-recovery.md.`);
@@ -23980,7 +24149,7 @@ async function main() {
23980
24149
  const vaultPath = process.env.SWITCHROOM_VAULT_PATH;
23981
24150
  let perAgentTargets = [];
23982
24151
  try {
23983
- if (existsSync11(perAgentDir)) {
24152
+ if (existsSync12(perAgentDir)) {
23984
24153
  const entries = readdirSync4(perAgentDir, { withFileTypes: true });
23985
24154
  const flat = [];
23986
24155
  const subdirs = [];
@@ -23988,11 +24157,11 @@ async function main() {
23988
24157
  if (e.name.startsWith("."))
23989
24158
  continue;
23990
24159
  if ((e.isFile() || e.isSocket()) && e.name.endsWith(".sock")) {
23991
- flat.push(resolve8(perAgentDir, e.name));
24160
+ flat.push(resolve9(perAgentDir, e.name));
23992
24161
  continue;
23993
24162
  }
23994
24163
  if (e.isDirectory()) {
23995
- const candidate = resolve8(perAgentDir, e.name, "sock");
24164
+ const candidate = resolve9(perAgentDir, e.name, "sock");
23996
24165
  if (socketPathToAgent(candidate) !== null) {
23997
24166
  subdirs.push(candidate);
23998
24167
  }
@@ -24035,7 +24204,7 @@ async function main() {
24035
24204
  }
24036
24205
  const operatorUidStr = process.env.SWITCHROOM_BROKER_OPERATOR_UID;
24037
24206
  const operatorDir = "/run/switchroom/broker/operator";
24038
- if (operatorUidStr !== undefined && existsSync11(operatorDir)) {
24207
+ if (operatorUidStr !== undefined && existsSync12(operatorDir)) {
24039
24208
  const operatorUid = parseInt(operatorUidStr, 10);
24040
24209
  if (!Number.isFinite(operatorUid) || operatorUid <= 0) {
24041
24210
  process.stderr.write(`[vault-broker] SWITCHROOM_BROKER_OPERATOR_UID='${operatorUidStr}' is not a positive integer; skipping operator listener