switchroom 0.13.24 → 0.13.26

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.
@@ -13598,7 +13598,6 @@ var init_schema = __esm(() => {
13598
13598
  SessionContinuitySchema = exports_external.object({
13599
13599
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
13600
13600
  show_handoff_line: exports_external.boolean().optional().describe("Whether the telegram plugin prepends a visible '\u21a9\ufe0f Picked up\u2026' " + "line to the first assistant reply after a restart (default true)."),
13601
- summarizer_model: exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._\-/\[\]:]*$/, "Model name must be alphanumeric with ._-/[]: only").optional().describe("Anthropic model used to produce the handoff briefing."),
13602
13601
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
13603
13602
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
13604
13603
  resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale.")
@@ -13645,10 +13644,9 @@ var init_schema = __esm(() => {
13645
13644
  start: exports_external.number().int().min(0).max(23),
13646
13645
  end: exports_external.number().int().min(0).max(23),
13647
13646
  tz: exports_external.string().optional()
13648
- }).optional(),
13649
- model: exports_external.string().optional()
13647
+ }).optional()
13650
13648
  })).optional()
13651
- }).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "spawn a one-shot `claude -p` turn for the agent with the rendered " + "prompt. Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
13649
+ }).optional().describe("Auto-dispatch rules: when a verified webhook event matches a rule, " + "inject the rendered prompt into the agent's live session (#1625). " + "Supports cooldowns, quiet hours, and label/action matchers. " + "Off by default \u2014 opt in per agent. See src/web/webhook-dispatch.ts."),
13652
13650
  webhook_rate_limit: exports_external.object({
13653
13651
  rpm: exports_external.number().int().positive()
13654
13652
  }).optional().describe("Per-source rate limit for the webhook ingest path (#714). " + "Off by default \u2014 when this key is absent the handler skips " + "rate-limit checks entirely. Opt in by setting `rpm` to an " + "integer requests-per-minute (token bucket per (agent, source); " + "burst equal to rpm). When enabled, exceeding the limit returns " + "429 with Retry-After header; first throttle event per " + "(agent, source) per 60s window is written to " + "<agent>/telegram/issues.jsonl. " + "Cascades from defaults.channels.telegram.webhook_rate_limit.")
@@ -21049,6 +21047,7 @@ __export(exports_vault, {
21049
21047
  setStringSecret: () => setStringSecret,
21050
21048
  setSecret: () => setSecret,
21051
21049
  setFilesSecret: () => setFilesSecret,
21050
+ setBinarySecret: () => setBinarySecret,
21052
21051
  saveVault: () => saveVault,
21053
21052
  removeSecret: () => removeSecret,
21054
21053
  openVault: () => openVault,
@@ -21312,6 +21311,13 @@ function setStringSecret(passphrase, vaultPath, key, value, format, scope) {
21312
21311
  }
21313
21312
  setSecret(passphrase, vaultPath, key, entry);
21314
21313
  }
21314
+ function setBinarySecret(passphrase, vaultPath, key, value, format, scope) {
21315
+ const entry = format ? { kind: "binary", value, format } : { kind: "binary", value };
21316
+ if (scope !== undefined) {
21317
+ entry.scope = scope;
21318
+ }
21319
+ setSecret(passphrase, vaultPath, key, entry);
21320
+ }
21315
21321
  function getStringSecret(passphrase, vaultPath, key) {
21316
21322
  const entry = getSecret(passphrase, vaultPath, key);
21317
21323
  if (entry === null)
@@ -21369,6 +21375,315 @@ var init_vault = __esm(() => {
21369
21375
  ];
21370
21376
  });
21371
21377
 
21378
+ // src/vault/broker/peercred-ffi.ts
21379
+ function getPeerCred(fd) {
21380
+ if (process.platform !== "linux")
21381
+ return null;
21382
+ try {
21383
+ const ffi = __require("bun:ffi");
21384
+ const { dlopen, FFIType, ptr } = ffi;
21385
+ const SOL_SOCKET = 1;
21386
+ const SO_PEERCRED = 17;
21387
+ const UCRED_SIZE = 12;
21388
+ const cache = getPeerCred;
21389
+ const lib = cache._lib ?? (() => {
21390
+ const candidates = ["libc.so.6", "libc.so"];
21391
+ const symbolSpec = {
21392
+ getsockopt: {
21393
+ args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr, FFIType.ptr],
21394
+ returns: FFIType.i32
21395
+ }
21396
+ };
21397
+ const errors2 = [];
21398
+ for (const name of candidates) {
21399
+ try {
21400
+ const opened = dlopen(name, symbolSpec);
21401
+ cache._lib = opened;
21402
+ return opened;
21403
+ } catch (e) {
21404
+ errors2.push(`${name}: ${e instanceof Error ? e.message : String(e)}`);
21405
+ }
21406
+ }
21407
+ process.stderr.write(`[vault-broker] peercred-ffi: dlopen failed for all libc candidates ` + `(${errors2.join("; ")}); falling back to ss-parsing.
21408
+ `);
21409
+ throw new Error("no libc candidate could be opened");
21410
+ })();
21411
+ const credBuf = new ArrayBuffer(UCRED_SIZE);
21412
+ const lenBuf = new Uint32Array(1);
21413
+ lenBuf[0] = UCRED_SIZE;
21414
+ const rc = lib.symbols.getsockopt(fd, SOL_SOCKET, SO_PEERCRED, ptr(credBuf), ptr(lenBuf.buffer));
21415
+ if (rc !== 0)
21416
+ return null;
21417
+ if (lenBuf[0] !== UCRED_SIZE)
21418
+ return null;
21419
+ const view = new DataView(credBuf);
21420
+ return {
21421
+ pid: view.getInt32(0, true),
21422
+ uid: view.getInt32(4, true),
21423
+ gid: view.getInt32(8, true)
21424
+ };
21425
+ } catch {
21426
+ return null;
21427
+ }
21428
+ }
21429
+
21430
+ // src/vault/broker/peercred.ts
21431
+ import { execFileSync } from "node:child_process";
21432
+ import { readFileSync as readFileSync8, readlinkSync as readlinkSync3, fstatSync } from "node:fs";
21433
+ function socketPathToIdentity(socketPath) {
21434
+ if (typeof socketPath !== "string" || socketPath.length === 0)
21435
+ return null;
21436
+ const m = socketPath.match(SOCKET_PATH_AGENT_RE) ?? socketPath.match(SOCKET_PATH_AGENT_SUBDIR_RE);
21437
+ if (!m)
21438
+ return null;
21439
+ const name = m[1];
21440
+ if (name === "operator")
21441
+ return { kind: "operator" };
21442
+ if (RESERVED_AGENT_NAMES.has(name))
21443
+ return null;
21444
+ return { kind: "agent", name };
21445
+ }
21446
+ function socketPathToAgent(socketPath) {
21447
+ const identity = socketPathToIdentity(socketPath);
21448
+ return identity?.kind === "agent" ? identity.name : null;
21449
+ }
21450
+ function isReservedAgentName(name) {
21451
+ return RESERVED_AGENT_NAMES.has(name);
21452
+ }
21453
+ function unlockSocketFor(dataSocketPath) {
21454
+ if (dataSocketPath.endsWith("/sock")) {
21455
+ return dataSocketPath.slice(0, -"/sock".length) + "/unlock";
21456
+ }
21457
+ return dataSocketPath.replace(/\.sock$/, ".unlock.sock");
21458
+ }
21459
+ function parseSsRows(output) {
21460
+ const rows = [];
21461
+ const lines = output.split(`
21462
+ `);
21463
+ for (const line of lines) {
21464
+ if (!line.trim() || line.startsWith("Netid"))
21465
+ continue;
21466
+ const tokens = line.split(/\s+/).filter((t) => t.length > 0);
21467
+ if (tokens.length < 8)
21468
+ continue;
21469
+ const localAddr = tokens[4];
21470
+ const localInode = tokens[5];
21471
+ const peerAddr = tokens[6];
21472
+ const peerInode = tokens[7];
21473
+ const usersToken = tokens.slice(8).join(" ");
21474
+ const m = usersToken.match(/users:\(\(".*?",pid=(\d+),fd=\d+\)\)/);
21475
+ const pid = m ? parseInt(m[1], 10) : null;
21476
+ rows.push({ localAddr, localInode, peerAddr, peerInode, pid });
21477
+ }
21478
+ return rows;
21479
+ }
21480
+ function findClientPids(rows, socketPath) {
21481
+ const pids = [];
21482
+ for (const serverRow of rows) {
21483
+ if (serverRow.localAddr !== socketPath)
21484
+ continue;
21485
+ for (const clientRow of rows) {
21486
+ if (clientRow.localAddr !== "*")
21487
+ continue;
21488
+ if (clientRow.localInode !== serverRow.peerInode)
21489
+ continue;
21490
+ if (clientRow.pid === null)
21491
+ continue;
21492
+ pids.push(clientRow.pid);
21493
+ break;
21494
+ }
21495
+ }
21496
+ return pids;
21497
+ }
21498
+ function findClientPidByServerInode(rows, socketPath, serverInode) {
21499
+ const serverInodeStr = String(serverInode);
21500
+ for (const serverRow of rows) {
21501
+ if (serverRow.localAddr !== socketPath)
21502
+ continue;
21503
+ if (serverRow.localInode !== serverInodeStr)
21504
+ continue;
21505
+ for (const clientRow of rows) {
21506
+ if (clientRow.localAddr !== "*")
21507
+ continue;
21508
+ if (clientRow.localInode !== serverRow.peerInode)
21509
+ continue;
21510
+ if (clientRow.pid === null)
21511
+ continue;
21512
+ return clientRow.pid;
21513
+ }
21514
+ return null;
21515
+ }
21516
+ return null;
21517
+ }
21518
+ function readUid(pid) {
21519
+ try {
21520
+ const status = readFileSync8(`/proc/${pid}/status`, "utf8");
21521
+ const m = status.match(/^Uid:\s+(\d+)/m);
21522
+ if (!m)
21523
+ return null;
21524
+ return parseInt(m[1], 10);
21525
+ } catch {
21526
+ return null;
21527
+ }
21528
+ }
21529
+ function readExe(pid) {
21530
+ try {
21531
+ return readlinkSync3(`/proc/${pid}/exe`);
21532
+ } catch {
21533
+ return null;
21534
+ }
21535
+ }
21536
+ function readSystemdUnit(pid) {
21537
+ try {
21538
+ const content = readFileSync8(`/proc/${pid}/cgroup`, "utf8");
21539
+ const lines = content.split(`
21540
+ `);
21541
+ for (const line of lines) {
21542
+ if (!line.trim())
21543
+ continue;
21544
+ const parts = line.split(":");
21545
+ if (parts.length < 3)
21546
+ continue;
21547
+ const controller = parts[1];
21548
+ const isV2 = parts[0] === "0" && controller === "";
21549
+ const isV1Systemd = controller === "name=systemd";
21550
+ if (!isV2 && !isV1Systemd)
21551
+ continue;
21552
+ const cgroupPath = parts.slice(2).join(":");
21553
+ const segments = cgroupPath.split("/");
21554
+ const lastSegment = segments[segments.length - 1];
21555
+ if (!lastSegment)
21556
+ continue;
21557
+ if (/^switchroom-[a-zA-Z0-9_-]+(-cron-\d+)?\.service$/.test(lastSegment)) {
21558
+ return lastSegment;
21559
+ }
21560
+ }
21561
+ return null;
21562
+ } catch {
21563
+ return null;
21564
+ }
21565
+ }
21566
+ function verifySystemdUnit(unitName, runner) {
21567
+ let raw;
21568
+ try {
21569
+ const out = runner("systemctl", [
21570
+ "--user",
21571
+ "show",
21572
+ unitName,
21573
+ "--property=LoadState,ActiveState"
21574
+ ], { timeout: 500, encoding: "utf8" });
21575
+ raw = typeof out === "string" ? out : out.toString("utf8");
21576
+ } catch {
21577
+ return false;
21578
+ }
21579
+ const props = {};
21580
+ for (const line of raw.split(`
21581
+ `)) {
21582
+ const m = line.match(/^([A-Za-z]+)=(.*)$/);
21583
+ if (m)
21584
+ props[m[1]] = m[2];
21585
+ }
21586
+ if (props.LoadState !== "loaded")
21587
+ return false;
21588
+ if (props.ActiveState !== "active" && props.ActiveState !== "activating") {
21589
+ return false;
21590
+ }
21591
+ return true;
21592
+ }
21593
+ function readFdInode(fd) {
21594
+ try {
21595
+ const stat = fstatSync(fd);
21596
+ return stat.ino;
21597
+ } catch {
21598
+ return null;
21599
+ }
21600
+ }
21601
+ function fdFromSocket(socket) {
21602
+ const handle = socket._handle;
21603
+ if (!handle || typeof handle.fd !== "number" || handle.fd < 0)
21604
+ return null;
21605
+ return handle.fd;
21606
+ }
21607
+ function identify(socketPath, socket, execFileSyncOverride) {
21608
+ if (process.platform !== "linux") {
21609
+ return null;
21610
+ }
21611
+ const runner = execFileSyncOverride ?? execFileSync;
21612
+ let pid = null;
21613
+ if (socket !== undefined) {
21614
+ const fd = fdFromSocket(socket);
21615
+ if (fd !== null) {
21616
+ const cred = getPeerCred(fd);
21617
+ if (cred !== null)
21618
+ pid = cred.pid;
21619
+ }
21620
+ }
21621
+ if (pid === null) {
21622
+ let ssOutput;
21623
+ try {
21624
+ const raw = runner("ss", ["-xpn"], {
21625
+ timeout: 200,
21626
+ encoding: "utf8"
21627
+ });
21628
+ ssOutput = typeof raw === "string" ? raw : raw.toString("utf8");
21629
+ } catch {
21630
+ return null;
21631
+ }
21632
+ const rows = parseSsRows(ssOutput);
21633
+ let serverInode = null;
21634
+ if (socket !== undefined) {
21635
+ const fd = fdFromSocket(socket);
21636
+ if (fd !== null)
21637
+ serverInode = readFdInode(fd);
21638
+ }
21639
+ if (serverInode !== null) {
21640
+ pid = findClientPidByServerInode(rows, socketPath, serverInode);
21641
+ } else {
21642
+ const clientPids = findClientPids(rows, socketPath);
21643
+ if (clientPids.length === 0)
21644
+ return null;
21645
+ if (clientPids.length > 1) {
21646
+ process.stderr.write(`[vault-broker] peercred: ${clientPids.length} connected peers found for ${socketPath}; ` + `using pid=${clientPids[0]}. ` + `Multiple simultaneous connections reduce identification accuracy. ` + `(This warning means identify() was called without a socket arg \u2014 likely a stale call site.)
21647
+ `);
21648
+ }
21649
+ pid = clientPids[0];
21650
+ }
21651
+ }
21652
+ if (pid === null)
21653
+ return null;
21654
+ const uid = readUid(pid);
21655
+ if (uid === null) {
21656
+ return null;
21657
+ }
21658
+ const brokerUid = typeof process.getuid === "function" ? process.getuid() : null;
21659
+ if (brokerUid !== null && uid !== brokerUid) {
21660
+ process.stderr.write(`[vault-broker] peercred: UID mismatch \u2014 caller uid=${uid}, broker uid=${brokerUid}; denying
21661
+ `);
21662
+ return null;
21663
+ }
21664
+ const exe = readExe(pid);
21665
+ if (exe === null) {
21666
+ return null;
21667
+ }
21668
+ const cgroupClaim = readSystemdUnit(pid);
21669
+ let systemdUnit = null;
21670
+ if (cgroupClaim !== null) {
21671
+ if (verifySystemdUnit(cgroupClaim, runner)) {
21672
+ systemdUnit = cgroupClaim;
21673
+ } else {
21674
+ process.stderr.write(`[vault-broker] peercred: cgroup claims unit=${cgroupClaim} but systemd-user does not report it as loaded+running; treating caller as unidentified
21675
+ `);
21676
+ }
21677
+ }
21678
+ return { uid, pid, exe, systemdUnit };
21679
+ }
21680
+ var SOCKET_PATH_AGENT_RE, SOCKET_PATH_AGENT_SUBDIR_RE, RESERVED_AGENT_NAMES;
21681
+ var init_peercred = __esm(() => {
21682
+ SOCKET_PATH_AGENT_RE = /^\/run\/switchroom\/broker\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\.sock$/;
21683
+ SOCKET_PATH_AGENT_SUBDIR_RE = /^\/run\/switchroom\/broker\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\/sock$/;
21684
+ RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
21685
+ });
21686
+
21372
21687
  // src/vault/broker/protocol.ts
21373
21688
  function encodeRequest(req) {
21374
21689
  const json = JSON.stringify(req);
@@ -21420,10 +21735,14 @@ function stripWireFields(entry) {
21420
21735
  files: entry.files
21421
21736
  };
21422
21737
  }
21423
- 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;
21738
+ var MAX_FRAME_BYTES, AgentNameSchema, 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;
21424
21739
  var init_protocol = __esm(() => {
21425
21740
  init_zod();
21741
+ init_peercred();
21426
21742
  MAX_FRAME_BYTES = 64 * 1024;
21743
+ AgentNameSchema = exports_external.string().min(1).max(64, "agent name max 64 chars").regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, "agent name must be kebab-case ASCII (alnum + _- only, first char alnum)").refine((s) => !isReservedAgentName(s), {
21744
+ message: "agent name is reserved"
21745
+ });
21427
21746
  GetRequestSchema = exports_external.object({
21428
21747
  v: exports_external.literal(1),
21429
21748
  op: exports_external.literal("get"),
@@ -21451,7 +21770,7 @@ var init_protocol = __esm(() => {
21451
21770
  MintGrantRequestSchema = exports_external.object({
21452
21771
  v: exports_external.literal(1),
21453
21772
  op: exports_external.literal("mint_grant"),
21454
- agent: exports_external.string().min(1),
21773
+ agent: AgentNameSchema,
21455
21774
  keys: exports_external.array(exports_external.string().min(1)),
21456
21775
  ttl_seconds: exports_external.number().int().positive().nullable(),
21457
21776
  description: exports_external.string().optional(),
@@ -21462,7 +21781,7 @@ var init_protocol = __esm(() => {
21462
21781
  ListGrantsRequestSchema = exports_external.object({
21463
21782
  v: exports_external.literal(1),
21464
21783
  op: exports_external.literal("list_grants"),
21465
- agent: exports_external.string().optional(),
21784
+ agent: AgentNameSchema.optional(),
21466
21785
  passphrase: exports_external.string().optional(),
21467
21786
  attest_via_posture: exports_external.boolean().optional()
21468
21787
  });
@@ -21482,7 +21801,7 @@ var init_protocol = __esm(() => {
21482
21801
  PreflightAccessRequestSchema = exports_external.object({
21483
21802
  v: exports_external.literal(1),
21484
21803
  op: exports_external.literal("preflight_access"),
21485
- agent: exports_external.string().min(1),
21804
+ agent: AgentNameSchema,
21486
21805
  keys: exports_external.array(exports_external.string().min(1)).min(1).max(128)
21487
21806
  });
21488
21807
  OkPreflightAccessResponseSchema = exports_external.object({
@@ -21730,315 +22049,6 @@ var init_protocol = __esm(() => {
21730
22049
  ]);
21731
22050
  });
21732
22051
 
21733
- // src/vault/broker/peercred-ffi.ts
21734
- function getPeerCred(fd) {
21735
- if (process.platform !== "linux")
21736
- return null;
21737
- try {
21738
- const ffi = __require("bun:ffi");
21739
- const { dlopen, FFIType, ptr } = ffi;
21740
- const SOL_SOCKET = 1;
21741
- const SO_PEERCRED = 17;
21742
- const UCRED_SIZE = 12;
21743
- const cache = getPeerCred;
21744
- const lib = cache._lib ?? (() => {
21745
- const candidates = ["libc.so.6", "libc.so"];
21746
- const symbolSpec = {
21747
- getsockopt: {
21748
- args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr, FFIType.ptr],
21749
- returns: FFIType.i32
21750
- }
21751
- };
21752
- const errors2 = [];
21753
- for (const name of candidates) {
21754
- try {
21755
- const opened = dlopen(name, symbolSpec);
21756
- cache._lib = opened;
21757
- return opened;
21758
- } catch (e) {
21759
- errors2.push(`${name}: ${e instanceof Error ? e.message : String(e)}`);
21760
- }
21761
- }
21762
- process.stderr.write(`[vault-broker] peercred-ffi: dlopen failed for all libc candidates ` + `(${errors2.join("; ")}); falling back to ss-parsing.
21763
- `);
21764
- throw new Error("no libc candidate could be opened");
21765
- })();
21766
- const credBuf = new ArrayBuffer(UCRED_SIZE);
21767
- const lenBuf = new Uint32Array(1);
21768
- lenBuf[0] = UCRED_SIZE;
21769
- const rc = lib.symbols.getsockopt(fd, SOL_SOCKET, SO_PEERCRED, ptr(credBuf), ptr(lenBuf.buffer));
21770
- if (rc !== 0)
21771
- return null;
21772
- if (lenBuf[0] !== UCRED_SIZE)
21773
- return null;
21774
- const view = new DataView(credBuf);
21775
- return {
21776
- pid: view.getInt32(0, true),
21777
- uid: view.getInt32(4, true),
21778
- gid: view.getInt32(8, true)
21779
- };
21780
- } catch {
21781
- return null;
21782
- }
21783
- }
21784
-
21785
- // src/vault/broker/peercred.ts
21786
- import { execFileSync } from "node:child_process";
21787
- import { readFileSync as readFileSync8, readlinkSync as readlinkSync3, fstatSync } from "node:fs";
21788
- function socketPathToIdentity(socketPath) {
21789
- if (typeof socketPath !== "string" || socketPath.length === 0)
21790
- return null;
21791
- const m = socketPath.match(SOCKET_PATH_AGENT_RE) ?? socketPath.match(SOCKET_PATH_AGENT_SUBDIR_RE);
21792
- if (!m)
21793
- return null;
21794
- const name = m[1];
21795
- if (name === "operator")
21796
- return { kind: "operator" };
21797
- if (RESERVED_AGENT_NAMES.has(name))
21798
- return null;
21799
- return { kind: "agent", name };
21800
- }
21801
- function socketPathToAgent(socketPath) {
21802
- const identity = socketPathToIdentity(socketPath);
21803
- return identity?.kind === "agent" ? identity.name : null;
21804
- }
21805
- function isReservedAgentName(name) {
21806
- return RESERVED_AGENT_NAMES.has(name);
21807
- }
21808
- function unlockSocketFor(dataSocketPath) {
21809
- if (dataSocketPath.endsWith("/sock")) {
21810
- return dataSocketPath.slice(0, -"/sock".length) + "/unlock";
21811
- }
21812
- return dataSocketPath.replace(/\.sock$/, ".unlock.sock");
21813
- }
21814
- function parseSsRows(output) {
21815
- const rows = [];
21816
- const lines = output.split(`
21817
- `);
21818
- for (const line of lines) {
21819
- if (!line.trim() || line.startsWith("Netid"))
21820
- continue;
21821
- const tokens = line.split(/\s+/).filter((t) => t.length > 0);
21822
- if (tokens.length < 8)
21823
- continue;
21824
- const localAddr = tokens[4];
21825
- const localInode = tokens[5];
21826
- const peerAddr = tokens[6];
21827
- const peerInode = tokens[7];
21828
- const usersToken = tokens.slice(8).join(" ");
21829
- const m = usersToken.match(/users:\(\(".*?",pid=(\d+),fd=\d+\)\)/);
21830
- const pid = m ? parseInt(m[1], 10) : null;
21831
- rows.push({ localAddr, localInode, peerAddr, peerInode, pid });
21832
- }
21833
- return rows;
21834
- }
21835
- function findClientPids(rows, socketPath) {
21836
- const pids = [];
21837
- for (const serverRow of rows) {
21838
- if (serverRow.localAddr !== socketPath)
21839
- continue;
21840
- for (const clientRow of rows) {
21841
- if (clientRow.localAddr !== "*")
21842
- continue;
21843
- if (clientRow.localInode !== serverRow.peerInode)
21844
- continue;
21845
- if (clientRow.pid === null)
21846
- continue;
21847
- pids.push(clientRow.pid);
21848
- break;
21849
- }
21850
- }
21851
- return pids;
21852
- }
21853
- function findClientPidByServerInode(rows, socketPath, serverInode) {
21854
- const serverInodeStr = String(serverInode);
21855
- for (const serverRow of rows) {
21856
- if (serverRow.localAddr !== socketPath)
21857
- continue;
21858
- if (serverRow.localInode !== serverInodeStr)
21859
- continue;
21860
- for (const clientRow of rows) {
21861
- if (clientRow.localAddr !== "*")
21862
- continue;
21863
- if (clientRow.localInode !== serverRow.peerInode)
21864
- continue;
21865
- if (clientRow.pid === null)
21866
- continue;
21867
- return clientRow.pid;
21868
- }
21869
- return null;
21870
- }
21871
- return null;
21872
- }
21873
- function readUid(pid) {
21874
- try {
21875
- const status = readFileSync8(`/proc/${pid}/status`, "utf8");
21876
- const m = status.match(/^Uid:\s+(\d+)/m);
21877
- if (!m)
21878
- return null;
21879
- return parseInt(m[1], 10);
21880
- } catch {
21881
- return null;
21882
- }
21883
- }
21884
- function readExe(pid) {
21885
- try {
21886
- return readlinkSync3(`/proc/${pid}/exe`);
21887
- } catch {
21888
- return null;
21889
- }
21890
- }
21891
- function readSystemdUnit(pid) {
21892
- try {
21893
- const content = readFileSync8(`/proc/${pid}/cgroup`, "utf8");
21894
- const lines = content.split(`
21895
- `);
21896
- for (const line of lines) {
21897
- if (!line.trim())
21898
- continue;
21899
- const parts = line.split(":");
21900
- if (parts.length < 3)
21901
- continue;
21902
- const controller = parts[1];
21903
- const isV2 = parts[0] === "0" && controller === "";
21904
- const isV1Systemd = controller === "name=systemd";
21905
- if (!isV2 && !isV1Systemd)
21906
- continue;
21907
- const cgroupPath = parts.slice(2).join(":");
21908
- const segments = cgroupPath.split("/");
21909
- const lastSegment = segments[segments.length - 1];
21910
- if (!lastSegment)
21911
- continue;
21912
- if (/^switchroom-[a-zA-Z0-9_-]+(-cron-\d+)?\.service$/.test(lastSegment)) {
21913
- return lastSegment;
21914
- }
21915
- }
21916
- return null;
21917
- } catch {
21918
- return null;
21919
- }
21920
- }
21921
- function verifySystemdUnit(unitName, runner) {
21922
- let raw;
21923
- try {
21924
- const out = runner("systemctl", [
21925
- "--user",
21926
- "show",
21927
- unitName,
21928
- "--property=LoadState,ActiveState"
21929
- ], { timeout: 500, encoding: "utf8" });
21930
- raw = typeof out === "string" ? out : out.toString("utf8");
21931
- } catch {
21932
- return false;
21933
- }
21934
- const props = {};
21935
- for (const line of raw.split(`
21936
- `)) {
21937
- const m = line.match(/^([A-Za-z]+)=(.*)$/);
21938
- if (m)
21939
- props[m[1]] = m[2];
21940
- }
21941
- if (props.LoadState !== "loaded")
21942
- return false;
21943
- if (props.ActiveState !== "active" && props.ActiveState !== "activating") {
21944
- return false;
21945
- }
21946
- return true;
21947
- }
21948
- function readFdInode(fd) {
21949
- try {
21950
- const stat = fstatSync(fd);
21951
- return stat.ino;
21952
- } catch {
21953
- return null;
21954
- }
21955
- }
21956
- function fdFromSocket(socket) {
21957
- const handle = socket._handle;
21958
- if (!handle || typeof handle.fd !== "number" || handle.fd < 0)
21959
- return null;
21960
- return handle.fd;
21961
- }
21962
- function identify(socketPath, socket, execFileSyncOverride) {
21963
- if (process.platform !== "linux") {
21964
- return null;
21965
- }
21966
- const runner = execFileSyncOverride ?? execFileSync;
21967
- let pid = null;
21968
- if (socket !== undefined) {
21969
- const fd = fdFromSocket(socket);
21970
- if (fd !== null) {
21971
- const cred = getPeerCred(fd);
21972
- if (cred !== null)
21973
- pid = cred.pid;
21974
- }
21975
- }
21976
- if (pid === null) {
21977
- let ssOutput;
21978
- try {
21979
- const raw = runner("ss", ["-xpn"], {
21980
- timeout: 200,
21981
- encoding: "utf8"
21982
- });
21983
- ssOutput = typeof raw === "string" ? raw : raw.toString("utf8");
21984
- } catch {
21985
- return null;
21986
- }
21987
- const rows = parseSsRows(ssOutput);
21988
- let serverInode = null;
21989
- if (socket !== undefined) {
21990
- const fd = fdFromSocket(socket);
21991
- if (fd !== null)
21992
- serverInode = readFdInode(fd);
21993
- }
21994
- if (serverInode !== null) {
21995
- pid = findClientPidByServerInode(rows, socketPath, serverInode);
21996
- } else {
21997
- const clientPids = findClientPids(rows, socketPath);
21998
- if (clientPids.length === 0)
21999
- return null;
22000
- if (clientPids.length > 1) {
22001
- process.stderr.write(`[vault-broker] peercred: ${clientPids.length} connected peers found for ${socketPath}; ` + `using pid=${clientPids[0]}. ` + `Multiple simultaneous connections reduce identification accuracy. ` + `(This warning means identify() was called without a socket arg \u2014 likely a stale call site.)
22002
- `);
22003
- }
22004
- pid = clientPids[0];
22005
- }
22006
- }
22007
- if (pid === null)
22008
- return null;
22009
- const uid = readUid(pid);
22010
- if (uid === null) {
22011
- return null;
22012
- }
22013
- const brokerUid = typeof process.getuid === "function" ? process.getuid() : null;
22014
- if (brokerUid !== null && uid !== brokerUid) {
22015
- process.stderr.write(`[vault-broker] peercred: UID mismatch \u2014 caller uid=${uid}, broker uid=${brokerUid}; denying
22016
- `);
22017
- return null;
22018
- }
22019
- const exe = readExe(pid);
22020
- if (exe === null) {
22021
- return null;
22022
- }
22023
- const cgroupClaim = readSystemdUnit(pid);
22024
- let systemdUnit = null;
22025
- if (cgroupClaim !== null) {
22026
- if (verifySystemdUnit(cgroupClaim, runner)) {
22027
- systemdUnit = cgroupClaim;
22028
- } else {
22029
- process.stderr.write(`[vault-broker] peercred: cgroup claims unit=${cgroupClaim} but systemd-user does not report it as loaded+running; treating caller as unidentified
22030
- `);
22031
- }
22032
- }
22033
- return { uid, pid, exe, systemdUnit };
22034
- }
22035
- var SOCKET_PATH_AGENT_RE, SOCKET_PATH_AGENT_SUBDIR_RE, RESERVED_AGENT_NAMES;
22036
- var init_peercred = __esm(() => {
22037
- SOCKET_PATH_AGENT_RE = /^\/run\/switchroom\/broker\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\.sock$/;
22038
- SOCKET_PATH_AGENT_SUBDIR_RE = /^\/run\/switchroom\/broker\/([a-zA-Z0-9][a-zA-Z0-9_-]*)\/sock$/;
22039
- RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
22040
- });
22041
-
22042
22052
  // src/runtime-mode.ts
22043
22053
  function isDockerRuntime() {
22044
22054
  return process.env.SWITCHROOM_RUNTIME === "docker";
@@ -25539,6 +25549,7 @@ var init_broker_call = __esm(() => {
25539
25549
 
25540
25550
  // src/auth/account-store.ts
25541
25551
  import {
25552
+ chownSync,
25542
25553
  existsSync as existsSync26,
25543
25554
  mkdirSync as mkdirSync15,
25544
25555
  readFileSync as readFileSync22,
@@ -29089,7 +29100,7 @@ function decodeResponse3(line) {
29089
29100
  const obj = JSON.parse(line);
29090
29101
  return ResponseSchema3.parse(obj);
29091
29102
  }
29092
- var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ResponseEnvelope, ResponseSchema3;
29103
+ var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema2, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ResponseEnvelope, ResponseSchema3;
29093
29104
  var init_protocol3 = __esm(() => {
29094
29105
  init_zod();
29095
29106
  MAX_FRAME_BYTES3 = 64 * 1024;
@@ -29119,7 +29130,7 @@ var init_protocol3 = __esm(() => {
29119
29130
  target_request_id: exports_external.string().min(1).max(128)
29120
29131
  })
29121
29132
  });
29122
- AgentNameSchema = exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, "agent name must be kebab-case ASCII");
29133
+ AgentNameSchema2 = exports_external.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, "agent name must be kebab-case ASCII");
29123
29134
  UpdateCheckRequestSchema = exports_external.object({
29124
29135
  ...RequestEnvelope,
29125
29136
  op: exports_external.literal("update_check"),
@@ -29144,21 +29155,21 @@ var init_protocol3 = __esm(() => {
29144
29155
  ...RequestEnvelope,
29145
29156
  op: exports_external.literal("agent_start"),
29146
29157
  args: exports_external.object({
29147
- name: AgentNameSchema
29158
+ name: AgentNameSchema2
29148
29159
  })
29149
29160
  });
29150
29161
  AgentStopRequestSchema = exports_external.object({
29151
29162
  ...RequestEnvelope,
29152
29163
  op: exports_external.literal("agent_stop"),
29153
29164
  args: exports_external.object({
29154
- name: AgentNameSchema
29165
+ name: AgentNameSchema2
29155
29166
  })
29156
29167
  });
29157
29168
  AgentLogsRequestSchema = exports_external.object({
29158
29169
  ...RequestEnvelope,
29159
29170
  op: exports_external.literal("agent_logs"),
29160
29171
  args: exports_external.object({
29161
- name: AgentNameSchema,
29172
+ name: AgentNameSchema2,
29162
29173
  tail: exports_external.number().int().positive().max(2000).optional()
29163
29174
  })
29164
29175
  });
@@ -29166,7 +29177,7 @@ var init_protocol3 = __esm(() => {
29166
29177
  ...RequestEnvelope,
29167
29178
  op: exports_external.literal("agent_exec"),
29168
29179
  args: exports_external.object({
29169
- name: AgentNameSchema,
29180
+ name: AgentNameSchema2,
29170
29181
  argv: exports_external.array(exports_external.string().min(1)).min(1).max(32)
29171
29182
  })
29172
29183
  });
@@ -29179,7 +29190,7 @@ var init_protocol3 = __esm(() => {
29179
29190
  ...RequestEnvelope,
29180
29191
  op: exports_external.literal("agent_smoke"),
29181
29192
  args: exports_external.object({
29182
- name: AgentNameSchema,
29193
+ name: AgentNameSchema2,
29183
29194
  deep: exports_external.boolean().optional()
29184
29195
  })
29185
29196
  });
@@ -29394,6 +29405,7 @@ __export(exports_doctor, {
29394
29405
  tryReadHostFile: () => tryReadHostFile,
29395
29406
  telegramGetMe: () => telegramGetMe,
29396
29407
  registerDoctorCommand: () => registerDoctorCommand,
29408
+ probeVaultBrokerSocketPair: () => probeVaultBrokerSocketPair,
29397
29409
  printSection: () => printSection,
29398
29410
  parseSimpleEnv: () => parseSimpleEnv,
29399
29411
  parsePythonVersion: () => parsePythonVersion,
@@ -29406,6 +29418,7 @@ __export(exports_doctor, {
29406
29418
  findChromium: () => findChromium,
29407
29419
  deriveEd25519PublicKeyBytes: () => deriveEd25519PublicKeyBytes,
29408
29420
  classifyReadError: () => classifyReadError,
29421
+ checkVaultBrokerSocketPairs: () => checkVaultBrokerSocketPairs,
29409
29422
  checkTelegram: () => checkTelegram,
29410
29423
  checkStartShStale: () => checkStartShStale,
29411
29424
  checkSkillsPrerequisites: () => checkSkillsPrerequisites,
@@ -29731,6 +29744,93 @@ function checkLegacyState() {
29731
29744
  }
29732
29745
  return results;
29733
29746
  }
29747
+ function probeVaultBrokerSocketPair(agentName) {
29748
+ const dataPath = `/run/switchroom/broker/${agentName}/sock`;
29749
+ const unlockPath = `/run/switchroom/broker/${agentName}/unlock`;
29750
+ const script = `D=0; U=0; ` + `test -S '${dataPath}' && D=1; ` + `test -S '${unlockPath}' && U=1; ` + `echo "D=$D U=$U"`;
29751
+ const r = spawnSync7("docker", ["exec", "switchroom-vault-broker", "sh", "-c", script], { stdio: "pipe", timeout: 3000 });
29752
+ if (r.error || r.status === null)
29753
+ return "unreachable";
29754
+ if (r.status !== 0) {
29755
+ if (r.status >= 125)
29756
+ return "unreachable";
29757
+ return "unreachable";
29758
+ }
29759
+ const out = r.stdout.toString();
29760
+ const dOk = /D=1/.test(out);
29761
+ const uOk = /U=1/.test(out);
29762
+ if (dOk && uOk)
29763
+ return "ok";
29764
+ if (dOk && !uOk)
29765
+ return "missing-unlock";
29766
+ if (!dOk && uOk)
29767
+ return "missing-data";
29768
+ return "missing-both";
29769
+ }
29770
+ function checkVaultBrokerSocketPairs(config, opts) {
29771
+ const agentNames = Object.keys(config.agents ?? {}).sort();
29772
+ if (agentNames.length === 0) {
29773
+ return {
29774
+ name: "vault-broker per-agent socket pairs",
29775
+ status: "ok",
29776
+ detail: "no agents configured"
29777
+ };
29778
+ }
29779
+ const probe2 = opts?.probe ?? probeVaultBrokerSocketPair;
29780
+ const states = new Map;
29781
+ for (const name of agentNames) {
29782
+ states.set(name, probe2(name));
29783
+ }
29784
+ const allUnreachable = [...states.values()].every((s) => s === "unreachable");
29785
+ if (allUnreachable) {
29786
+ return {
29787
+ name: "vault-broker per-agent socket pairs",
29788
+ status: "skip",
29789
+ detail: "vault-broker container unreachable \u2014 couldn't probe per-agent socket pairs",
29790
+ fix: "Check the vault-broker service health row above; `switchroom update` brings it back"
29791
+ };
29792
+ }
29793
+ const groups = {
29794
+ "missing-unlock": [],
29795
+ "missing-data": [],
29796
+ "missing-both": [],
29797
+ unreachable: []
29798
+ };
29799
+ let okCount = 0;
29800
+ for (const [name, state] of states) {
29801
+ if (state === "ok") {
29802
+ okCount++;
29803
+ } else {
29804
+ groups[state].push(name);
29805
+ }
29806
+ }
29807
+ if (okCount === agentNames.length) {
29808
+ return {
29809
+ name: "vault-broker per-agent socket pairs",
29810
+ status: "ok",
29811
+ detail: `${okCount}/${agentNames.length} agents: data + unlock sockets bound`
29812
+ };
29813
+ }
29814
+ const parts = [];
29815
+ if (groups["missing-unlock"].length > 0) {
29816
+ parts.push(`unlock missing: ${groups["missing-unlock"].join(", ")} ` + `(the documented Telegram /vault unlock flow fails ENOENT for these)`);
29817
+ }
29818
+ if (groups["missing-data"].length > 0) {
29819
+ parts.push(`data missing: ${groups["missing-data"].join(", ")}`);
29820
+ }
29821
+ if (groups["missing-both"].length > 0) {
29822
+ parts.push(`both missing: ${groups["missing-both"].join(", ")}`);
29823
+ }
29824
+ if (groups["unreachable"].length > 0) {
29825
+ parts.push(`unreachable: ${groups["unreachable"].join(", ")}`);
29826
+ }
29827
+ return {
29828
+ name: "vault-broker per-agent socket pairs",
29829
+ status: "fail",
29830
+ detail: `${okCount}/${agentNames.length} ok \u2014 ${parts.join("; ")}`,
29831
+ fix: "Run `switchroom update` (or `switchroom apply` then recreate the " + "vault-broker container). If only the unlock half is missing, the " + "broker image predates PR #1721 \u2014 pull the latest image."
29832
+ };
29833
+ }
29734
29834
  function checkVault(config) {
29735
29835
  const vaultPath = config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
29736
29836
  const broker = config.vault?.broker;
@@ -29749,6 +29849,7 @@ function checkVault(config) {
29749
29849
  status: "ok",
29750
29850
  detail: "Approval auth: passphrase (two-factor)"
29751
29851
  };
29852
+ const pairsResult = checkVaultBrokerSocketPairs(config);
29752
29853
  if (!existsSync50(vaultPath)) {
29753
29854
  return [
29754
29855
  postureResult,
@@ -29757,7 +29858,8 @@ function checkVault(config) {
29757
29858
  status: "warn",
29758
29859
  detail: `${vaultPath} not found`,
29759
29860
  fix: "Run `switchroom vault init` if you plan to store secrets in the vault"
29760
- }
29861
+ },
29862
+ pairsResult
29761
29863
  ];
29762
29864
  }
29763
29865
  const passphrase = process.env.SWITCHROOM_VAULT_PASSPHRASE;
@@ -29774,7 +29876,8 @@ function checkVault(config) {
29774
29876
  status: "skip",
29775
29877
  detail: "SWITCHROOM_VAULT_PASSPHRASE not set; cannot verify decrypt",
29776
29878
  fix: "Export SWITCHROOM_VAULT_PASSPHRASE to verify the vault unlocks"
29777
- }
29879
+ },
29880
+ pairsResult
29778
29881
  ];
29779
29882
  }
29780
29883
  try {
@@ -29785,7 +29888,8 @@ function checkVault(config) {
29785
29888
  name: "vault unlock",
29786
29889
  status: "ok",
29787
29890
  detail: `${keys.length} secret(s)`
29788
- }
29891
+ },
29892
+ pairsResult
29789
29893
  ];
29790
29894
  } catch (err) {
29791
29895
  return [
@@ -29795,7 +29899,8 @@ function checkVault(config) {
29795
29899
  status: "fail",
29796
29900
  detail: err.message,
29797
29901
  fix: "SWITCHROOM_VAULT_PASSPHRASE is wrong, or the vault file is corrupted"
29798
- }
29902
+ },
29903
+ pairsResult
29799
29904
  ];
29800
29905
  }
29801
29906
  }
@@ -47331,8 +47436,8 @@ var {
47331
47436
  } = import__.default;
47332
47437
 
47333
47438
  // src/build-info.ts
47334
- var VERSION = "0.13.24";
47335
- var COMMIT_SHA = "9bfca233";
47439
+ var VERSION = "0.13.26";
47440
+ var COMMIT_SHA = "b2767bb7";
47336
47441
 
47337
47442
  // src/cli/agent.ts
47338
47443
  init_source();
@@ -55825,7 +55930,7 @@ import { spawn as spawn3 } from "node:child_process";
55825
55930
  init_compose();
55826
55931
  init_vault();
55827
55932
  import * as net3 from "node:net";
55828
- import { mkdirSync as mkdirSync20, chmodSync as chmodSync7, chownSync, existsSync as existsSync33, readFileSync as readFileSync29, readdirSync as readdirSync15, statSync as statSync19, unlinkSync as unlinkSync8, writeFileSync as writeFileSync18, renameSync as renameSync9 } from "node:fs";
55933
+ import { mkdirSync as mkdirSync20, chmodSync as chmodSync7, chownSync as chownSync2, existsSync as existsSync33, readFileSync as readFileSync29, readdirSync as readdirSync15, statSync as statSync19, unlinkSync as unlinkSync8, writeFileSync as writeFileSync18, renameSync as renameSync9 } from "node:fs";
55829
55934
  import { dirname as dirname6, resolve as resolve25, basename as basename5 } from "node:path";
55830
55935
  import * as os4 from "node:os";
55831
55936
  import * as path3 from "node:path";
@@ -58622,7 +58727,7 @@ class VaultBroker {
58622
58727
  const dir = abs.slice(0, -"/sock".length);
58623
58728
  if (existsSync33(dir)) {
58624
58729
  try {
58625
- chownSync(dir, 0, 0);
58730
+ chownSync2(dir, 0, 0);
58626
58731
  } catch {}
58627
58732
  try {
58628
58733
  chmodSync7(dir, 448);
@@ -58646,20 +58751,48 @@ class VaultBroker {
58646
58751
  try {
58647
58752
  chmodSync7(abs, 432);
58648
58753
  } catch {}
58754
+ let agentUid = null;
58649
58755
  try {
58650
- const uid = allocateAgentUid(agentName);
58756
+ agentUid = allocateAgentUid(agentName);
58651
58757
  try {
58652
- chownSync(abs, uid, uid);
58758
+ chownSync2(abs, agentUid, agentUid);
58653
58759
  } catch {}
58654
58760
  if (abs.endsWith("/sock")) {
58655
58761
  const dir = abs.slice(0, -"/sock".length);
58656
58762
  try {
58657
- chownSync(dir, uid, uid);
58763
+ chownSync2(dir, agentUid, agentUid);
58658
58764
  } catch {}
58659
58765
  }
58660
58766
  } catch {}
58661
58767
  this.agentServers.set(abs, { server, agentName });
58662
- resolveP(agentName);
58768
+ const unlockAbs = unlockSocketFor(abs);
58769
+ if (existsSync33(unlockAbs)) {
58770
+ try {
58771
+ unlinkSync8(unlockAbs);
58772
+ } catch {}
58773
+ }
58774
+ const unlockServer = net3.createServer((sock) => {
58775
+ this._handleUnlockConnection(sock, false);
58776
+ });
58777
+ unlockServer.on("error", (err) => {
58778
+ try {
58779
+ server.close();
58780
+ } catch {}
58781
+ this.agentServers.delete(abs);
58782
+ rejectP(err);
58783
+ });
58784
+ unlockServer.listen(unlockAbs, () => {
58785
+ try {
58786
+ chmodSync7(unlockAbs, 432);
58787
+ } catch {}
58788
+ if (agentUid !== null) {
58789
+ try {
58790
+ chownSync2(unlockAbs, agentUid, agentUid);
58791
+ } catch {}
58792
+ }
58793
+ this.agentServers.set(unlockAbs, { server: unlockServer, agentName });
58794
+ resolveP(agentName);
58795
+ });
58663
58796
  });
58664
58797
  });
58665
58798
  }
@@ -58689,7 +58822,7 @@ class VaultBroker {
58689
58822
  return;
58690
58823
  try {
58691
58824
  if (existsSync33(this.vaultPath))
58692
- chownSync(this.vaultPath, uid, uid);
58825
+ chownSync2(this.vaultPath, uid, uid);
58693
58826
  } catch {}
58694
58827
  }
58695
58828
  bindOperatorListener(socketPath, operatorUid) {
@@ -58703,7 +58836,7 @@ class VaultBroker {
58703
58836
  const dir = abs.slice(0, -"/sock".length);
58704
58837
  if (existsSync33(dir)) {
58705
58838
  try {
58706
- chownSync(dir, 0, 0);
58839
+ chownSync2(dir, 0, 0);
58707
58840
  } catch {}
58708
58841
  try {
58709
58842
  chmodSync7(dir, 448);
@@ -58727,7 +58860,7 @@ class VaultBroker {
58727
58860
  chmodSync7(abs, 384);
58728
58861
  } catch {}
58729
58862
  try {
58730
- chownSync(abs, operatorUid, operatorUid);
58863
+ chownSync2(abs, operatorUid, operatorUid);
58731
58864
  } catch {}
58732
58865
  const unlockServer = net3.createServer((sock) => {
58733
58866
  this._handleUnlockConnection(sock, true);
@@ -58738,12 +58871,12 @@ class VaultBroker {
58738
58871
  chmodSync7(unlockAbs, 384);
58739
58872
  } catch {}
58740
58873
  try {
58741
- chownSync(unlockAbs, operatorUid, operatorUid);
58874
+ chownSync2(unlockAbs, operatorUid, operatorUid);
58742
58875
  } catch {}
58743
58876
  if (abs.endsWith("/sock")) {
58744
58877
  const dir = abs.slice(0, -"/sock".length);
58745
58878
  try {
58746
- chownSync(dir, operatorUid, operatorUid);
58879
+ chownSync2(dir, operatorUid, operatorUid);
58747
58880
  } catch {}
58748
58881
  try {
58749
58882
  chmodSync7(dir, 448);
@@ -59586,7 +59719,7 @@ class VaultBroker {
59586
59719
  const revoked = revokeGrant(this.grantsDb, id);
59587
59720
  try {
59588
59721
  const row = this.grantsDb.query("SELECT agent_slug FROM vault_grants WHERE id = ?").get(id);
59589
- if (row) {
59722
+ if (row && AgentNameSchema.safeParse(row.agent_slug).success) {
59590
59723
  const tokenPath = path3.join(os4.homedir(), ".switchroom", "agents", row.agent_slug, ".vault-token");
59591
59724
  if (existsSync33(tokenPath)) {
59592
59725
  try {
@@ -61405,9 +61538,18 @@ function registerVaultCommand(program3) {
61405
61538
  }
61406
61539
  const passphrase = await getPassphrase();
61407
61540
  let value;
61541
+ let isBinaryFile = false;
61408
61542
  if (opts.file) {
61409
61543
  try {
61410
- value = readFileSync34(resolvePath(opts.file), "utf8");
61544
+ const buf = readFileSync34(resolvePath(opts.file));
61545
+ const asUtf8 = buf.toString("utf8");
61546
+ if (Buffer.compare(buf, Buffer.from(asUtf8, "utf8")) === 0) {
61547
+ value = asUtf8;
61548
+ } else {
61549
+ value = buf.toString("base64");
61550
+ isBinaryFile = true;
61551
+ console.error(source_default.yellow(`note: file contains non-UTF-8 bytes; storing as kind="binary" ` + `(base64-encoded). Retrieve with: switchroom vault get ${key} | base64 -d`));
61552
+ }
61411
61553
  } catch (err) {
61412
61554
  const msg = err instanceof Error ? err.message : String(err);
61413
61555
  console.error(source_default.red(`Error reading file: ${msg}`));
@@ -61422,7 +61564,7 @@ function registerVaultCommand(program3) {
61422
61564
  console.error(source_default.red("Error: Value cannot be empty"));
61423
61565
  process.exit(1);
61424
61566
  }
61425
- if (formatHint) {
61567
+ if (formatHint && !isBinaryFile) {
61426
61568
  const validationError = validateFormatHint(value, formatHint);
61427
61569
  if (validationError) {
61428
61570
  console.error(source_default.red(`Error: format validation failed for --format ${formatHint}: ${validationError}`));
@@ -61451,7 +61593,11 @@ function registerVaultCommand(program3) {
61451
61593
  }
61452
61594
  } catch {}
61453
61595
  }
61454
- setStringSecret(passphrase, vaultPath, key, value, formatHint, scope);
61596
+ if (isBinaryFile) {
61597
+ setBinarySecret(passphrase, vaultPath, key, value, formatHint, scope);
61598
+ } else {
61599
+ setStringSecret(passphrase, vaultPath, key, value, formatHint, scope);
61600
+ }
61455
61601
  if (formatHint && scope) {
61456
61602
  const scopeDesc = [
61457
61603
  scope.allow?.length ? `allow: ${scope.allow.join(", ")}` : "",
@@ -62193,13 +62339,12 @@ function registerDispatchVerb(tg, _program) {
62193
62339
  `)) {
62194
62340
  console.log(` ${line}`);
62195
62341
  }
62196
- console.log(` model: ${rule.model ?? "claude-sonnet-4-6"}`);
62197
62342
  }
62198
62343
  console.log();
62199
62344
  if (matchCount === 0) {
62200
62345
  console.log(source_default.yellow("No rules matched \u2014 no dispatch would fire."));
62201
62346
  } else {
62202
- console.log(source_default.green(`${matchCount} rule(s) matched. ` + `Run without --dry-run in production to spawn claude -p.`));
62347
+ console.log(source_default.green(`${matchCount} rule(s) matched. ` + `The webhook turn injects into the agent's live session \u2014 ` + `agent's configured model wins.`));
62203
62348
  }
62204
62349
  }));
62205
62350
  }
@@ -74040,7 +74185,7 @@ function detectInstallType() {
74040
74185
 
74041
74186
  // src/cli/operator-uid.ts
74042
74187
  import {
74043
- chownSync as chownSync2,
74188
+ chownSync as chownSync3,
74044
74189
  existsSync as existsSync66,
74045
74190
  lstatSync as lstatSync7,
74046
74191
  readdirSync as readdirSync24,
@@ -74074,7 +74219,7 @@ function operatorOwnedPaths(home2) {
74074
74219
  ];
74075
74220
  }
74076
74221
  function restoreOperatorOwnership(home2, operatorUid, deps = {}) {
74077
- const chown = deps.chown ?? ((p, u, g) => chownSync2(p, u, g));
74222
+ const chown = deps.chown ?? ((p, u, g) => chownSync3(p, u, g));
74078
74223
  const exists = deps.exists ?? ((p) => existsSync66(p));
74079
74224
  const isSymlink = deps.isSymlink ?? ((p) => {
74080
74225
  try {