switchroom 0.15.25 → 0.15.27

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.
@@ -28762,7 +28762,7 @@ function decodeResponse3(line) {
28762
28762
  const obj = JSON.parse(line);
28763
28763
  return ResponseSchema3.parse(obj);
28764
28764
  }
28765
- var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema2, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ErrorFixSchema, ErrorEnvelopeSchema, ResponseEnvelope, ResponseSchema3;
28765
+ var MAX_FRAME_BYTES3, RequestEnvelope, AgentRestartRequestSchema, UpgradeStatusRequestSchema, GetStatusRequestSchema, AgentNameSchema2, UpdateCheckRequestSchema, UpdateApplyRequestSchema, ApplyRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, AgentLogsRequestSchema, AgentExecRequestSchema, DoctorRequestSchema, AgentStatusRequestSchema, AgentScheduleRequestSchema, AgentSmokeRequestSchema, ConfigProposeEditRequestSchema, RequestSchema3, ResultSchema, ErrorFixSchema, ErrorEnvelopeSchema, ResponseEnvelope, ResponseSchema3;
28766
28766
  var init_protocol3 = __esm(() => {
28767
28767
  init_zod();
28768
28768
  MAX_FRAME_BYTES3 = 64 * 1024;
@@ -28848,6 +28848,20 @@ var init_protocol3 = __esm(() => {
28848
28848
  op: exports_external.literal("doctor"),
28849
28849
  args: exports_external.object({}).optional()
28850
28850
  });
28851
+ AgentStatusRequestSchema = exports_external.object({
28852
+ ...RequestEnvelope,
28853
+ op: exports_external.literal("agent_status"),
28854
+ args: exports_external.object({
28855
+ name: AgentNameSchema2.optional()
28856
+ })
28857
+ });
28858
+ AgentScheduleRequestSchema = exports_external.object({
28859
+ ...RequestEnvelope,
28860
+ op: exports_external.literal("agent_schedule"),
28861
+ args: exports_external.object({
28862
+ name: AgentNameSchema2.optional()
28863
+ })
28864
+ });
28851
28865
  AgentSmokeRequestSchema = exports_external.object({
28852
28866
  ...RequestEnvelope,
28853
28867
  op: exports_external.literal("agent_smoke"),
@@ -28878,6 +28892,8 @@ var init_protocol3 = __esm(() => {
28878
28892
  AgentExecRequestSchema,
28879
28893
  DoctorRequestSchema,
28880
28894
  AgentSmokeRequestSchema,
28895
+ AgentStatusRequestSchema,
28896
+ AgentScheduleRequestSchema,
28881
28897
  ConfigProposeEditRequestSchema
28882
28898
  ]);
28883
28899
  ResultSchema = exports_external.enum(["started", "completed", "denied", "error"]);
@@ -28929,6 +28945,7 @@ var init_protocol3 = __esm(() => {
28929
28945
  audit_id: exports_external.string().min(1).optional(),
28930
28946
  stdout_tail: exports_external.string().optional(),
28931
28947
  stderr_tail: exports_external.string().optional(),
28948
+ payload: exports_external.string().optional(),
28932
28949
  error: exports_external.string().optional(),
28933
28950
  error_envelope: ErrorEnvelopeSchema.optional()
28934
28951
  };
@@ -29314,12 +29331,12 @@ import {
29314
29331
  readFileSync as readFileSync48,
29315
29332
  readdirSync as readdirSync19
29316
29333
  } from "node:fs";
29317
- import { dirname as dirname12, join as join47 } from "node:path";
29334
+ import { dirname as dirname12, join as join48 } from "node:path";
29318
29335
  import { execSync as execSync2 } from "node:child_process";
29319
29336
  function locateManifestPath() {
29320
29337
  let dir = import.meta.dirname;
29321
29338
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
29322
- const candidate = join47(dir, "dependencies.json");
29339
+ const candidate = join48(dir, "dependencies.json");
29323
29340
  if (existsSync52(candidate))
29324
29341
  return candidate;
29325
29342
  dir = dirname12(dir);
@@ -29389,13 +29406,13 @@ function probeClaudeVersion() {
29389
29406
  }
29390
29407
  function probePlaywrightMcpVersion() {
29391
29408
  const home2 = process.env.HOME ?? "";
29392
- const npxCache = join47(home2, ".npm/_npx");
29409
+ const npxCache = join48(home2, ".npm/_npx");
29393
29410
  if (!existsSync52(npxCache))
29394
29411
  return null;
29395
29412
  try {
29396
29413
  const entries = readdirSync19(npxCache);
29397
29414
  for (const entry of entries) {
29398
- const pkgPath = join47(npxCache, entry, "node_modules/@playwright/mcp/package.json");
29415
+ const pkgPath = join48(npxCache, entry, "node_modules/@playwright/mcp/package.json");
29399
29416
  if (existsSync52(pkgPath)) {
29400
29417
  try {
29401
29418
  const pkg = JSON.parse(readFileSync48(pkgPath, "utf-8"));
@@ -29840,8 +29857,8 @@ var init_doctor_docker = __esm(() => {
29840
29857
  import { existsSync as existsSync53, readFileSync as readFileSync50 } from "node:fs";
29841
29858
  import { createHash as createHash10 } from "node:crypto";
29842
29859
  import { spawnSync as spawnSync6 } from "node:child_process";
29843
- import { homedir as homedir26 } from "node:os";
29844
- import { join as join48 } from "node:path";
29860
+ import { homedir as homedir27 } from "node:os";
29861
+ import { join as join49 } from "node:path";
29845
29862
  function defaultDockerInspect(container, format) {
29846
29863
  try {
29847
29864
  const r = spawnSync6("docker", ["inspect", "-f", format, container], { encoding: "utf-8", timeout: 5000 });
@@ -29941,7 +29958,7 @@ function checkAuthBrokerPerAgentSockets(config, deps = {}) {
29941
29958
  }
29942
29959
  function checkAuthBrokerDrift(deps = {}) {
29943
29960
  const stateDir = resolveStateDir(deps);
29944
- const indexPath = join48(stateDir, "sha-index.json");
29961
+ const indexPath = join49(stateDir, "sha-index.json");
29945
29962
  if (!existsSync53(indexPath)) {
29946
29963
  return {
29947
29964
  name: "auth-broker: drift",
@@ -29960,7 +29977,7 @@ function checkAuthBrokerDrift(deps = {}) {
29960
29977
  fix: "Inspect `~/.switchroom/state/auth-broker/sha-index.json` for corruption."
29961
29978
  };
29962
29979
  }
29963
- const home2 = deps.home ?? homedir26();
29980
+ const home2 = deps.home ?? homedir27();
29964
29981
  const divergent = [];
29965
29982
  const missingOnDisk = [];
29966
29983
  for (const [label, expected] of Object.entries(index)) {
@@ -30002,7 +30019,7 @@ function checkAuthBrokerDrift(deps = {}) {
30002
30019
  }
30003
30020
  function checkAuthBrokerThresholdViolations(deps = {}) {
30004
30021
  const stateDir = resolveStateDir(deps);
30005
- const path4 = join48(stateDir, "threshold-violations.json");
30022
+ const path4 = join49(stateDir, "threshold-violations.json");
30006
30023
  if (!existsSync53(path4)) {
30007
30024
  return {
30008
30025
  name: "auth-broker: threshold violations",
@@ -30046,7 +30063,7 @@ function checkAuthBrokerActiveAccount(config, deps = {}) {
30046
30063
  fix: "Run `switchroom auth use <label>` to pin a fleet-wide account, then `switchroom apply`. Without an active account every agent boot fails."
30047
30064
  };
30048
30065
  }
30049
- const home2 = deps.home ?? homedir26();
30066
+ const home2 = deps.home ?? homedir27();
30050
30067
  const dir = accountDir(active, home2);
30051
30068
  if (!existsSync53(dir)) {
30052
30069
  return {
@@ -30198,8 +30215,8 @@ import {
30198
30215
  realpathSync as realpathSync5,
30199
30216
  statSync as statSync23
30200
30217
  } from "node:fs";
30201
- import { userInfo, homedir as homedir27 } from "node:os";
30202
- import { join as join49 } from "node:path";
30218
+ import { userInfo, homedir as homedir28 } from "node:os";
30219
+ import { join as join50 } from "node:path";
30203
30220
  function resolveVaultPath2(config) {
30204
30221
  return config.vault?.path ? config.vault.path.replace(/^~/, process.env.HOME ?? "") : resolveStatePath("vault.enc");
30205
30222
  }
@@ -30337,7 +30354,7 @@ async function runSecretAccessChecks(config, deps = {}) {
30337
30354
  };
30338
30355
  const passphrase = deps.passphrase ?? process.env.SWITCHROOM_VAULT_PASSPHRASE;
30339
30356
  if (!passphrase) {
30340
- const sock = deps.brokerOperatorSocket ?? join49(homedir27(), ".switchroom", "broker-operator", "sock");
30357
+ const sock = deps.brokerOperatorSocket ?? join50(homedir28(), ".switchroom", "broker-operator", "sock");
30341
30358
  const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
30342
30359
  for (const name of Object.keys(config.agents ?? {})) {
30343
30360
  const resolved = resolveAgentConfig(config.defaults, config.profiles, config.agents[name]);
@@ -30422,8 +30439,8 @@ import {
30422
30439
  existsSync as realExistsSync,
30423
30440
  readFileSync as realReadFileSync
30424
30441
  } from "node:fs";
30425
- import { join as join50, resolve as resolve30 } from "node:path";
30426
- import { homedir as homedir28 } from "node:os";
30442
+ import { join as join51, resolve as resolve30 } from "node:path";
30443
+ import { homedir as homedir29 } from "node:os";
30427
30444
  function resolveDeps(config, deps) {
30428
30445
  let agentsDir = deps.agentsDir;
30429
30446
  if (agentsDir === undefined) {
@@ -30546,8 +30563,8 @@ function checkScaffoldWiring(config, driveAgents, d) {
30546
30563
  });
30547
30564
  continue;
30548
30565
  }
30549
- const mcpPath = join50(agentDir, ".mcp.json");
30550
- const claudeJsonPath = join50(agentDir, ".claude", ".claude.json");
30566
+ const mcpPath = join51(agentDir, ".mcp.json");
30567
+ const claudeJsonPath = join51(agentDir, ".claude", ".claude.json");
30551
30568
  const mcpRead = readJson(d, mcpPath);
30552
30569
  const trustRead = readJson(d, claudeJsonPath);
30553
30570
  if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
@@ -30660,7 +30677,7 @@ async function runDriveBrokerReachabilityChecks(config, deps = {}) {
30660
30677
  }
30661
30678
  ];
30662
30679
  }
30663
- const sock = deps.brokerOperatorSocket ?? join50(homedir28(), ".switchroom", "broker-operator", "sock");
30680
+ const sock = deps.brokerOperatorSocket ?? join51(homedir29(), ".switchroom", "broker-operator", "sock");
30664
30681
  const preflight = deps.preflight ?? ((a, k) => defaultPreflight(sock, a, k));
30665
30682
  const results = [];
30666
30683
  for (const agent of driveAgents) {
@@ -30714,8 +30731,8 @@ import {
30714
30731
  readSync as realReadSync,
30715
30732
  closeSync as realCloseSync
30716
30733
  } from "node:fs";
30717
- import { join as join51 } from "node:path";
30718
- import { homedir as homedir29 } from "node:os";
30734
+ import { join as join52 } from "node:path";
30735
+ import { homedir as homedir30 } from "node:os";
30719
30736
  function defaultReadHead(p, n) {
30720
30737
  let fd;
30721
30738
  try {
@@ -30743,7 +30760,7 @@ function resolveDeps2(config, deps) {
30743
30760
  }
30744
30761
  }
30745
30762
  return {
30746
- homeDir: deps.homeDir ?? homedir29(),
30763
+ homeDir: deps.homeDir ?? homedir30(),
30747
30764
  agentsDir,
30748
30765
  existsSync: deps.existsSync ?? ((p) => realExistsSync2(p)),
30749
30766
  readFileSync: deps.readFileSync ?? ((p) => realReadFileSync2(p, "utf-8")),
@@ -30772,7 +30789,7 @@ function runWebkiteChecks(config, deps = {}) {
30772
30789
  return [];
30773
30790
  const d = resolveDeps2(config, deps);
30774
30791
  const results = [];
30775
- const binPath = join51(d.homeDir, ".switchroom", "bin", "webkite");
30792
+ const binPath = join52(d.homeDir, ".switchroom", "bin", "webkite");
30776
30793
  if (!d.existsSync(binPath)) {
30777
30794
  results.push({
30778
30795
  name: "webkite: binary",
@@ -30802,12 +30819,12 @@ function runWebkiteChecks(config, deps = {}) {
30802
30819
  });
30803
30820
  }
30804
30821
  }
30805
- const cloakDir = join51(d.homeDir, ".cloakbrowser");
30822
+ const cloakDir = join52(d.homeDir, ".cloakbrowser");
30806
30823
  let chromeFound = false;
30807
30824
  if (d.existsSync(cloakDir)) {
30808
30825
  try {
30809
30826
  for (const entry of d.readdirSync(cloakDir)) {
30810
- if (entry.startsWith("chromium-") && d.existsSync(join51(cloakDir, entry, "chrome"))) {
30827
+ if (entry.startsWith("chromium-") && d.existsSync(join52(cloakDir, entry, "chrome"))) {
30811
30828
  chromeFound = true;
30812
30829
  break;
30813
30830
  }
@@ -30833,9 +30850,9 @@ function runWebkiteChecks(config, deps = {}) {
30833
30850
  return results;
30834
30851
  }
30835
30852
  for (const agent of enabledAgents) {
30836
- const agentDir = join51(d.agentsDir, agent);
30837
- const settingsPath = join51(agentDir, ".claude", "settings.json");
30838
- const mcpPath = join51(agentDir, ".mcp.json");
30853
+ const agentDir = join52(d.agentsDir, agent);
30854
+ const settingsPath = join52(agentDir, ".claude", "settings.json");
30855
+ const mcpPath = join52(agentDir, ".mcp.json");
30839
30856
  if (!d.existsSync(settingsPath) && !d.existsSync(mcpPath)) {
30840
30857
  continue;
30841
30858
  }
@@ -30965,7 +30982,7 @@ var init_doctor_cron_session = __esm(() => {
30965
30982
  });
30966
30983
 
30967
30984
  // src/cli/doctor-scaffold-wiring.ts
30968
- import { join as join52, resolve as resolve32 } from "node:path";
30985
+ import { join as join53, resolve as resolve32 } from "node:path";
30969
30986
  function readJson2(d, path4) {
30970
30987
  if (!d.existsSync(path4))
30971
30988
  return { kind: "absent" };
@@ -31008,8 +31025,8 @@ function checkIntegrationScaffoldWiring(args) {
31008
31025
  });
31009
31026
  continue;
31010
31027
  }
31011
- const mcpPath = join52(agentDir, ".mcp.json");
31012
- const claudeJsonPath = join52(agentDir, ".claude", ".claude.json");
31028
+ const mcpPath = join53(agentDir, ".mcp.json");
31029
+ const claudeJsonPath = join53(agentDir, ".claude", ".claude.json");
31013
31030
  const mcpRead = readJson2(deps, mcpPath);
31014
31031
  const trustRead = readJson2(deps, claudeJsonPath);
31015
31032
  if (mcpRead.kind === "unreadable" || trustRead.kind === "unreadable") {
@@ -31073,14 +31090,14 @@ import {
31073
31090
  existsSync as realExistsSync3,
31074
31091
  readFileSync as realReadFileSync3
31075
31092
  } from "node:fs";
31076
- import { join as join53 } from "node:path";
31077
- import { homedir as homedir30 } from "node:os";
31093
+ import { join as join54 } from "node:path";
31094
+ import { homedir as homedir31 } from "node:os";
31078
31095
  function resolveDeps3(deps) {
31079
- const home2 = deps.homeDir?.() ?? homedir30();
31096
+ const home2 = deps.homeDir?.() ?? homedir31();
31080
31097
  return {
31081
31098
  existsSync: deps.existsSync ?? realExistsSync3,
31082
31099
  readFileSync: deps.readFileSync ?? realReadFileSync3,
31083
- agentsDir: join53(home2, ".switchroom", "agents"),
31100
+ agentsDir: join54(home2, ".switchroom", "agents"),
31084
31101
  now: deps.now ?? Date.now
31085
31102
  };
31086
31103
  }
@@ -31163,7 +31180,7 @@ function checkOAuthClient2(config, anyAgentEnabled) {
31163
31180
  ];
31164
31181
  }
31165
31182
  function readHeartbeat(d, agentName) {
31166
- const path4 = join53(d.agentsDir, agentName, "m365-launcher.heartbeat.json");
31183
+ const path4 = join54(d.agentsDir, agentName, "m365-launcher.heartbeat.json");
31167
31184
  if (!d.existsSync(path4)) {
31168
31185
  return { error: "heartbeat file missing \u2014 launcher has not yet started" };
31169
31186
  }
@@ -31260,15 +31277,15 @@ import {
31260
31277
  readFileSync as realReadFileSync4,
31261
31278
  statSync as realStatSync2
31262
31279
  } from "node:fs";
31263
- import { join as join54 } from "node:path";
31264
- import { homedir as homedir31 } from "node:os";
31280
+ import { join as join55 } from "node:path";
31281
+ import { homedir as homedir32 } from "node:os";
31265
31282
  function resolveDeps4(deps) {
31266
- const home2 = deps.homeDir?.() ?? homedir31();
31283
+ const home2 = deps.homeDir?.() ?? homedir32();
31267
31284
  return {
31268
31285
  existsSync: deps.existsSync ?? realExistsSync4,
31269
31286
  readFileSync: deps.readFileSync ?? realReadFileSync4,
31270
31287
  statSync: deps.statSync ?? realStatSync2,
31271
- agentsDir: join54(home2, ".switchroom", "agents"),
31288
+ agentsDir: join55(home2, ".switchroom", "agents"),
31272
31289
  now: deps.now ?? Date.now,
31273
31290
  vaultAclReader: deps.vaultAclReader ?? (async () => ({ kind: "unreachable", msg: "no default reader wired" }))
31274
31291
  };
@@ -31393,7 +31410,7 @@ function checkLauncherHeartbeat2(notionAgents, d) {
31393
31410
  return [];
31394
31411
  const results = [];
31395
31412
  for (const name of notionAgents) {
31396
- const heartbeatPath = join54(d.agentsDir, name, "notion-launcher.heartbeat.json");
31413
+ const heartbeatPath = join55(d.agentsDir, name, "notion-launcher.heartbeat.json");
31397
31414
  if (!d.existsSync(heartbeatPath)) {
31398
31415
  results.push({
31399
31416
  name: `notion:launcher-heartbeat:${name}`,
@@ -31468,10 +31485,10 @@ import {
31468
31485
  readdirSync as realReaddirSync2,
31469
31486
  statSync as realStatSync3
31470
31487
  } from "node:fs";
31471
- import { homedir as homedir32 } from "node:os";
31472
- import { join as join55 } from "node:path";
31488
+ import { homedir as homedir33 } from "node:os";
31489
+ import { join as join56 } from "node:path";
31473
31490
  function runCredentialsMigrationChecks(config, deps = {}) {
31474
- const credDir = deps.credentialsDir ?? join55(homedir32(), ".switchroom", "credentials");
31491
+ const credDir = deps.credentialsDir ?? join56(homedir33(), ".switchroom", "credentials");
31475
31492
  const existsSync55 = deps.existsSync ?? ((p) => realExistsSync5(p));
31476
31493
  const readdirSync21 = deps.readdirSync ?? ((p) => realReaddirSync2(p));
31477
31494
  const isDirectory = deps.isDirectory ?? ((p) => {
@@ -31499,7 +31516,7 @@ function runCredentialsMigrationChecks(config, deps = {}) {
31499
31516
  const flat = [];
31500
31517
  const perAgentDirs = [];
31501
31518
  for (const e of entries) {
31502
- const full = join55(credDir, e);
31519
+ const full = join56(credDir, e);
31503
31520
  if (isDirectory(full) && agentNames.has(e)) {
31504
31521
  perAgentDirs.push(e);
31505
31522
  } else {
@@ -31622,19 +31639,19 @@ var init_doctor_inlined_secrets = __esm(() => {
31622
31639
 
31623
31640
  // src/cli/doctor-audit-integrity.ts
31624
31641
  import { readFileSync as fsReadFileSync2 } from "node:fs";
31625
- import { homedir as homedir33 } from "node:os";
31626
- import { join as join56 } from "node:path";
31642
+ import { homedir as homedir34 } from "node:os";
31643
+ import { join as join57 } from "node:path";
31627
31644
  function rootWrittenLogs(home2) {
31628
31645
  return [
31629
- { label: "vault-broker", path: join56(home2, ".switchroom", "vault-audit.log") },
31646
+ { label: "vault-broker", path: join57(home2, ".switchroom", "vault-audit.log") },
31630
31647
  {
31631
31648
  label: "hostd",
31632
- path: join56(home2, ".switchroom", "host-control-audit.log")
31649
+ path: join57(home2, ".switchroom", "host-control-audit.log")
31633
31650
  }
31634
31651
  ];
31635
31652
  }
31636
31653
  function runAuditIntegrityChecks(deps = {}) {
31637
- const home2 = deps.homeDir ?? homedir33();
31654
+ const home2 = deps.homeDir ?? homedir34();
31638
31655
  const read = deps.readFileSync ?? ((p) => fsReadFileSync2(p, "utf8"));
31639
31656
  const results = [];
31640
31657
  for (const { label, path: path4 } of rootWrittenLogs(home2)) {
@@ -31692,14 +31709,14 @@ var init_doctor_audit_integrity = __esm(() => {
31692
31709
 
31693
31710
  // src/cli/doctor-agent-smoke.ts
31694
31711
  import { existsSync as existsSync55 } from "node:fs";
31695
- import { homedir as homedir34 } from "node:os";
31696
- import { join as join57 } from "node:path";
31712
+ import { homedir as homedir35 } from "node:os";
31713
+ import { join as join58 } from "node:path";
31697
31714
  import { randomUUID as randomUUID5 } from "node:crypto";
31698
31715
  async function runAgentSmokeChecks(config, deps = {}) {
31699
31716
  if (deps.fast)
31700
31717
  return [];
31701
- const home2 = deps.homeDir ?? homedir34();
31702
- const sock = deps.operatorSockPath ?? join57(home2, ".switchroom", "hostd", "operator", "sock");
31718
+ const home2 = deps.homeDir ?? homedir35();
31719
+ const sock = deps.operatorSockPath ?? join58(home2, ".switchroom", "hostd", "operator", "sock");
31703
31720
  if (!deps.hostdRequestImpl && !existsSync55(sock)) {
31704
31721
  return [
31705
31722
  {
@@ -31779,8 +31796,8 @@ var init_doctor_agent_smoke = __esm(() => {
31779
31796
  // src/cli/doctor-vault-broker-durability.ts
31780
31797
  import { execFileSync as execFileSync18 } from "node:child_process";
31781
31798
  import { existsSync as existsSync56, statSync as statSync24 } from "node:fs";
31782
- import { homedir as homedir35 } from "node:os";
31783
- import { join as join58 } from "node:path";
31799
+ import { homedir as homedir36 } from "node:os";
31800
+ import { join as join59 } from "node:path";
31784
31801
  function probeBindMountInode(hostPath, brokerContainerPath, opts) {
31785
31802
  const statHost = opts?.statHost ?? defaultStatHost;
31786
31803
  const statBroker = opts?.statBroker ?? defaultStatBroker;
@@ -31923,22 +31940,22 @@ function defaultBrokerStatusProbe() {
31923
31940
  }
31924
31941
  }
31925
31942
  function runVaultBrokerDurabilityChecks(_config, opts) {
31926
- const home2 = homedir35();
31943
+ const home2 = homedir36();
31927
31944
  const probe2 = opts?.inodeProbe ?? probeBindMountInode;
31928
31945
  return [
31929
31946
  probeBrokerUnlocked(opts?.statusProbe),
31930
31947
  probeAutoUnlockBlob(home2),
31931
31948
  probeMachineIdMount(),
31932
- formatBindMountResult("vault-broker: vault.enc bind mount", join58(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join58(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
31933
- formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join58(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join58(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
31934
- formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join58(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join58(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
31949
+ formatBindMountResult("vault-broker: vault.enc bind mount", join59(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc", probe2(join59(home2, ".switchroom", "vault", "vault.enc"), "/state/vault/vault.enc")),
31950
+ formatBindMountResult("vault-broker: vault-grants.db bind mount (#1737)", join59(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db", probe2(join59(home2, ".switchroom", "vault-grants.db"), "/root/.switchroom/vault-grants.db")),
31951
+ formatBindMountResult("vault-broker: vault-audit.log bind mount (#1025)", join59(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log", probe2(join59(home2, ".switchroom", "vault-audit.log"), "/root/.switchroom/vault-audit.log")),
31935
31952
  probeKernelDbDurability(home2, {
31936
31953
  statBroker: opts?.kernelStatBroker
31937
31954
  })
31938
31955
  ];
31939
31956
  }
31940
31957
  function probeKernelDbDurability(home2, opts) {
31941
- const hostDir = join58(home2, ".switchroom", "approvals");
31958
+ const hostDir = join59(home2, ".switchroom", "approvals");
31942
31959
  const containerDir = "/state/approvals";
31943
31960
  const name = "approval-kernel: approvals bind mount (allow_always durability)";
31944
31961
  const kernelStat = opts?.statBroker ?? defaultKernelStatBroker;
@@ -32006,7 +32023,7 @@ function defaultKernelStatBroker(p) {
32006
32023
  return { kind: "ok-with-stat", ino: inoStr, size };
32007
32024
  }
32008
32025
  function probeAutoUnlockBlob(home2) {
32009
- const blobPath = join58(home2, ".switchroom", "vault-auto-unlock");
32026
+ const blobPath = join59(home2, ".switchroom", "vault-auto-unlock");
32010
32027
  if (!existsSync56(blobPath)) {
32011
32028
  return {
32012
32029
  name: "vault-broker: auto-unlock blob",
@@ -32118,16 +32135,16 @@ import {
32118
32135
  readdirSync as readdirSync21,
32119
32136
  statSync as statSync25
32120
32137
  } from "node:fs";
32121
- import { dirname as dirname13, join as join59, resolve as resolve33 } from "node:path";
32138
+ import { dirname as dirname13, join as join60, resolve as resolve33 } from "node:path";
32122
32139
  import { createPublicKey, createPrivateKey } from "node:crypto";
32123
32140
  function findInNvm(bin) {
32124
- const nvmRoot = join59(process.env.HOME ?? "", ".nvm", "versions", "node");
32141
+ const nvmRoot = join60(process.env.HOME ?? "", ".nvm", "versions", "node");
32125
32142
  if (!existsSync57(nvmRoot))
32126
32143
  return null;
32127
32144
  try {
32128
32145
  const versions = readdirSync21(nvmRoot).sort().reverse();
32129
32146
  for (const v of versions) {
32130
- const candidate = join59(nvmRoot, v, "bin", bin);
32147
+ const candidate = join60(nvmRoot, v, "bin", bin);
32131
32148
  try {
32132
32149
  const s = statSync25(candidate);
32133
32150
  if (s.isFile() || s.isSymbolicLink()) {
@@ -32292,7 +32309,7 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
32292
32309
  if (envBrowsersPath && envBrowsersPath.length > 0) {
32293
32310
  cacheLocations.push(envBrowsersPath);
32294
32311
  }
32295
- cacheLocations.push(join59(homeDir, ".cache", "ms-playwright"));
32312
+ cacheLocations.push(join60(homeDir, ".cache", "ms-playwright"));
32296
32313
  for (const cacheDir of cacheLocations) {
32297
32314
  if (!existsSync57(cacheDir))
32298
32315
  continue;
@@ -32300,10 +32317,10 @@ function findChromium(homeDir = process.env.HOME ?? "", envBrowsersPath = proces
32300
32317
  const entries = readdirSync21(cacheDir).filter((e) => e.startsWith("chromium"));
32301
32318
  for (const entry of entries) {
32302
32319
  const candidates2 = [
32303
- join59(cacheDir, entry, "chrome-linux64", "chrome"),
32304
- join59(cacheDir, entry, "chrome-linux", "chrome"),
32305
- join59(cacheDir, entry, "chrome-linux64", "headless_shell"),
32306
- join59(cacheDir, entry, "chrome-linux", "headless_shell")
32320
+ join60(cacheDir, entry, "chrome-linux64", "chrome"),
32321
+ join60(cacheDir, entry, "chrome-linux", "chrome"),
32322
+ join60(cacheDir, entry, "chrome-linux64", "headless_shell"),
32323
+ join60(cacheDir, entry, "chrome-linux", "headless_shell")
32307
32324
  ];
32308
32325
  for (const path4 of candidates2) {
32309
32326
  if (existsSync57(path4))
@@ -32426,7 +32443,7 @@ function checkUserDeclaredMcps(name, agentConfig, config, renderedMcpServers) {
32426
32443
  function checkLegacyState() {
32427
32444
  const results = [];
32428
32445
  const h = process.env.HOME ?? "/root";
32429
- const clerkDir = join59(h, LEGACY_STATE_DIR);
32446
+ const clerkDir = join60(h, LEGACY_STATE_DIR);
32430
32447
  const clerkPresent = existsSync57(clerkDir);
32431
32448
  results.push({
32432
32449
  name: "legacy ~/.clerk state",
@@ -32436,7 +32453,7 @@ function checkLegacyState() {
32436
32453
  fix: "Legacy state detected. Run `mv ~/.clerk ~/.switchroom` and rename " + "any top-level `clerk:` key in switchroom.yaml to `switchroom:`. " + "This back-compat shim is REMOVED in v0.13.0 \u2014 no automatic " + "migration exists."
32437
32454
  } : {}
32438
32455
  });
32439
- const legacySock = join59(h, ".switchroom", "vault-broker.sock");
32456
+ const legacySock = join60(h, ".switchroom", "vault-broker.sock");
32440
32457
  let sockStat = null;
32441
32458
  try {
32442
32459
  sockStat = lstatSync6(legacySock);
@@ -32814,7 +32831,7 @@ async function checkHindsight(config) {
32814
32831
  }
32815
32832
  function checkPendingRetainsQueue(dir) {
32816
32833
  const home2 = process.env.HOME ?? "";
32817
- const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join59(home2, ".hindsight", "pending-retains");
32834
+ const pendingDir = dir ?? process.env.HINDSIGHT_PENDING_DIR ?? join60(home2, ".hindsight", "pending-retains");
32818
32835
  if (!existsSync57(pendingDir)) {
32819
32836
  return {
32820
32837
  name: "pending-retains queue",
@@ -32945,7 +32962,7 @@ async function checkTelegram(config) {
32945
32962
  const plugin = agentConfig.channels?.telegram?.plugin ?? "switchroom";
32946
32963
  if (plugin !== "switchroom")
32947
32964
  continue;
32948
- const envPath = join59(agentsDir, name, "telegram", ".env");
32965
+ const envPath = join60(agentsDir, name, "telegram", ".env");
32949
32966
  const read = tryReadHostFile(envPath);
32950
32967
  if (read.kind === "eacces") {
32951
32968
  results.push({
@@ -33028,7 +33045,7 @@ function checkStartShStale(agentName, startShPath) {
33028
33045
  }
33029
33046
  function checkLeakedHomeSwitchroom(agentName, agentDir) {
33030
33047
  const label = `${agentName}: $HOME/.switchroom symlink (#910)`;
33031
- const path4 = join59(agentDir, "home", ".switchroom");
33048
+ const path4 = join60(agentDir, "home", ".switchroom");
33032
33049
  let stats;
33033
33050
  try {
33034
33051
  stats = lstatSync6(path4);
@@ -33065,7 +33082,7 @@ function checkLeakedHomeSwitchroom(agentName, agentDir) {
33065
33082
  }
33066
33083
  function checkRepoHygiene(repoRoot) {
33067
33084
  const results = [];
33068
- const exportDir = join59(repoRoot, "clerk-export");
33085
+ const exportDir = join60(repoRoot, "clerk-export");
33069
33086
  if (existsSync57(exportDir)) {
33070
33087
  results.push({
33071
33088
  name: "repo hygiene: clerk-export/ on disk (#1072)",
@@ -33074,7 +33091,7 @@ function checkRepoHygiene(repoRoot) {
33074
33091
  fix: `Run scripts/migrate-clerk-export-to-vault.sh to move the bundle ` + `into the vault, then delete the on-disk copy.`
33075
33092
  });
33076
33093
  }
33077
- const knownTarball = join59(repoRoot, "clerk-export-with-secrets.tar.gz");
33094
+ const knownTarball = join60(repoRoot, "clerk-export-with-secrets.tar.gz");
33078
33095
  if (existsSync57(knownTarball)) {
33079
33096
  results.push({
33080
33097
  name: "repo hygiene: clerk-export-with-secrets.tar.gz on disk (#1072)",
@@ -33092,7 +33109,7 @@ function checkRepoHygiene(repoRoot) {
33092
33109
  results.push({
33093
33110
  name: `repo hygiene: ${name} on disk (#1072)`,
33094
33111
  status: "warn",
33095
- detail: `${join59(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
33112
+ detail: `${join60(repoRoot, name)} matches the *-with-secrets*.tar.gz ` + `pattern. Likely contains real credentials.`,
33096
33113
  fix: `Inspect, migrate any secrets into the vault, then delete the ` + `archive.`
33097
33114
  });
33098
33115
  }
@@ -33115,9 +33132,9 @@ function checkRepoHygiene(repoRoot) {
33115
33132
  }
33116
33133
  function isSwitchroomCheckout(dir) {
33117
33134
  try {
33118
- if (!existsSync57(join59(dir, ".git")))
33135
+ if (!existsSync57(join60(dir, ".git")))
33119
33136
  return false;
33120
- const pkgPath = join59(dir, "package.json");
33137
+ const pkgPath = join60(dir, "package.json");
33121
33138
  if (!existsSync57(pkgPath))
33122
33139
  return false;
33123
33140
  const pkg = JSON.parse(readFileSync51(pkgPath, "utf-8"));
@@ -33154,7 +33171,7 @@ function checkAgents(config, configPath) {
33154
33171
  fix: `Rotate the bot token (e.g. via \`switchroom vault\`), then run ` + `\`switchroom agent unquarantine ${name}\` and \`switchroom agent restart ${name}\``
33155
33172
  });
33156
33173
  }
33157
- results.push(checkStartShStale(name, join59(agentDir, "start.sh")));
33174
+ results.push(checkStartShStale(name, join60(agentDir, "start.sh")));
33158
33175
  results.push(checkLeakedHomeSwitchroom(name, agentDir));
33159
33176
  const status = statuses[name];
33160
33177
  const active = status?.active ?? "unknown";
@@ -33231,7 +33248,7 @@ function checkAgents(config, configPath) {
33231
33248
  }
33232
33249
  }
33233
33250
  if (agentConfig.channels?.telegram?.plugin === "switchroom") {
33234
- const mcpJsonPath = join59(agentDir, ".mcp.json");
33251
+ const mcpJsonPath = join60(agentDir, ".mcp.json");
33235
33252
  if (!existsSync57(mcpJsonPath)) {
33236
33253
  results.push({
33237
33254
  name: `${name}: .mcp.json`,
@@ -33533,7 +33550,7 @@ async function checkMffAuthFlow(envPath = mffEnvPath(), timeoutMs = 8000) {
33533
33550
  };
33534
33551
  }
33535
33552
  const credDir = dirname13(envPath);
33536
- const authScript = join59(credDir, "claude-auth.py");
33553
+ const authScript = join60(credDir, "claude-auth.py");
33537
33554
  if (!existsSync57(authScript)) {
33538
33555
  return {
33539
33556
  name: "mff: auth flow",
@@ -50477,8 +50494,8 @@ var {
50477
50494
  } = import__.default;
50478
50495
 
50479
50496
  // src/build-info.ts
50480
- var VERSION = "0.15.25";
50481
- var COMMIT_SHA = "0d066743";
50497
+ var VERSION = "0.15.27";
50498
+ var COMMIT_SHA = "97160057";
50482
50499
 
50483
50500
  // src/cli/agent.ts
50484
50501
  init_source();
@@ -67478,6 +67495,23 @@ function addWebhookSource(yamlText, agentName, source) {
67478
67495
  }
67479
67496
  return String(doc);
67480
67497
  }
67498
+ function addAgentSecret(yamlText, agentName, key) {
67499
+ const doc = import_yaml11.parseDocument(yamlText);
67500
+ ensureAgent(doc, agentName);
67501
+ const existing = doc.getIn(["agents", agentName, "secrets"]);
67502
+ if (import_yaml11.isSeq(existing)) {
67503
+ const seq = existing;
67504
+ for (const item of seq.items) {
67505
+ const v = item.value ?? item;
67506
+ if (v === key)
67507
+ return yamlText;
67508
+ }
67509
+ seq.add(key);
67510
+ } else {
67511
+ doc.setIn(["agents", agentName, "secrets"], [key]);
67512
+ }
67513
+ return String(doc);
67514
+ }
67481
67515
  function removeWebhookSource(yamlText, agentName, source) {
67482
67516
  const doc = import_yaml11.parseDocument(yamlText);
67483
67517
  if (!hasAgent(doc, agentName))
@@ -67860,6 +67894,22 @@ async function vaultPut(program3, key, value) {
67860
67894
  setStringSecret(passphrase, vaultPath, key, value);
67861
67895
  console.log(source_default.green(`\u2713 Stored secret in vault as '${key}'`));
67862
67896
  }
67897
+ async function vaultPutQuiet(program3, key, value) {
67898
+ const configPath = program3.optsWithGlobals().config ?? undefined;
67899
+ const vaultPath = resolveVaultPath(configPath);
67900
+ const passphrase = await getVaultPassphrase();
67901
+ if (!existsSync43(vaultPath))
67902
+ createVault(passphrase, vaultPath);
67903
+ setStringSecret(passphrase, vaultPath, key, value);
67904
+ }
67905
+ async function vaultGet(program3, key) {
67906
+ const configPath = program3.optsWithGlobals().config ?? undefined;
67907
+ const vaultPath = resolveVaultPath(configPath);
67908
+ if (!existsSync43(vaultPath))
67909
+ return null;
67910
+ const passphrase = await getVaultPassphrase();
67911
+ return getStringSecret(passphrase, vaultPath, key);
67912
+ }
67863
67913
  function resolveVaultPath(configPath) {
67864
67914
  try {
67865
67915
  const config = loadConfig(configPath);
@@ -67988,9 +68038,118 @@ function promptHidden2(prompt) {
67988
68038
  init_source();
67989
68039
  init_helpers();
67990
68040
  import { readFileSync as readFileSync39, writeFileSync as writeFileSync22 } from "node:fs";
68041
+
68042
+ // src/linear/oauth-refresh.ts
68043
+ var LINEAR_TOKEN_ENDPOINT = "https://api.linear.app/oauth/token";
68044
+ var DEFAULT_REFRESH_SKEW_SEC = 2 * 3600;
68045
+ async function refreshLinearAppToken(bundle, opts = {}) {
68046
+ const fetchImpl = opts.fetchImpl ?? fetch;
68047
+ const nowSec = opts.nowSec ?? (() => Math.floor(Date.now() / 1000));
68048
+ const form = new URLSearchParams({
68049
+ grant_type: "refresh_token",
68050
+ refresh_token: bundle.refreshToken,
68051
+ client_id: bundle.clientId,
68052
+ client_secret: bundle.clientSecret
68053
+ });
68054
+ let resp;
68055
+ try {
68056
+ resp = await fetchImpl(LINEAR_TOKEN_ENDPOINT, {
68057
+ method: "POST",
68058
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
68059
+ body: form.toString()
68060
+ });
68061
+ } catch (err) {
68062
+ return { ok: false, reason: "network", detail: err.message };
68063
+ }
68064
+ if (!resp.ok) {
68065
+ const txt = await resp.text().catch(() => "");
68066
+ const revoked = resp.status === 400 || /invalid_grant|invalid_token/i.test(txt);
68067
+ return {
68068
+ ok: false,
68069
+ reason: revoked ? "revoked" : "http_error",
68070
+ detail: `HTTP ${resp.status}${txt ? ` ${txt.slice(0, 200)}` : ""}`
68071
+ };
68072
+ }
68073
+ let json;
68074
+ try {
68075
+ json = await resp.json();
68076
+ } catch {
68077
+ return { ok: false, reason: "bad_response", detail: "non-JSON token response" };
68078
+ }
68079
+ const accessToken = json.access_token;
68080
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
68081
+ return { ok: false, reason: "bad_response", detail: "no access_token in response" };
68082
+ }
68083
+ const expiresIn = typeof json.expires_in === "number" ? json.expires_in : 86400;
68084
+ const rotated = typeof json.refresh_token === "string" && json.refresh_token.length > 0 ? json.refresh_token : bundle.refreshToken;
68085
+ return {
68086
+ ok: true,
68087
+ accessToken,
68088
+ refreshToken: rotated,
68089
+ expiresAt: nowSec() + expiresIn,
68090
+ ...typeof json.scope === "string" ? { scope: json.scope } : {}
68091
+ };
68092
+ }
68093
+ function parseBundle(raw) {
68094
+ if (raw == null || raw === "")
68095
+ return null;
68096
+ let o;
68097
+ try {
68098
+ o = JSON.parse(raw);
68099
+ } catch {
68100
+ return null;
68101
+ }
68102
+ if (typeof o.client_id === "string" && typeof o.client_secret === "string" && typeof o.refresh_token === "string" && o.client_id.length > 0 && o.client_secret.length > 0 && o.refresh_token.length > 0) {
68103
+ return {
68104
+ clientId: o.client_id,
68105
+ clientSecret: o.client_secret,
68106
+ refreshToken: o.refresh_token,
68107
+ ...typeof o.expires_at === "number" ? { expiresAt: o.expires_at } : {}
68108
+ };
68109
+ }
68110
+ return null;
68111
+ }
68112
+ function serializeBundle(b) {
68113
+ return JSON.stringify({
68114
+ client_id: b.clientId,
68115
+ client_secret: b.clientSecret,
68116
+ refresh_token: b.refreshToken,
68117
+ ...b.expiresAt != null ? { expires_at: b.expiresAt } : {}
68118
+ });
68119
+ }
68120
+ async function performLinearRefresh(io) {
68121
+ const raw = await io.readBundle();
68122
+ const bundle = parseBundle(raw);
68123
+ if (!bundle) {
68124
+ return { ok: false, reason: "no_bundle", detail: "no/invalid refresh bundle" };
68125
+ }
68126
+ const res = await refreshLinearAppToken(bundle, {
68127
+ ...io.fetchImpl ? { fetchImpl: io.fetchImpl } : {},
68128
+ ...io.nowSec ? { nowSec: io.nowSec } : {}
68129
+ });
68130
+ if (!res.ok)
68131
+ return { ok: false, reason: res.reason, detail: res.detail };
68132
+ try {
68133
+ await io.writeBundle(serializeBundle({
68134
+ clientId: bundle.clientId,
68135
+ clientSecret: bundle.clientSecret,
68136
+ refreshToken: res.refreshToken,
68137
+ expiresAt: res.expiresAt
68138
+ }));
68139
+ await io.writeToken(res.accessToken);
68140
+ } catch (err) {
68141
+ return { ok: false, reason: "persist_failed", detail: err.message };
68142
+ }
68143
+ return { ok: true, accessToken: res.accessToken, expiresAt: res.expiresAt };
68144
+ }
68145
+
68146
+ // src/cli/linear-agent.ts
68147
+ function bundleKeyFor(agent) {
68148
+ return `linear/${agent}/oauth`;
68149
+ }
67991
68150
  function registerLinearAgentCommand(program3) {
67992
68151
  const linear = program3.command("linear-agent").description("Install an agent into a Linear workspace as a first-class app actor (#2298) \u2014 @-mentionable, delegate-assignable, agent sessions wake it instantly.");
67993
- linear.command("setup").description("Provision <agent> as a Linear agent. Vault-stores the Linear OAuth app token (actor=app) under 'linear/<agent>/token' and enables the linear_agent block in switchroom.yaml. The OAuth browser authorize step is printed as instructions (it can't run headless); pass the already-obtained --token.").requiredOption("--agent <name>", "Agent name (must exist in switchroom.yaml)").requiredOption("--token <token>", "The Linear OAuth app token (actor=app), obtained out-of-band via the browser authorize step. Stored in the vault, never in switchroom.yaml.").option("--client-id <id>", "Linear OAuth app client id (for the printed authorize-URL hint).").option("--client-secret <secret>", "Linear OAuth app client secret (informational \u2014 not stored by this verb).").option("--redirect-uri <uri>", "OAuth redirect URI registered on the Linear app (for the authorize-URL hint).").option("--workspace-id <id>", "Optional Linear workspace (organization) id to record in config.").option("--webhook-base <url>", "Base URL of the switchroom web server (e.g. https://hooks.switchroom.ai). Used to print the webhook URL to register in Linear. Defaults to a placeholder.").option("--dry-run", "Print the YAML diff + instructions without writing or vaulting anything").action(withConfigError(async (opts) => {
68152
+ linear.command("setup").description("Provision <agent> as a Linear agent. Vault-stores the Linear OAuth app token (actor=app) under 'linear/<agent>/token' and enables the linear_agent block in switchroom.yaml. The OAuth browser authorize step is printed as instructions (it can't run headless); pass the already-obtained --token.").requiredOption("--agent <name>", "Agent name (must exist in switchroom.yaml)").requiredOption("--token <token>", "The Linear OAuth app token (actor=app), obtained out-of-band via the browser authorize step. Stored in the vault, never in switchroom.yaml.").option("--client-id <id>", "Linear OAuth app client id. Stored (with --client-secret + --refresh-token) to enable unattended token refresh.").option("--client-secret <secret>", "Linear OAuth app client secret. Stored in the vault (with --client-id + --refresh-token) so the token can be refreshed without a browser re-auth.").option("--refresh-token <token>", "The refresh_token from the OAuth exchange. Stored so an expired access token self-heals (see 'linear-agent refresh').").option("--token-expires-in <seconds>", "expires_in from the OAuth token response (seconds). Records when the access token expires so refresh runs proactively. Defaults to 86400.").option("--redirect-uri <uri>", "OAuth redirect URI registered on the Linear app (for the authorize-URL hint).").option("--workspace-id <id>", "Optional Linear workspace (organization) id to record in config.").option("--webhook-base <url>", "Base URL of the switchroom web server (e.g. https://hooks.switchroom.ai). Used to print the webhook URL to register in Linear. Defaults to a placeholder.").option("--dry-run", "Print the YAML diff + instructions without writing or vaulting anything").action(withConfigError(async (opts) => {
67994
68153
  if (!/^[a-z][a-z0-9_-]{0,63}$/.test(opts.agent)) {
67995
68154
  fail2(`--agent must be a lowercase agent slug (got '${opts.agent}').`);
67996
68155
  }
@@ -67998,10 +68157,26 @@ function registerLinearAgentCommand(program3) {
67998
68157
  fail2("--token must be a non-empty Linear app token.");
67999
68158
  }
68000
68159
  const vaultKey = `linear/${opts.agent}/token`;
68160
+ const bundleKey = bundleKeyFor(opts.agent);
68161
+ const canRefresh = Boolean(opts.refreshToken && opts.clientId && opts.clientSecret);
68001
68162
  if (!opts.dryRun) {
68002
68163
  await vaultPut(program3, vaultKey, opts.token);
68164
+ if (canRefresh) {
68165
+ const expiresIn = Number.parseInt(opts.tokenExpiresIn ?? "", 10);
68166
+ const ttl = Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn : 86400;
68167
+ const bundle = serializeBundle({
68168
+ clientId: opts.clientId,
68169
+ clientSecret: opts.clientSecret,
68170
+ refreshToken: opts.refreshToken,
68171
+ expiresAt: Math.floor(Date.now() / 1000) + ttl
68172
+ });
68173
+ await vaultPutQuiet(program3, bundleKey, bundle);
68174
+ }
68003
68175
  } else {
68004
68176
  console.log(source_default.gray(`[dry-run] would store the Linear token in the vault as '${vaultKey}'`));
68177
+ if (canRefresh) {
68178
+ console.log(source_default.gray(`[dry-run] would store the refresh bundle as '${bundleKey}' (enables auto-refresh)`));
68179
+ }
68005
68180
  }
68006
68181
  const path4 = getConfigPath(program3);
68007
68182
  const before = readFileSync39(path4, "utf-8");
@@ -68011,6 +68186,10 @@ function registerLinearAgentCommand(program3) {
68011
68186
  token: `vault:${vaultKey}`,
68012
68187
  ...opts.workspaceId ? { workspaceId: opts.workspaceId } : {}
68013
68188
  });
68189
+ if (canRefresh) {
68190
+ after = addAgentSecret(after, opts.agent, bundleKey);
68191
+ after = addAgentSecret(after, opts.agent, vaultKey);
68192
+ }
68014
68193
  } catch (err) {
68015
68194
  fail2(err.message);
68016
68195
  }
@@ -68021,10 +68200,42 @@ function registerLinearAgentCommand(program3) {
68021
68200
  writeFileSync22(path4, after, "utf-8");
68022
68201
  console.log(source_default.green(`\u2713 Enabled linear-agent for agent '${opts.agent}'`));
68023
68202
  console.log(source_default.gray(` Vault key: ${vaultKey}`));
68203
+ if (canRefresh) {
68204
+ console.log(source_default.green(`\u2713 Auto-refresh enabled \u2014 refresh bundle stored at '${bundleKey}'`));
68205
+ console.log(source_default.gray(` Granted ACL: '${bundleKey}' + '${vaultKey}' added to agents.${opts.agent}.secrets[] (agent rotates them in-container on a 401).`));
68206
+ } else {
68207
+ console.log(source_default.yellow(`\u26a0 No refresh bundle stored \u2014 the access token will expire (~24h) and need a manual re-auth.`));
68208
+ console.log(source_default.gray(` To enable auto-refresh, re-run with --refresh-token <rt> --client-id <id> --client-secret <secret> --token-expires-in <sec>.`));
68209
+ }
68024
68210
  console.log(source_default.gray(` Run 'switchroom agent restart ${opts.agent}' to pick up the change.`));
68025
68211
  }
68026
68212
  printLinearInstructions(opts, vaultKey);
68027
68213
  }));
68214
+ linear.command("refresh").description("Refresh <agent>'s Linear app token using the stored refresh bundle (linear/<agent>/oauth). Exchanges the refresh_token for a fresh access token, writes it to linear/<agent>/token, and rotates the stored refresh_token + expiry. Use to recover an expired token or seed automation. Host-side write \u2014 the running agent picks it up on its next broker re-mirror / restart.").requiredOption("--agent <name>", "Agent name (must have a linear_agent block)").action(withConfigError(async (opts) => {
68215
+ if (!/^[a-z][a-z0-9_-]{0,63}$/.test(opts.agent)) {
68216
+ fail2(`--agent must be a lowercase agent slug (got '${opts.agent}').`);
68217
+ }
68218
+ const bundleKey = bundleKeyFor(opts.agent);
68219
+ const res = await performLinearRefresh({
68220
+ readBundle: () => vaultGet(program3, bundleKey),
68221
+ writeToken: (t) => vaultPutQuiet(program3, `linear/${opts.agent}/token`, t),
68222
+ writeBundle: (json) => vaultPutQuiet(program3, bundleKey, json)
68223
+ });
68224
+ if (!res.ok) {
68225
+ if (res.reason === "no_bundle") {
68226
+ fail2(`No refresh bundle at '${bundleKey}'. Provision one via 'linear-agent setup --agent ${opts.agent} ` + `--token <t> --refresh-token <rt> --client-id <id> --client-secret <secret>'.`);
68227
+ }
68228
+ if (res.reason === "revoked") {
68229
+ fail2(`Refresh token is dead (revoked/expired) \u2014 re-authorize in a browser (actor=app) and re-run setup with the new --refresh-token. (${res.detail})`);
68230
+ }
68231
+ fail2(`Refresh failed (${res.reason}): ${res.detail}`);
68232
+ }
68233
+ if (res.ok) {
68234
+ const hours = Math.max(1, Math.round((res.expiresAt - Date.now() / 1000) / 3600));
68235
+ console.log(source_default.green(`\u2713 Refreshed Linear token for '${opts.agent}' (expires in ~${hours}h).`));
68236
+ console.log(source_default.gray(` Written to vault:linear/${opts.agent}/token (+ rotated bundle). Restart the agent or wait for the broker to re-mirror.`));
68237
+ }
68238
+ }));
68028
68239
  linear.command("set-team").description("Set (or clear) the default Linear team captured issues file into for <agent>. Only needed when the workspace has multiple teams \u2014 a single-team workspace auto-resolves. Pass --clear to remove the default.").requiredOption("--agent <name>", "Agent name (must have a linear_agent block)").option("--team <id>", "Linear team id new captured issues default to.").option("--clear", "Remove the configured default team (revert to auto-resolve).").action(withConfigError(async (opts) => {
68029
68240
  if (!/^[a-z][a-z0-9_-]{0,63}$/.test(opts.agent)) {
68030
68241
  fail2(`--agent must be a lowercase agent slug (got '${opts.agent}').`);
@@ -68680,7 +68891,6 @@ init_source();
68680
68891
  init_merge();
68681
68892
  init_loader();
68682
68893
  init_client();
68683
- init_lifecycle();
68684
68894
  import {
68685
68895
  readFileSync as readFileSync46,
68686
68896
  existsSync as existsSync50,
@@ -68691,9 +68901,8 @@ import {
68691
68901
  writeSync as writeSync6,
68692
68902
  constants as fsConstants3
68693
68903
  } from "node:fs";
68694
- import { resolve as resolve28, extname, join as join46, relative, dirname as dirname10 } from "node:path";
68695
- import { homedir as homedir25 } from "node:os";
68696
- import { spawn as spawn5 } from "node:child_process";
68904
+ import { resolve as resolve28, extname, join as join47, relative, dirname as dirname10 } from "node:path";
68905
+ import { homedir as homedir26 } from "node:os";
68697
68906
  import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
68698
68907
 
68699
68908
  // src/web/api.ts
@@ -68702,7 +68911,71 @@ init_manager();
68702
68911
  init_hindsight();
68703
68912
  import { spawnSync as spawnSync5 } from "node:child_process";
68704
68913
  import { existsSync as existsSync47, readFileSync as readFileSync43, statSync as statSync22 } from "node:fs";
68705
- import { resolve as resolve27 } from "node:path";
68914
+ import { resolve as resolve27, join as join44 } from "node:path";
68915
+
68916
+ // src/web/memory-remediation.ts
68917
+ init_hindsight();
68918
+ function restBaseFromMcpUrl(mcpUrl) {
68919
+ return hindsightRestBase(mcpUrl);
68920
+ }
68921
+ async function postTrigger(url, opts) {
68922
+ const fetchImpl = opts?.fetchImpl ?? fetch;
68923
+ const timeoutMs = opts?.timeoutMs ?? 5000;
68924
+ const controller = new AbortController;
68925
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
68926
+ try {
68927
+ const resp = await fetchImpl(url, {
68928
+ method: "POST",
68929
+ headers: { "Content-Type": "application/json" },
68930
+ signal: controller.signal
68931
+ });
68932
+ clearTimeout(timeout);
68933
+ if (!resp.ok)
68934
+ return { ok: false, reason: `HTTP ${resp.status}` };
68935
+ return { ok: true };
68936
+ } catch (err) {
68937
+ clearTimeout(timeout);
68938
+ if (err.name === "AbortError")
68939
+ return { ok: false, reason: "Timeout" };
68940
+ return { ok: false, reason: String(err.message ?? err) };
68941
+ }
68942
+ }
68943
+ async function reprocessDocuments(restBase, bank, docIds, opts) {
68944
+ let triggered = 0;
68945
+ let failed = 0;
68946
+ let error;
68947
+ const encodedBank = encodeURIComponent(bank);
68948
+ for (const docId of docIds) {
68949
+ const url = `${restBase}/v1/default/banks/${encodedBank}/documents/${encodeURIComponent(docId)}/reprocess`;
68950
+ const r = await postTrigger(url, opts);
68951
+ if (r.ok) {
68952
+ triggered += 1;
68953
+ } else {
68954
+ failed += 1;
68955
+ if (!error)
68956
+ error = r.reason;
68957
+ }
68958
+ }
68959
+ return error ? { triggered, failed, error } : { triggered, failed };
68960
+ }
68961
+ async function refreshMentalModel(restBase, bank, modelId, opts) {
68962
+ const url = `${restBase}/v1/default/banks/${encodeURIComponent(bank)}/mental-models/${encodeURIComponent(modelId)}/refresh`;
68963
+ const r = await postTrigger(url, opts);
68964
+ return r.ok ? { ok: true } : { ok: false, error: r.reason };
68965
+ }
68966
+ async function buildUserProfile(mcpUrl, bank, opts) {
68967
+ try {
68968
+ const r = await ensureUserProfileMentalModel(mcpUrl, bank, {
68969
+ fetchImpl: opts?.fetchImpl,
68970
+ timeoutMs: opts?.timeoutMs
68971
+ });
68972
+ return r.ok ? { ok: true } : { ok: false, error: r.reason };
68973
+ } catch (err) {
68974
+ return { ok: false, error: String(err.message ?? err) };
68975
+ }
68976
+ }
68977
+
68978
+ // src/web/api.ts
68706
68979
  init_audit_reader();
68707
68980
 
68708
68981
  // src/scheduler/dispatch.ts
@@ -68765,6 +69038,8 @@ function readRecentFires(jsonlPath) {
68765
69038
 
68766
69039
  // src/web/api.ts
68767
69040
  init_client3();
69041
+ init_client();
69042
+ import { homedir as homedir23 } from "node:os";
68768
69043
 
68769
69044
  // node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
68770
69045
  import { dirname as dirname8, posix, sep as sep2 } from "path";
@@ -73500,6 +73775,121 @@ async function proposeConfigEditViaHostd(args) {
73500
73775
  }
73501
73776
  }
73502
73777
 
73778
+ // src/web/hostd-read-client.ts
73779
+ init_client4();
73780
+ var READ_TIMEOUT_MS = 4000;
73781
+ function readRequestId(verb) {
73782
+ return `web-${verb}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
73783
+ }
73784
+ async function fetchFleetStatusViaHostd(opts = {}) {
73785
+ const socketPath = opts.socketPath !== undefined ? opts.socketPath : resolveHostdOperatorSocket();
73786
+ if (!socketPath)
73787
+ return null;
73788
+ const send = opts.send ?? hostdRequest;
73789
+ const req = {
73790
+ v: 1,
73791
+ op: "agent_status",
73792
+ request_id: readRequestId("agent_status"),
73793
+ args: {}
73794
+ };
73795
+ try {
73796
+ const resp = await send({ socketPath, timeoutMs: opts.timeoutMs ?? READ_TIMEOUT_MS }, req);
73797
+ if (resp.result !== "completed" || !resp.payload)
73798
+ return null;
73799
+ const parsed = JSON.parse(resp.payload);
73800
+ if (!parsed || typeof parsed !== "object" || !parsed.statuses)
73801
+ return null;
73802
+ return parsed.statuses;
73803
+ } catch {
73804
+ return null;
73805
+ }
73806
+ }
73807
+ async function fetchScheduleViaHostd(opts = {}) {
73808
+ const socketPath = opts.socketPath !== undefined ? opts.socketPath : resolveHostdOperatorSocket();
73809
+ if (!socketPath)
73810
+ return null;
73811
+ const send = opts.send ?? hostdRequest;
73812
+ const req = {
73813
+ v: 1,
73814
+ op: "agent_schedule",
73815
+ request_id: readRequestId("agent_schedule"),
73816
+ args: {}
73817
+ };
73818
+ try {
73819
+ const resp = await send({ socketPath, timeoutMs: opts.timeoutMs ?? READ_TIMEOUT_MS }, req);
73820
+ if (resp.result !== "completed" || !resp.payload)
73821
+ return null;
73822
+ const parsed = JSON.parse(resp.payload);
73823
+ if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.entries)) {
73824
+ return null;
73825
+ }
73826
+ return {
73827
+ entries: parsed.entries,
73828
+ recentByAgent: parsed.recentByAgent ?? {},
73829
+ ...parsed.truncated ? { truncated: true } : {}
73830
+ };
73831
+ } catch {
73832
+ return null;
73833
+ }
73834
+ }
73835
+ async function fetchAgentLogsViaHostd(name, lines, opts = {}) {
73836
+ const socketPath = opts.socketPath !== undefined ? opts.socketPath : resolveHostdOperatorSocket();
73837
+ if (!socketPath) {
73838
+ return { ok: false, error: HOSTD_UNREACHABLE_LOGS_MESSAGE };
73839
+ }
73840
+ const send = opts.send ?? hostdRequest;
73841
+ const tail = Math.max(1, Math.min(lines, 2000));
73842
+ const req = {
73843
+ v: 1,
73844
+ op: "agent_logs",
73845
+ request_id: readRequestId("agent_logs"),
73846
+ args: { name, tail }
73847
+ };
73848
+ try {
73849
+ const resp = await send({ socketPath, timeoutMs: opts.timeoutMs ?? READ_TIMEOUT_MS }, req);
73850
+ if (resp.result === "completed") {
73851
+ return { ok: true, logs: resp.stdout_tail ?? "" };
73852
+ }
73853
+ return {
73854
+ ok: false,
73855
+ error: resp.error ?? (resp.stderr_tail && resp.stderr_tail.trim()) ?? `hostd agent_logs returned '${resp.result}'`
73856
+ };
73857
+ } catch {
73858
+ return { ok: false, error: HOSTD_UNREACHABLE_LOGS_MESSAGE };
73859
+ }
73860
+ }
73861
+ var HOSTD_UNREACHABLE_LOGS_MESSAGE = "Logs need hostd (the dashboard container has no docker access by design). " + "Ensure host_control is enabled, or run `switchroom agent logs <name>` on the host.";
73862
+ var HOSTD_UNREACHABLE_CONTROL_MESSAGE = "Agent control needs hostd (the dashboard container has no docker access by design). " + "Ensure host_control is enabled, or run `switchroom agent restart <name>` on the host.";
73863
+ async function restartAgentViaHostd(name, opts = {}) {
73864
+ const socketPath = opts.socketPath !== undefined ? opts.socketPath : resolveHostdOperatorSocket();
73865
+ if (!socketPath) {
73866
+ return { ok: false, error: HOSTD_UNREACHABLE_CONTROL_MESSAGE };
73867
+ }
73868
+ const send = opts.send ?? hostdRequest;
73869
+ const req = {
73870
+ v: 1,
73871
+ op: "agent_restart",
73872
+ request_id: readRequestId("agent_restart"),
73873
+ args: {
73874
+ name,
73875
+ reason: opts.reason ?? "restart requested from dashboard",
73876
+ force: opts.force ?? true
73877
+ }
73878
+ };
73879
+ try {
73880
+ const resp = await send({ socketPath, timeoutMs: opts.timeoutMs ?? READ_TIMEOUT_MS }, req);
73881
+ if (resp.result === "completed" || resp.result === "started") {
73882
+ return { ok: true };
73883
+ }
73884
+ return {
73885
+ ok: false,
73886
+ error: resp.error ?? `hostd agent_restart returned '${resp.result}'`
73887
+ };
73888
+ } catch {
73889
+ return { ok: false, error: HOSTD_UNREACHABLE_CONTROL_MESSAGE };
73890
+ }
73891
+ }
73892
+
73503
73893
  // src/web/api.ts
73504
73894
  import { randomUUID as randomUUID4 } from "node:crypto";
73505
73895
 
@@ -73731,7 +74121,78 @@ function listSubagents(db, opts = {}) {
73731
74121
  return rows.map(mapSubagentRow);
73732
74122
  }
73733
74123
 
74124
+ // src/web/cache.ts
74125
+ class SwrCache {
74126
+ now;
74127
+ entries = new Map;
74128
+ constructor(now = Date.now) {
74129
+ this.now = now;
74130
+ }
74131
+ async get(key, ttlMs, producer) {
74132
+ const existing = this.entries.get(key);
74133
+ if (!existing) {
74134
+ const value = await producer();
74135
+ const fetchedAt = this.now();
74136
+ this.entries.set(key, { value, fetchedAt, refreshing: false });
74137
+ return { value, dataAsOf: fetchedAt, stale: false };
74138
+ }
74139
+ const age = this.now() - existing.fetchedAt;
74140
+ if (age < ttlMs) {
74141
+ return {
74142
+ value: existing.value,
74143
+ dataAsOf: existing.fetchedAt,
74144
+ stale: false
74145
+ };
74146
+ }
74147
+ if (!existing.refreshing) {
74148
+ existing.refreshing = true;
74149
+ producer().then((value) => {
74150
+ this.entries.set(key, {
74151
+ value,
74152
+ fetchedAt: this.now(),
74153
+ refreshing: false
74154
+ });
74155
+ }, () => {
74156
+ const cur = this.entries.get(key);
74157
+ if (cur)
74158
+ cur.refreshing = false;
74159
+ });
74160
+ }
74161
+ return {
74162
+ value: existing.value,
74163
+ dataAsOf: existing.fetchedAt,
74164
+ stale: true
74165
+ };
74166
+ }
74167
+ clear() {
74168
+ this.entries.clear();
74169
+ }
74170
+ }
74171
+
73734
74172
  // src/web/api.ts
74173
+ var dashboardCache = new SwrCache;
74174
+ var DASHBOARD_CACHE_TTL = {
74175
+ "system-health": 15000,
74176
+ "memory-health": 60000,
74177
+ schedule: 30000,
74178
+ agents: 5000,
74179
+ accounts: 1e4,
74180
+ approvals: 15000,
74181
+ grants: 15000
74182
+ };
74183
+ function readLastTurnAt(agentsDir, name) {
74184
+ try {
74185
+ const db = openTurnsDb(resolve27(agentsDir, name));
74186
+ try {
74187
+ const turns = listTurnsForAgent(db, { limit: 1 });
74188
+ return turns.length > 0 ? turns[0].started_at : null;
74189
+ } finally {
74190
+ db.close();
74191
+ }
74192
+ } catch {
74193
+ return null;
74194
+ }
74195
+ }
73735
74196
  function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now = Date.now()) {
73736
74197
  try {
73737
74198
  const f = resolve27(agentsDir, name, "telegram", ".bridge-alive");
@@ -73740,11 +74201,25 @@ function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now = Date.now()) {
73740
74201
  return false;
73741
74202
  }
73742
74203
  }
73743
- function handleGetAgents(config) {
74204
+ var AGENT_STATUS_CACHE_TTL_MS = 5000;
74205
+ var statusCache = new Map;
74206
+ async function cachedFleetStatus(now, fetchImpl = fetchFleetStatusViaHostd) {
74207
+ const cached = statusCache.get("fleet");
74208
+ if (cached && now - cached.fetchedAt < AGENT_STATUS_CACHE_TTL_MS) {
74209
+ return cached.statuses;
74210
+ }
74211
+ const statuses = await fetchImpl();
74212
+ statusCache.set("fleet", { statuses, fetchedAt: now });
74213
+ return statuses;
74214
+ }
74215
+ async function handleGetAgents(config, deps = {}) {
74216
+ const readLastTurn = deps.readLastTurn ?? readLastTurnAt;
73744
74217
  const statuses = getAllAgentStatuses(config);
73745
74218
  const authStatuses = getAllAuthStatuses(config);
73746
74219
  const agentsDir = resolveAgentsDir(config);
73747
74220
  const agents = [];
74221
+ const needsBackfill = Object.values(statuses).some((s) => s && (s.uptime === null || s.memory === null));
74222
+ const hostdStatuses = needsBackfill ? await cachedFleetStatus(deps.now ?? Date.now(), deps.fetchFleetStatus) : null;
73748
74223
  for (const [name, agentConfig] of Object.entries(config.agents)) {
73749
74224
  const status = statuses[name];
73750
74225
  const auth = authStatuses[name];
@@ -73752,11 +74227,14 @@ function handleGetAgents(config) {
73752
74227
  const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
73753
74228
  const primaryAccount = resolved.auth?.override ?? config.auth?.active;
73754
74229
  const active = status?.active === "active" ? "active" : agentBridgeAlive(agentsDir, name) ? "active" : status?.active ?? "unknown";
74230
+ const hostd = hostdStatuses?.[name];
74231
+ const uptime = status?.uptime ?? hostd?.uptime ?? null;
74232
+ const memory = status?.memory ?? hostd?.memory ?? null;
73755
74233
  agents.push({
73756
74234
  name,
73757
74235
  active,
73758
- uptime: status?.uptime ?? null,
73759
- memory: status?.memory ?? null,
74236
+ uptime,
74237
+ memory,
73760
74238
  extends: agentConfig.extends ?? "default",
73761
74239
  topic_name: agentConfig.topic_name,
73762
74240
  topic_emoji: agentConfig.topic_emoji,
@@ -73767,54 +74245,40 @@ function handleGetAgents(config) {
73767
74245
  timeUntilExpiry: auth?.timeUntilExpiry,
73768
74246
  expiresAt: auth?.expiresAt
73769
74247
  },
73770
- memoryCollection: collection
74248
+ memoryCollection: collection,
74249
+ lastTurnAt: readLastTurn(agentsDir, name)
73771
74250
  });
73772
74251
  }
73773
74252
  return agents;
73774
74253
  }
73775
- function handleStartAgent(name) {
73776
- try {
73777
- startAgent(name);
73778
- captureEvent("agent_started", { agent: name, source: "web_api" });
73779
- return { ok: true };
73780
- } catch (err) {
73781
- captureException(err, { action: "start_agent", agent: name });
73782
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
73783
- }
74254
+ function startStopNeedsOperatorMessage(verb) {
74255
+ return `${verb === "start" ? "Start" : "Stop"} from the dashboard needs operator approval (a follow-up). ` + `For now run on the host: \`switchroom agent ${verb} ${"<name>"}\`.`;
73784
74256
  }
73785
- function handleStopAgent(name) {
73786
- try {
73787
- stopAgent(name);
73788
- captureEvent("agent_stopped", { agent: name, source: "web_api" });
73789
- return { ok: true };
73790
- } catch (err) {
73791
- captureException(err, { action: "stop_agent", agent: name });
73792
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
73793
- }
74257
+ var START_NEEDS_OPERATOR_MESSAGE = startStopNeedsOperatorMessage("start");
74258
+ var STOP_NEEDS_OPERATOR_MESSAGE = startStopNeedsOperatorMessage("stop");
74259
+ async function handleStartAgent(_name) {
74260
+ return { ok: false, error: START_NEEDS_OPERATOR_MESSAGE };
74261
+ }
74262
+ async function handleStopAgent(_name) {
74263
+ return { ok: false, error: STOP_NEEDS_OPERATOR_MESSAGE };
73794
74264
  }
73795
- function handleRestartAgent(name) {
74265
+ async function handleRestartAgent(name, deps = {}) {
74266
+ const restart = deps.restart ?? restartAgentViaHostd;
73796
74267
  try {
73797
- restartAgent(name);
73798
- captureEvent("agent_restarted", { agent: name, source: "web_api" });
73799
- return { ok: true };
74268
+ const result = await restart(name);
74269
+ if (result.ok) {
74270
+ captureEvent("agent_restarted", { agent: name, source: "web_api" });
74271
+ return { ok: true };
74272
+ }
74273
+ return { ok: false, error: result.error };
73800
74274
  } catch (err) {
73801
74275
  captureException(err, { action: "restart_agent", agent: name });
73802
74276
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
73803
74277
  }
73804
74278
  }
73805
- function handleGetLogs(name, lines = 50) {
73806
- const res = spawnSync5("docker", ["logs", "--tail", String(lines), containerName(name)], { encoding: "utf-8", timeout: 5000 });
73807
- if (res.error) {
73808
- return { ok: false, error: res.error.message };
73809
- }
73810
- if (res.status !== 0) {
73811
- const stderr = (res.stderr ?? "").trim();
73812
- return {
73813
- ok: false,
73814
- error: stderr || `docker logs exited ${res.status ?? "non-zero"}`
73815
- };
73816
- }
73817
- return { ok: true, logs: `${res.stdout ?? ""}${res.stderr ?? ""}` };
74279
+ async function handleGetLogs(name, lines = 50, deps = {}) {
74280
+ const fetchLogs = deps.fetchLogs ?? fetchAgentLogsViaHostd;
74281
+ return await fetchLogs(name, lines);
73818
74282
  }
73819
74283
  function handleGetTurns(config, agentName, limit) {
73820
74284
  try {
@@ -73858,6 +74322,31 @@ var quotaCache = new Map;
73858
74322
  function quotaEntryFresh(e, now) {
73859
74323
  return !!e && now - e.fetchedAt < QUOTA_CACHE_TTL_MS;
73860
74324
  }
74325
+ function isoToMs(iso) {
74326
+ if (!iso)
74327
+ return null;
74328
+ const ms = Date.parse(iso);
74329
+ return Number.isNaN(ms) ? null : ms;
74330
+ }
74331
+ function deriveAccountQuotaView(brokerState, probe, now) {
74332
+ const probeFresh = !!probe && now - probe.fetchedAt < QUOTA_CACHE_TTL_MS;
74333
+ let quotaUsage = probeFresh ? probe.usage ?? null : null;
74334
+ let quotaUsageSource = quotaUsage ? "probe" : null;
74335
+ let quotaStale = !probeFresh;
74336
+ if (!quotaUsage && brokerState?.last_quota) {
74337
+ const lq = brokerState.last_quota;
74338
+ quotaUsage = {
74339
+ fiveHourPct: lq.fiveHourUtilizationPct,
74340
+ sevenDayPct: lq.sevenDayUtilizationPct,
74341
+ fiveHourResetAt: isoToMs(lq.fiveHourResetAt),
74342
+ sevenDayResetAt: isoToMs(lq.sevenDayResetAt),
74343
+ capturedAt: lq.capturedAt
74344
+ };
74345
+ quotaUsageSource = "broker-cache";
74346
+ quotaStale = now - lq.capturedAt >= QUOTA_CACHE_TTL_MS;
74347
+ }
74348
+ return { quotaUsage, quotaUsageSource, quotaStale };
74349
+ }
73861
74350
  async function handleGetAccounts(config, home2) {
73862
74351
  const infos = getAccountInfos(Date.now(), home2);
73863
74352
  const brokerAccounts = new Map;
@@ -73884,12 +74373,15 @@ async function handleGetAccounts(config, home2) {
73884
74373
  usedBy.sort();
73885
74374
  }
73886
74375
  const now = Date.now();
73887
- const ce = quotaCache.get(info.label);
74376
+ const brokerState = brokerAccounts.get(info.label) ?? null;
74377
+ const view = deriveAccountQuotaView(brokerState, quotaCache.get(info.label), now);
73888
74378
  return {
73889
74379
  ...info,
73890
- quota: brokerAccounts.get(info.label) ?? null,
73891
- quotaUsage: quotaEntryFresh(ce, now) ? ce.usage ?? null : null,
73892
- quotaStale: !quotaEntryFresh(ce, now),
74380
+ health: brokerState?.exhausted ? "quota-exhausted" : info.health,
74381
+ quota: brokerState,
74382
+ quotaUsage: view.quotaUsage,
74383
+ quotaUsageSource: view.quotaUsageSource,
74384
+ quotaStale: view.quotaStale,
73893
74385
  usedBy
73894
74386
  };
73895
74387
  });
@@ -74012,6 +74504,12 @@ function inspectEnv(container, keys) {
74012
74504
  } catch {}
74013
74505
  return out;
74014
74506
  }
74507
+ var HOSTD_NOISE_OPS = new Set([
74508
+ "agent_smoke",
74509
+ "get_status",
74510
+ "agent_status",
74511
+ "agent_logs"
74512
+ ]);
74015
74513
  async function handleGetSystemHealth(config, home2) {
74016
74514
  const broker = { reachable: false };
74017
74515
  try {
@@ -74063,7 +74561,7 @@ async function handleGetSystemHealth(config, home2) {
74063
74561
  if (existsSync47(logPath)) {
74064
74562
  hostd.auditLogPresent = true;
74065
74563
  const raw = readFileSync43(logPath, "utf-8");
74066
- hostd.recent = readAndFilter(raw, {}, 10);
74564
+ hostd.recent = readAndFilter(raw, {}, 1000).filter((e) => !HOSTD_NOISE_OPS.has(e.op)).slice(-10);
74067
74565
  }
74068
74566
  } catch (err) {
74069
74567
  hostd.error = err instanceof Error ? err.message : String(err);
@@ -74084,8 +74582,10 @@ async function handleGetGoogleAccounts(config) {
74084
74582
  }
74085
74583
  });
74086
74584
  } catch (err) {
74087
- if (!(err instanceof AuthBrokerUnreachableError))
74088
- throw err;
74585
+ if (!(err instanceof AuthBrokerUnreachableError)) {
74586
+ process.stderr.write(`dashboard: live broker error reading workspace accounts ` + `(${err instanceof AuthBrokerError ? err.code : "unknown"}); ` + `showing config-only ACL: ${err instanceof Error ? err.message : String(err)}
74587
+ `);
74588
+ }
74089
74589
  }
74090
74590
  const cfgAccounts = config.google_accounts ?? {};
74091
74591
  const keys = new Set([
@@ -74122,8 +74622,10 @@ async function handleGetMicrosoftAccounts(config) {
74122
74622
  }
74123
74623
  });
74124
74624
  } catch (err) {
74125
- if (!(err instanceof AuthBrokerUnreachableError))
74126
- throw err;
74625
+ if (!(err instanceof AuthBrokerUnreachableError)) {
74626
+ process.stderr.write(`dashboard: live broker error reading workspace accounts ` + `(${err instanceof AuthBrokerError ? err.code : "unknown"}); ` + `showing config-only ACL: ${err instanceof Error ? err.message : String(err)}
74627
+ `);
74628
+ }
74127
74629
  }
74128
74630
  const cfgAccounts = config.microsoft_accounts ?? {};
74129
74631
  const keys = new Set([
@@ -74328,7 +74830,16 @@ function handleSetConnectionAccess(configPath, config, args, deps = {}) {
74328
74830
  });
74329
74831
  return { ok: true, changed: true, requestId, pendingApproval: true };
74330
74832
  }
74331
- function handleGetSchedule(config) {
74833
+ async function handleGetSchedule(config, deps = {}) {
74834
+ const fetchSchedule = deps.fetchSchedule ?? fetchScheduleViaHostd;
74835
+ const viaHostd = await fetchSchedule();
74836
+ if (viaHostd) {
74837
+ return {
74838
+ entries: viaHostd.entries,
74839
+ recentByAgent: viaHostd.recentByAgent,
74840
+ ...viaHostd.truncated ? { truncated: true } : {}
74841
+ };
74842
+ }
74332
74843
  const entries = collectScheduleEntries(config);
74333
74844
  const agentsDir = resolveAgentsDir(config);
74334
74845
  const recentByAgent = {};
@@ -74338,7 +74849,12 @@ function handleGetSchedule(config) {
74338
74849
  if (rows.length > 0)
74339
74850
  recentByAgent[agent] = rows.slice(-10);
74340
74851
  }
74341
- return { entries, recentByAgent };
74852
+ return {
74853
+ entries,
74854
+ recentByAgent,
74855
+ degraded: true,
74856
+ reason: "schedule view needs hostd \u2014 showing base-config only " + "(agent-authored schedule.d crons are not included). " + "Ensure host_control is enabled."
74857
+ };
74342
74858
  }
74343
74859
  async function handleGetApprovals() {
74344
74860
  const opSock = resolveKernelOperatorSocket();
@@ -74360,6 +74876,48 @@ async function handleGetApprovals() {
74360
74876
  const sorted = [...decisions].sort((a, b) => b.granted_at - a.granted_at);
74361
74877
  return { reachable: true, decisions: sorted };
74362
74878
  }
74879
+ function brokerOperatorSocketPath(home2 = process.env.HOME || homedir23()) {
74880
+ return join44(home2, ".switchroom", "broker-operator", "sock");
74881
+ }
74882
+ function resolveBrokerOperatorSocket(home2 = process.env.HOME || homedir23()) {
74883
+ const p = brokerOperatorSocketPath(home2);
74884
+ return existsSync47(p) ? p : null;
74885
+ }
74886
+ async function handleGetGrants(deps = {}) {
74887
+ const socket = "socketPath" in deps ? deps.socketPath : resolveBrokerOperatorSocket();
74888
+ if (socket == null) {
74889
+ return {
74890
+ reachable: false,
74891
+ grants: [],
74892
+ error: "vault-broker operator socket not present \u2014 host-side grant " + "listing needs a broker-operator bind (re-apply to (re)create it)."
74893
+ };
74894
+ }
74895
+ const list = deps.list ?? listGrantsViaBroker;
74896
+ let result;
74897
+ try {
74898
+ result = await list(undefined, { socket });
74899
+ } catch (err) {
74900
+ return {
74901
+ reachable: false,
74902
+ grants: [],
74903
+ error: err instanceof Error ? err.message : String(err)
74904
+ };
74905
+ }
74906
+ if (result.kind !== "ok") {
74907
+ return { reachable: false, grants: [], error: result.msg };
74908
+ }
74909
+ const rows = result.grants.map((g) => ({
74910
+ id: g.id,
74911
+ agent: g.agent_slug,
74912
+ keys: g.key_allow,
74913
+ writeKeys: g.write_allow,
74914
+ expiresAt: g.expires_at == null ? null : g.expires_at * 1000,
74915
+ createdAt: g.created_at * 1000,
74916
+ description: g.description
74917
+ }));
74918
+ rows.sort((a, b) => b.createdAt - a.createdAt);
74919
+ return { reachable: true, grants: rows };
74920
+ }
74363
74921
  async function handleGetMemoryHealth(config, opts) {
74364
74922
  const url = config.memory?.config?.url ?? "http://127.0.0.1:18888/mcp/";
74365
74923
  const now = opts?.now ?? new Date;
@@ -74381,6 +74939,8 @@ async function handleGetMemoryHealth(config, opts) {
74381
74939
  const gaps = recentUnextracted(h.unextractedDocuments, 30, now);
74382
74940
  const stale = staleMentalModels(h.mentalModels, 7, now);
74383
74941
  const corrupted = corruptedMentalModels(h.mentalModels);
74942
+ const newestAge = ageDays(h.newestDocumentAt, now);
74943
+ const pendingStuck = h.pendingOperations > 0 && newestAge !== null && newestAge > 1;
74384
74944
  let status = "ok";
74385
74945
  let statusDetail = "facts flowing";
74386
74946
  if (!h.ok) {
@@ -74392,6 +74952,9 @@ async function handleGetMemoryHealth(config, opts) {
74392
74952
  } else if (gaps.length > 0) {
74393
74953
  status = "fail";
74394
74954
  statusDetail = `${gaps.length} recent conversation(s) stored with zero extracted facts \u2014 invisible to recall`;
74955
+ } else if (pendingStuck) {
74956
+ status = "warn";
74957
+ statusDetail = `${h.pendingOperations} memory operation(s) pending with no new documents for ${Math.round(newestAge)}d \u2014 pipeline may be stuck`;
74395
74958
  } else if (stale.length > 0) {
74396
74959
  status = "warn";
74397
74960
  statusDetail = `${stale.length} mental model(s) not refreshed in >7d`;
@@ -74410,6 +74973,7 @@ async function handleGetMemoryHealth(config, opts) {
74410
74973
  newestDocumentAt: h.newestDocumentAt,
74411
74974
  recentUnextractedCount: gaps.length,
74412
74975
  oldestUnextractedAt: gaps[0]?.createdAt ?? null,
74976
+ unextractedDocIds: gaps.slice(0, 20).map((d) => d.id),
74413
74977
  mentalModels: h.mentalModels,
74414
74978
  staleMentalModelCount: stale.length,
74415
74979
  corruptedMentalModelNames: corrupted.map((m) => m.name),
@@ -74420,11 +74984,236 @@ async function handleGetMemoryHealth(config, opts) {
74420
74984
  rows.sort((a, b) => a.bank.localeCompare(b.bank));
74421
74985
  return { reachable, url, banks: rows };
74422
74986
  }
74987
+ function resolveMemoryUrl(config) {
74988
+ return config.memory?.config?.url ?? "http://127.0.0.1:18888/mcp/";
74989
+ }
74990
+ function isKnownBank(config, bank) {
74991
+ for (const agentName of Object.keys(config.agents)) {
74992
+ if (getCollectionForAgent(agentName, config) === bank)
74993
+ return true;
74994
+ }
74995
+ return false;
74996
+ }
74997
+ async function handleMemoryReprocess(config, body, deps) {
74998
+ const bank = typeof body.bank === "string" ? body.bank : "";
74999
+ if (!bank)
75000
+ return { ok: false, error: "Body must include `bank` (string)." };
75001
+ if (!isKnownBank(config, bank))
75002
+ return { ok: false, error: `Unknown bank: ${bank}` };
75003
+ const url = resolveMemoryUrl(config);
75004
+ const inspect = deps?.inspect ?? inspectBankHealth;
75005
+ const trigger = deps?.trigger ?? reprocessDocuments;
75006
+ const now = deps?.now ?? new Date;
75007
+ try {
75008
+ const h = await inspect(url, bank, { fetchImpl: deps?.fetchImpl });
75009
+ if (!h.ok)
75010
+ return { ok: false, error: `inspection failed: ${h.reason ?? "unknown"}` };
75011
+ const gaps = recentUnextracted(h.unextractedDocuments, 30, now);
75012
+ const docIds = gaps.slice(0, 20).map((d) => d.id);
75013
+ if (docIds.length === 0)
75014
+ return { ok: true, triggered: 0, failed: 0 };
75015
+ const result = await trigger(restBaseFromMcpUrl(url), bank, docIds, {
75016
+ fetchImpl: deps?.fetchImpl
75017
+ });
75018
+ return {
75019
+ ok: result.failed === 0,
75020
+ triggered: result.triggered,
75021
+ failed: result.failed,
75022
+ ...result.error ? { error: result.error } : {}
75023
+ };
75024
+ } catch (err) {
75025
+ return { ok: false, error: String(err.message ?? err) };
75026
+ }
75027
+ }
75028
+ async function handleMemoryRefreshModel(config, body, deps) {
75029
+ const bank = typeof body.bank === "string" ? body.bank : "";
75030
+ const modelId = typeof body.modelId === "string" ? body.modelId : "";
75031
+ if (!bank)
75032
+ return { ok: false, error: "Body must include `bank` (string)." };
75033
+ if (!modelId)
75034
+ return { ok: false, error: "Body must include `modelId` (string)." };
75035
+ if (!isKnownBank(config, bank))
75036
+ return { ok: false, error: `Unknown bank: ${bank}` };
75037
+ const url = resolveMemoryUrl(config);
75038
+ const inspect = deps?.inspect ?? inspectBankHealth;
75039
+ const trigger = deps?.trigger ?? refreshMentalModel;
75040
+ try {
75041
+ const h = await inspect(url, bank, { fetchImpl: deps?.fetchImpl });
75042
+ if (!h.ok)
75043
+ return { ok: false, error: `inspection failed: ${h.reason ?? "unknown"}` };
75044
+ if (!h.mentalModels.some((m) => m.id === modelId)) {
75045
+ return { ok: false, error: `Unknown mental model for bank ${bank}` };
75046
+ }
75047
+ return await trigger(restBaseFromMcpUrl(url), bank, modelId, {
75048
+ fetchImpl: deps?.fetchImpl
75049
+ });
75050
+ } catch (err) {
75051
+ return { ok: false, error: String(err.message ?? err) };
75052
+ }
75053
+ }
75054
+ async function handleMemoryBuildProfile(config, body, deps) {
75055
+ const bank = typeof body.bank === "string" ? body.bank : "";
75056
+ if (!bank)
75057
+ return { ok: false, error: "Body must include `bank` (string)." };
75058
+ if (!isKnownBank(config, bank))
75059
+ return { ok: false, error: `Unknown bank: ${bank}` };
75060
+ const url = resolveMemoryUrl(config);
75061
+ const trigger = deps?.trigger ?? buildUserProfile;
75062
+ try {
75063
+ return await trigger(url, bank, { fetchImpl: deps?.fetchImpl });
75064
+ } catch (err) {
75065
+ return { ok: false, error: String(err.message ?? err) };
75066
+ }
75067
+ }
75068
+ var ATTENTION_SEVERITY_RANK = {
75069
+ critical: 0,
75070
+ warn: 1,
75071
+ info: 2
75072
+ };
75073
+ var ATTENTION_MAX_ITEMS = 12;
75074
+ function deriveAttention(parts, now) {
75075
+ const items = [];
75076
+ const mem = parts.memoryHealth;
75077
+ if (mem) {
75078
+ if (!mem.reachable) {
75079
+ items.push({
75080
+ severity: "critical",
75081
+ title: "Hindsight unreachable",
75082
+ detail: "Memory recall and extraction are down \u2014 agents are amnesiac.",
75083
+ tab: "memory"
75084
+ });
75085
+ }
75086
+ for (const bank of mem.banks ?? []) {
75087
+ for (const name of bank.corruptedMentalModelNames ?? []) {
75088
+ items.push({
75089
+ severity: "critical",
75090
+ title: `Mental model corrupted: ${name}`,
75091
+ detail: `Bank ${bank.bank} \u2014 an LLM-failure message is injected into every turn until refreshed.`,
75092
+ tab: "memory"
75093
+ });
75094
+ }
75095
+ if (bank.recentUnextractedCount > 0) {
75096
+ items.push({
75097
+ severity: "critical",
75098
+ title: `${bank.recentUnextractedCount} unextracted conversation(s)`,
75099
+ detail: `Bank ${bank.bank} \u2014 stored with zero extracted facts, invisible to recall.`,
75100
+ tab: "memory"
75101
+ });
75102
+ }
75103
+ if (bank.staleMentalModelCount > 0) {
75104
+ items.push({
75105
+ severity: "warn",
75106
+ title: `${bank.staleMentalModelCount} stale mental model(s)`,
75107
+ detail: `Bank ${bank.bank} \u2014 not refreshed in >7d.`,
75108
+ tab: "memory"
75109
+ });
75110
+ }
75111
+ }
75112
+ }
75113
+ for (const acct of parts.accounts ?? []) {
75114
+ if (acct?.quota?.exhausted) {
75115
+ items.push({
75116
+ severity: "warn",
75117
+ title: `Account ${acct.label} quota exhausted`,
75118
+ detail: acct.quota.exhausted_until ? `Resets around ${new Date(acct.quota.exhausted_until).toISOString()}.` : undefined,
75119
+ tab: "accounts"
75120
+ });
75121
+ }
75122
+ }
75123
+ for (const [agent, fires] of Object.entries(parts.schedule?.recentByAgent ?? {})) {
75124
+ if ((fires ?? []).some((f) => f && f.exitCode !== 0)) {
75125
+ items.push({
75126
+ severity: "warn",
75127
+ title: `Cron ${agent} last fire failed`,
75128
+ detail: "A recent scheduled fire returned a non-zero exit code.",
75129
+ tab: "schedule"
75130
+ });
75131
+ }
75132
+ }
75133
+ const sys = parts.systemHealth;
75134
+ if (sys) {
75135
+ if (sys.broker?.reachable === false) {
75136
+ items.push({
75137
+ severity: "critical",
75138
+ title: "Auth-broker unreachable",
75139
+ detail: sys.broker.error,
75140
+ tab: "system"
75141
+ });
75142
+ }
75143
+ if (sys.hindsight?.running === false) {
75144
+ items.push({
75145
+ severity: "critical",
75146
+ title: "Hindsight not running",
75147
+ detail: "The memory backend container isn't serving.",
75148
+ tab: "system"
75149
+ });
75150
+ }
75151
+ if (sys.hostd?.error) {
75152
+ items.push({
75153
+ severity: "critical",
75154
+ title: "Hostd error",
75155
+ detail: sys.hostd.error,
75156
+ tab: "system"
75157
+ });
75158
+ }
75159
+ }
75160
+ const DAY_MS = 24 * 60 * 60 * 1000;
75161
+ for (const agent of parts.agents ?? []) {
75162
+ const exp = agent?.auth?.expiresAt;
75163
+ if (typeof exp === "number" && exp - now > 0 && exp - now < DAY_MS) {
75164
+ items.push({
75165
+ severity: "warn",
75166
+ title: `${agent.name} token expires soon`,
75167
+ detail: "OAuth token expires within 24h \u2014 re-auth before it lapses.",
75168
+ tab: "agents"
75169
+ });
75170
+ }
75171
+ }
75172
+ items.sort((a, b) => ATTENTION_SEVERITY_RANK[a.severity] - ATTENTION_SEVERITY_RANK[b.severity]);
75173
+ return items.slice(0, ATTENTION_MAX_ITEMS);
75174
+ }
75175
+ async function handleGetSummary(deps, now = Date.now()) {
75176
+ const safe = async (p) => {
75177
+ try {
75178
+ const r = await p();
75179
+ return { value: r.value, dataAsOf: r.dataAsOf };
75180
+ } catch {
75181
+ return { value: null, dataAsOf: null };
75182
+ }
75183
+ };
75184
+ const [agents, systemHealth, approvals, schedule, accounts, memoryHealth] = await Promise.all([
75185
+ safe(deps.agents),
75186
+ safe(deps.systemHealth),
75187
+ safe(deps.approvals),
75188
+ safe(deps.schedule),
75189
+ safe(deps.accounts),
75190
+ safe(deps.memoryHealth)
75191
+ ]);
75192
+ const stamps = [agents, systemHealth, approvals, schedule, accounts, memoryHealth].map((p) => p.dataAsOf).filter((d) => typeof d === "number");
75193
+ const dataAsOf = stamps.length > 0 ? Math.min(...stamps) : 0;
75194
+ const attention = deriveAttention({
75195
+ memoryHealth: memoryHealth.value,
75196
+ accounts: accounts.value,
75197
+ schedule: schedule.value,
75198
+ systemHealth: systemHealth.value,
75199
+ agents: agents.value
75200
+ }, now);
75201
+ return {
75202
+ agents: agents.value,
75203
+ systemHealth: systemHealth.value,
75204
+ approvals: approvals.value,
75205
+ schedule: schedule.value,
75206
+ accounts: accounts.value,
75207
+ memoryHealth: memoryHealth.value,
75208
+ attention,
75209
+ dataAsOf
75210
+ };
75211
+ }
74423
75212
 
74424
75213
  // src/web/webhook-handler.ts
74425
75214
  import { appendFileSync as appendFileSync4, existsSync as existsSync49, mkdirSync as mkdirSync28, readFileSync as readFileSync45, writeFileSync as writeFileSync26 } from "fs";
74426
- import { join as join45 } from "path";
74427
- import { homedir as homedir24 } from "os";
75215
+ import { join as join46 } from "path";
75216
+ import { homedir as homedir25 } from "os";
74428
75217
 
74429
75218
  // src/web/webhook-verify.ts
74430
75219
  import { createHmac as createHmac2, timingSafeEqual } from "crypto";
@@ -74630,12 +75419,12 @@ function forwardToGateway(socketPath, req, opts = {}) {
74630
75419
 
74631
75420
  // src/web/webhook-edge.ts
74632
75421
  import { existsSync as existsSync48, readFileSync as readFileSync44 } from "fs";
74633
- import { join as join44 } from "path";
74634
- import { homedir as homedir23 } from "os";
75422
+ import { join as join45 } from "path";
75423
+ import { homedir as homedir24 } from "os";
74635
75424
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
74636
75425
  var EDGE_HEADER = "x-switchroom-edge";
74637
75426
  function edgeSecretPath() {
74638
- return join44(homedir23(), ".switchroom", "webhook-edge-secret");
75427
+ return join45(homedir24(), ".switchroom", "webhook-edge-secret");
74639
75428
  }
74640
75429
  function loadEdgeSecret(path4) {
74641
75430
  const p = path4 ?? edgeSecretPath();
@@ -74698,8 +75487,8 @@ var agentDedupCache = new Map;
74698
75487
  function createFileDedupStore(resolveAgentDir) {
74699
75488
  return {
74700
75489
  check(agent, deliveryId, now) {
74701
- const telegramDir = join45(resolveAgentDir(agent), "telegram");
74702
- const filePath = join45(telegramDir, "webhook-dedup.json");
75490
+ const telegramDir = join46(resolveAgentDir(agent), "telegram");
75491
+ const filePath = join46(telegramDir, "webhook-dedup.json");
74703
75492
  if (!agentDedupCache.has(agent)) {
74704
75493
  agentDedupCache.set(agent, loadDedupFile(filePath));
74705
75494
  }
@@ -74751,7 +75540,7 @@ function shouldWriteThrottleIssue(agent, source, now, windowMap) {
74751
75540
  return true;
74752
75541
  }
74753
75542
  function writeThrottleIssue(agent, source, now, telegramDir, log) {
74754
- const issuesPath = join45(telegramDir, "issues.jsonl");
75543
+ const issuesPath = join46(telegramDir, "issues.jsonl");
74755
75544
  try {
74756
75545
  mkdirSync28(telegramDir, { recursive: true });
74757
75546
  const record = {
@@ -74776,7 +75565,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
74776
75565
  async function handleWebhookIngest(args, deps = {}) {
74777
75566
  const log = deps.log ?? ((s) => process.stderr.write(s));
74778
75567
  const now = (deps.now ?? Date.now)();
74779
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join45(homedir24(), ".switchroom", "agents", a));
75568
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join46(homedir25(), ".switchroom", "agents", a));
74780
75569
  const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
74781
75570
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
74782
75571
  if (!args.agentExists) {
@@ -74843,7 +75632,7 @@ async function handleWebhookIngest(args, deps = {}) {
74843
75632
  if (retryAfter !== null) {
74844
75633
  if (!args.viaGateway) {
74845
75634
  const agentDir2 = resolveAgentDir(args.agent);
74846
- const telegramDir2 = join45(agentDir2, "telegram");
75635
+ const telegramDir2 = join46(agentDir2, "telegram");
74847
75636
  if (shouldWriteThrottleIssue(args.agent, source, now)) {
74848
75637
  writeThrottleIssue(args.agent, source, now, telegramDir2, log);
74849
75638
  }
@@ -74863,7 +75652,7 @@ async function handleWebhookIngest(args, deps = {}) {
74863
75652
  const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : source === "linear" ? String(payload.type ?? "unknown").toLowerCase() : args.source;
74864
75653
  const rendered = source === "github" ? renderGithubEvent(eventType, payload) : source === "linear" ? renderLinearEvent(eventType, payload) : renderGenericEvent(args.source, payload);
74865
75654
  if (args.viaGateway) {
74866
- const socketPath = join45(resolveAgentDir(args.agent), "telegram", "webhook.sock");
75655
+ const socketPath = join46(resolveAgentDir(args.agent), "telegram", "webhook.sock");
74867
75656
  const forward = deps.forwardFn ?? forwardToGateway;
74868
75657
  const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
74869
75658
  let resp;
@@ -74902,8 +75691,8 @@ async function handleWebhookIngest(args, deps = {}) {
74902
75691
  return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
74903
75692
  }
74904
75693
  const agentDir = resolveAgentDir(args.agent);
74905
- const telegramDir = join45(agentDir, "telegram");
74906
- const logPath = join45(telegramDir, "webhook-events.jsonl");
75694
+ const telegramDir = join46(agentDir, "telegram");
75695
+ const logPath = join46(telegramDir, "webhook-events.jsonl");
74907
75696
  try {
74908
75697
  mkdirSync28(telegramDir, { recursive: true });
74909
75698
  const record = {
@@ -74926,6 +75715,8 @@ async function handleWebhookIngest(args, deps = {}) {
74926
75715
  }
74927
75716
 
74928
75717
  // src/web/server.ts
75718
+ var LOG_POLL_INTERVAL_MS = 3000;
75719
+ var LOG_POLL_TAIL_LINES = 400;
74929
75720
  var MIME_TYPES = {
74930
75721
  ".html": "text/html",
74931
75722
  ".css": "text/css",
@@ -74956,8 +75747,8 @@ function resolveWebToken() {
74956
75747
  const fromEnv = process.env.SWITCHROOM_WEB_TOKEN;
74957
75748
  if (fromEnv && fromEnv.length > 0)
74958
75749
  return fromEnv;
74959
- const home2 = process.env.HOME ?? homedir25();
74960
- const tokenPath = join46(home2, ".switchroom", "web-token");
75750
+ const home2 = process.env.HOME ?? homedir26();
75751
+ const tokenPath = join47(home2, ".switchroom", "web-token");
74961
75752
  if (existsSync50(tokenPath)) {
74962
75753
  const existing = readFileSync46(tokenPath, "utf8").trim();
74963
75754
  if (existing.length > 0)
@@ -75042,7 +75833,7 @@ function checkWsAuth(req, token, server) {
75042
75833
  return presented !== null && constantTimeEqual(presented, token);
75043
75834
  }
75044
75835
  function loadWebhookSecrets() {
75045
- const path4 = join46(homedir25(), ".switchroom", "webhook-secrets.json");
75836
+ const path4 = join47(homedir26(), ".switchroom", "webhook-secrets.json");
75046
75837
  if (!existsSync50(path4))
75047
75838
  return {};
75048
75839
  try {
@@ -75118,6 +75909,9 @@ function parseRoute(pathname, method) {
75118
75909
  if (method === "GET" && pathname === "/api/agents") {
75119
75910
  return { handler: "getAgents", params: {} };
75120
75911
  }
75912
+ if (method === "GET" && pathname === "/api/summary") {
75913
+ return { handler: "getSummary", params: {} };
75914
+ }
75121
75915
  const logsMatch = pathname.match(/^\/api\/agents\/([^/]+)\/logs$/);
75122
75916
  if (method === "GET" && logsMatch) {
75123
75917
  return { handler: "getLogs", params: { name: logsMatch[1] } };
@@ -75148,6 +75942,15 @@ function parseRoute(pathname, method) {
75148
75942
  if (method === "GET" && pathname === "/api/memory-health") {
75149
75943
  return { handler: "getMemoryHealth", params: {} };
75150
75944
  }
75945
+ if (method === "POST" && pathname === "/api/memory/reprocess") {
75946
+ return { handler: "memoryReprocess", params: {} };
75947
+ }
75948
+ if (method === "POST" && pathname === "/api/memory/refresh-model") {
75949
+ return { handler: "memoryRefreshModel", params: {} };
75950
+ }
75951
+ if (method === "POST" && pathname === "/api/memory/build-profile") {
75952
+ return { handler: "memoryBuildProfile", params: {} };
75953
+ }
75151
75954
  if (method === "GET" && pathname === "/api/google-accounts") {
75152
75955
  return { handler: "getGoogleAccounts", params: {} };
75153
75956
  }
@@ -75163,6 +75966,9 @@ function parseRoute(pathname, method) {
75163
75966
  if (method === "GET" && pathname === "/api/approvals") {
75164
75967
  return { handler: "getApprovals", params: {} };
75165
75968
  }
75969
+ if (method === "GET" && pathname === "/api/grants") {
75970
+ return { handler: "getGrants", params: {} };
75971
+ }
75166
75972
  if (method === "GET" && pathname === "/api/accounts") {
75167
75973
  return { handler: "getAccounts", params: {} };
75168
75974
  }
@@ -75215,6 +76021,14 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75215
76021
  return config;
75216
76022
  }
75217
76023
  };
76024
+ const cachedAgents = () => dashboardCache.get("agents", DASHBOARD_CACHE_TTL.agents, () => handleGetAgents(freshConfig()));
76025
+ const cachedSystemHealth = () => dashboardCache.get("system-health", DASHBOARD_CACHE_TTL["system-health"], () => handleGetSystemHealth(freshConfig()));
76026
+ const cachedMemoryHealth = () => dashboardCache.get("memory-health", DASHBOARD_CACHE_TTL["memory-health"], () => handleGetMemoryHealth(freshConfig()));
76027
+ const cachedSchedule = () => dashboardCache.get("schedule", DASHBOARD_CACHE_TTL.schedule, () => handleGetSchedule(freshConfig()));
76028
+ const cachedApprovals = () => dashboardCache.get("approvals", DASHBOARD_CACHE_TTL.approvals, () => handleGetApprovals());
76029
+ const cachedGrants = () => dashboardCache.get("grants", DASHBOARD_CACHE_TTL.grants, () => handleGetGrants());
76030
+ const cachedAccounts = () => dashboardCache.get("accounts", DASHBOARD_CACHE_TTL.accounts, () => handleGetAccounts(freshConfig()));
76031
+ const withStamp = (part) => ({ ...part.value, dataAsOf: part.dataAsOf, stale: part.stale });
75218
76032
  const localhostOnly = hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
75219
76033
  const server = Bun.serve({
75220
76034
  port,
@@ -75251,7 +76065,16 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75251
76065
  return authError;
75252
76066
  switch (route.handler) {
75253
76067
  case "getAgents":
75254
- return jsonResponse(handleGetAgents(config));
76068
+ return (async () => jsonResponse((await cachedAgents()).value))();
76069
+ case "getSummary":
76070
+ return (async () => jsonResponse(await handleGetSummary({
76071
+ agents: cachedAgents,
76072
+ systemHealth: cachedSystemHealth,
76073
+ approvals: cachedApprovals,
76074
+ schedule: cachedSchedule,
76075
+ accounts: cachedAccounts,
76076
+ memoryHealth: cachedMemoryHealth
76077
+ })))();
75255
76078
  case "getLogs": {
75256
76079
  const agentName = route.params.name;
75257
76080
  if (!config.agents[agentName]) {
@@ -75259,28 +76082,28 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75259
76082
  }
75260
76083
  const rawLines = Number(url.searchParams.get("lines") ?? "50");
75261
76084
  const lines = Number.isInteger(rawLines) && rawLines >= 1 && rawLines <= 1e4 ? rawLines : 50;
75262
- return jsonResponse(handleGetLogs(agentName, lines));
76085
+ return (async () => jsonResponse(await handleGetLogs(agentName, lines)))();
75263
76086
  }
75264
76087
  case "startAgent": {
75265
76088
  const agentName = route.params.name;
75266
76089
  if (!config.agents[agentName]) {
75267
76090
  return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
75268
76091
  }
75269
- return jsonResponse(handleStartAgent(agentName));
76092
+ return (async () => jsonResponse(await handleStartAgent(agentName)))();
75270
76093
  }
75271
76094
  case "stopAgent": {
75272
76095
  const agentName = route.params.name;
75273
76096
  if (!config.agents[agentName]) {
75274
76097
  return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
75275
76098
  }
75276
- return jsonResponse(handleStopAgent(agentName));
76099
+ return (async () => jsonResponse(await handleStopAgent(agentName)))();
75277
76100
  }
75278
76101
  case "restartAgent": {
75279
76102
  const agentName = route.params.name;
75280
76103
  if (!config.agents[agentName]) {
75281
76104
  return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
75282
76105
  }
75283
- return jsonResponse(handleRestartAgent(agentName));
76106
+ return (async () => jsonResponse(await handleRestartAgent(agentName)))();
75284
76107
  }
75285
76108
  case "getTurns": {
75286
76109
  const agentName = route.params.name;
@@ -75308,9 +76131,9 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75308
76131
  return jsonResponse(result.subagents);
75309
76132
  }
75310
76133
  case "getSystemHealth":
75311
- return (async () => jsonResponse(await handleGetSystemHealth(config)))();
76134
+ return (async () => jsonResponse(withStamp(await cachedSystemHealth())))();
75312
76135
  case "getMemoryHealth":
75313
- return (async () => jsonResponse(await handleGetMemoryHealth(freshConfig())))();
76136
+ return (async () => jsonResponse(withStamp(await cachedMemoryHealth())))();
75314
76137
  case "getGoogleAccounts":
75315
76138
  return (async () => jsonResponse(await handleGetGoogleAccounts(freshConfig())))();
75316
76139
  case "getMicrosoftAccounts":
@@ -75318,11 +76141,13 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75318
76141
  case "getNotionWorkspace":
75319
76142
  return jsonResponse(handleGetNotionWorkspace(freshConfig()));
75320
76143
  case "getSchedule":
75321
- return jsonResponse(handleGetSchedule(config));
76144
+ return (async () => jsonResponse(withStamp(await cachedSchedule())))();
75322
76145
  case "getApprovals":
75323
- return (async () => jsonResponse(await handleGetApprovals()))();
76146
+ return (async () => jsonResponse(withStamp(await cachedApprovals())))();
76147
+ case "getGrants":
76148
+ return (async () => jsonResponse(withStamp(await cachedGrants())))();
75324
76149
  case "getAccounts":
75325
- return (async () => jsonResponse(await handleGetAccounts(config)))();
76150
+ return (async () => jsonResponse((await cachedAccounts()).value))();
75326
76151
  case "useAccount": {
75327
76152
  return (async () => {
75328
76153
  let body;
@@ -75386,6 +76211,45 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75386
76211
  return jsonResponse(result, result.ok ? 200 : 502);
75387
76212
  })();
75388
76213
  }
76214
+ case "memoryReprocess": {
76215
+ return (async () => {
76216
+ let body = {};
76217
+ try {
76218
+ const raw = await req.text();
76219
+ body = raw ? JSON.parse(raw) : {};
76220
+ } catch {
76221
+ return jsonResponse({ ok: false, error: "Invalid JSON body" }, 400);
76222
+ }
76223
+ const result = await handleMemoryReprocess(freshConfig(), body);
76224
+ return jsonResponse(result, result.ok ? 200 : 400);
76225
+ })();
76226
+ }
76227
+ case "memoryRefreshModel": {
76228
+ return (async () => {
76229
+ let body = {};
76230
+ try {
76231
+ const raw = await req.text();
76232
+ body = raw ? JSON.parse(raw) : {};
76233
+ } catch {
76234
+ return jsonResponse({ ok: false, error: "Invalid JSON body" }, 400);
76235
+ }
76236
+ const result = await handleMemoryRefreshModel(freshConfig(), body);
76237
+ return jsonResponse(result, result.ok ? 200 : 400);
76238
+ })();
76239
+ }
76240
+ case "memoryBuildProfile": {
76241
+ return (async () => {
76242
+ let body = {};
76243
+ try {
76244
+ const raw = await req.text();
76245
+ body = raw ? JSON.parse(raw) : {};
76246
+ } catch {
76247
+ return jsonResponse({ ok: false, error: "Invalid JSON body" }, 400);
76248
+ }
76249
+ const result = await handleMemoryBuildProfile(freshConfig(), body);
76250
+ return jsonResponse(result, result.ok ? 200 : 400);
76251
+ })();
76252
+ }
75389
76253
  case "getAgentAccounts": {
75390
76254
  const agentName = route.params.name;
75391
76255
  if (!config.agents[agentName]) {
@@ -75403,7 +76267,7 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75403
76267
  }
75404
76268
  }
75405
76269
  let filePath = pathname === "/" ? "/index.html" : pathname;
75406
- const fullPath = join46(uiDir, filePath);
76270
+ const fullPath = join47(uiDir, filePath);
75407
76271
  if (!existsSync50(fullPath)) {
75408
76272
  return new Response("Not Found", { status: 404 });
75409
76273
  }
@@ -75427,64 +76291,79 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75427
76291
  websocket: {
75428
76292
  open(_ws) {},
75429
76293
  close(ws) {
75430
- const proc = ws._logProcess;
75431
- if (proc) {
75432
- proc.kill();
75433
- ws._logProcess = null;
76294
+ const interval = ws._logInterval;
76295
+ if (interval) {
76296
+ clearInterval(interval);
76297
+ ws._logInterval = null;
75434
76298
  }
75435
76299
  },
75436
76300
  message(ws, message) {
76301
+ let data;
75437
76302
  try {
75438
- const data = JSON.parse(String(message));
75439
- if (data.type === "subscribe" && data.agent) {
75440
- const agentName = String(data.agent).replace(/[^a-zA-Z0-9_-]/g, "");
75441
- if (!agentName || !config.agents[agentName]) {
75442
- try {
75443
- ws.send(JSON.stringify({ type: "error", error: "Unknown agent" }));
75444
- } catch {}
75445
- return;
75446
- }
75447
- const existing = ws._logProcess;
75448
- if (existing) {
75449
- existing.kill();
75450
- ws._logProcess = null;
75451
- }
75452
- const child = spawn5("docker", ["logs", "-f", "--tail", "20", containerName(agentName)], { stdio: ["ignore", "pipe", "pipe"] });
75453
- child.on("error", (err) => {
76303
+ data = JSON.parse(String(message));
76304
+ } catch {
76305
+ return;
76306
+ }
76307
+ const clearLogInterval = () => {
76308
+ const existing = ws._logInterval;
76309
+ if (existing) {
76310
+ clearInterval(existing);
76311
+ ws._logInterval = null;
76312
+ }
76313
+ };
76314
+ if (data && data.type === "unsubscribe") {
76315
+ clearLogInterval();
76316
+ return;
76317
+ }
76318
+ if (data && data.type === "subscribe" && data.agent) {
76319
+ const agentName = String(data.agent).replace(/[^a-zA-Z0-9_-]/g, "");
76320
+ if (!agentName || !config.agents[agentName]) {
76321
+ try {
76322
+ ws.send(JSON.stringify({ type: "error", error: "Unknown agent" }));
76323
+ } catch {}
76324
+ return;
76325
+ }
76326
+ clearLogInterval();
76327
+ const poll = async () => {
76328
+ let result;
76329
+ try {
76330
+ result = await fetchAgentLogsViaHostd(agentName, LOG_POLL_TAIL_LINES);
76331
+ } catch (err) {
75454
76332
  try {
75455
76333
  ws.send(JSON.stringify({
75456
76334
  type: "log_error",
75457
76335
  agent: agentName,
75458
- data: `failed to stream logs: ${err.message}
75459
- `
76336
+ data: err instanceof Error ? err.message : String(err)
75460
76337
  }));
75461
76338
  } catch {}
75462
- });
75463
- child.stdout.on("data", (chunk) => {
76339
+ clearLogInterval();
76340
+ return;
76341
+ }
76342
+ if (result.ok) {
75464
76343
  try {
75465
76344
  ws.send(JSON.stringify({
75466
76345
  type: "log",
75467
76346
  agent: agentName,
75468
- data: chunk.toString("utf-8")
76347
+ data: result.logs,
76348
+ replace: true
75469
76349
  }));
75470
76350
  } catch {
75471
- child.kill();
76351
+ clearLogInterval();
75472
76352
  }
75473
- });
75474
- child.stderr.on("data", (chunk) => {
76353
+ } else {
75475
76354
  try {
75476
76355
  ws.send(JSON.stringify({
75477
76356
  type: "log_error",
75478
76357
  agent: agentName,
75479
- data: chunk.toString("utf-8")
76358
+ data: result.error
75480
76359
  }));
75481
- } catch {
75482
- child.kill();
75483
- }
75484
- });
75485
- ws._logProcess = child;
75486
- }
75487
- } catch {}
76360
+ } catch {}
76361
+ clearLogInterval();
76362
+ }
76363
+ };
76364
+ poll();
76365
+ ws._logInterval = setInterval(() => void poll(), LOG_POLL_INTERVAL_MS);
76366
+ }
75488
76367
  }
75489
76368
  }
75490
76369
  });
@@ -76447,8 +77326,8 @@ init_loader();
76447
77326
  init_lifecycle();
76448
77327
  import { cpSync as cpSync2, existsSync as existsSync58, mkdirSync as mkdirSync32, readFileSync as readFileSync52, realpathSync as realpathSync6, rmSync as rmSync12, statSync as statSync26, chownSync as chownSync5 } from "node:fs";
76449
77328
  import { spawnSync as spawnSync9 } from "node:child_process";
76450
- import { join as join60, dirname as dirname14, resolve as resolve34 } from "node:path";
76451
- import { homedir as homedir36 } from "node:os";
77329
+ import { join as join61, dirname as dirname14, resolve as resolve34 } from "node:path";
77330
+ import { homedir as homedir37 } from "node:os";
76452
77331
 
76453
77332
  // src/cli/release-yaml.ts
76454
77333
  var import_yaml18 = __toESM(require_dist(), 1);
@@ -76487,13 +77366,13 @@ function defaultPersistPin(configPath) {
76487
77366
  } catch {}
76488
77367
  };
76489
77368
  }
76490
- var DEFAULT_COMPOSE_PATH = join60(homedir36(), ".switchroom", "compose", "docker-compose.yml");
77369
+ var DEFAULT_COMPOSE_PATH = join61(homedir37(), ".switchroom", "compose", "docker-compose.yml");
76491
77370
  function runningFromSwitchroomCheckout(scriptPath) {
76492
77371
  let dir = dirname14(scriptPath);
76493
77372
  for (let i = 0;i < 12; i++) {
76494
- if (existsSync58(join60(dir, ".git"))) {
77373
+ if (existsSync58(join61(dir, ".git"))) {
76495
77374
  try {
76496
- const pkg = JSON.parse(readFileSync52(join60(dir, "package.json"), "utf-8"));
77375
+ const pkg = JSON.parse(readFileSync52(join61(dir, "package.json"), "utf-8"));
76497
77376
  if (pkg.name === "switchroom")
76498
77377
  return true;
76499
77378
  } catch {}
@@ -76653,7 +77532,7 @@ function planUpdate(opts) {
76653
77532
  return;
76654
77533
  }
76655
77534
  const source = resolve34(import.meta.dirname, "../../skills");
76656
- const dest = join60(homedir36(), ".switchroom", "skills", "_bundled");
77535
+ const dest = join61(homedir37(), ".switchroom", "skills", "_bundled");
76657
77536
  if (!existsSync58(source)) {
76658
77537
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
76659
77538
  `);
@@ -76767,7 +77646,7 @@ function defaultStatusProbe(composePath) {
76767
77646
  } catch {}
76768
77647
  let dir = dirname14(scriptPath);
76769
77648
  for (let i = 0;i < 8; i++) {
76770
- const pkgPath = join60(dir, "package.json");
77649
+ const pkgPath = join61(dir, "package.json");
76771
77650
  if (existsSync58(pkgPath)) {
76772
77651
  try {
76773
77652
  const pkg = JSON.parse(readFileSync52(pkgPath, "utf-8"));
@@ -77209,7 +78088,7 @@ init_helpers();
77209
78088
  init_lifecycle();
77210
78089
  import { execSync as execSync4 } from "node:child_process";
77211
78090
  import { existsSync as existsSync59, readFileSync as readFileSync54 } from "node:fs";
77212
- import { dirname as dirname15, join as join61 } from "node:path";
78091
+ import { dirname as dirname15, join as join62 } from "node:path";
77213
78092
  function getClaudeCodeVersion() {
77214
78093
  try {
77215
78094
  const out = execSync4("claude --version 2>/dev/null", {
@@ -77259,11 +78138,11 @@ function formatUptime3(timestamp) {
77259
78138
  function locateSwitchroomInstallDir() {
77260
78139
  let dir = import.meta.dirname;
77261
78140
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
77262
- const pkgPath = join61(dir, "package.json");
78141
+ const pkgPath = join62(dir, "package.json");
77263
78142
  if (existsSync59(pkgPath)) {
77264
78143
  try {
77265
78144
  const pkg = JSON.parse(readFileSync54(pkgPath, "utf-8"));
77266
- if (pkg.name === "switchroom" && existsSync59(join61(dir, ".git"))) {
78145
+ if (pkg.name === "switchroom" && existsSync59(join62(dir, ".git"))) {
77267
78146
  return dir;
77268
78147
  }
77269
78148
  } catch {}
@@ -77500,7 +78379,7 @@ import {
77500
78379
  writeFileSync as writeFileSync28,
77501
78380
  writeSync as writeSync7
77502
78381
  } from "node:fs";
77503
- import { join as join62 } from "node:path";
78382
+ import { join as join63 } from "node:path";
77504
78383
  import { randomBytes as randomBytes12 } from "node:crypto";
77505
78384
  import { execSync as execSync5 } from "node:child_process";
77506
78385
 
@@ -77898,7 +78777,7 @@ function redactedMarker(ruleId) {
77898
78777
  var ISSUES_FILE = "issues.jsonl";
77899
78778
  var ISSUES_LOCK = "issues.lock";
77900
78779
  function readAll(stateDir) {
77901
- const path4 = join62(stateDir, ISSUES_FILE);
78780
+ const path4 = join63(stateDir, ISSUES_FILE);
77902
78781
  if (!existsSync60(path4))
77903
78782
  return [];
77904
78783
  let raw;
@@ -77976,7 +78855,7 @@ function record(stateDir, input, nowFn = Date.now) {
77976
78855
  });
77977
78856
  }
77978
78857
  function resolve37(stateDir, fingerprint, nowFn = Date.now) {
77979
- if (!existsSync60(join62(stateDir, ISSUES_FILE)))
78858
+ if (!existsSync60(join63(stateDir, ISSUES_FILE)))
77980
78859
  return 0;
77981
78860
  return withLock(stateDir, () => {
77982
78861
  const all = readAll(stateDir);
@@ -77994,7 +78873,7 @@ function resolve37(stateDir, fingerprint, nowFn = Date.now) {
77994
78873
  });
77995
78874
  }
77996
78875
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
77997
- if (!existsSync60(join62(stateDir, ISSUES_FILE)))
78876
+ if (!existsSync60(join63(stateDir, ISSUES_FILE)))
77998
78877
  return 0;
77999
78878
  return withLock(stateDir, () => {
78000
78879
  const all = readAll(stateDir);
@@ -78012,7 +78891,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
78012
78891
  });
78013
78892
  }
78014
78893
  function prune(stateDir, opts = {}) {
78015
- if (!existsSync60(join62(stateDir, ISSUES_FILE)))
78894
+ if (!existsSync60(join63(stateDir, ISSUES_FILE)))
78016
78895
  return 0;
78017
78896
  return withLock(stateDir, () => {
78018
78897
  const all = readAll(stateDir);
@@ -78045,7 +78924,7 @@ function ensureDir(stateDir) {
78045
78924
  mkdirSync33(stateDir, { recursive: true });
78046
78925
  }
78047
78926
  function writeAll(stateDir, events) {
78048
- const path4 = join62(stateDir, ISSUES_FILE);
78927
+ const path4 = join63(stateDir, ISSUES_FILE);
78049
78928
  sweepOrphanTmpFiles(stateDir);
78050
78929
  const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
78051
78930
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -78067,7 +78946,7 @@ function sweepOrphanTmpFiles(stateDir) {
78067
78946
  for (const entry of entries) {
78068
78947
  if (!entry.startsWith(TMP_PREFIX))
78069
78948
  continue;
78070
- const tmpPath = join62(stateDir, entry);
78949
+ const tmpPath = join63(stateDir, entry);
78071
78950
  try {
78072
78951
  const stat = statSync28(tmpPath);
78073
78952
  if (stat.mtimeMs < cutoff) {
@@ -78079,7 +78958,7 @@ function sweepOrphanTmpFiles(stateDir) {
78079
78958
  var LOCK_RETRY_MS = 25;
78080
78959
  var LOCK_TIMEOUT_MS = 1e4;
78081
78960
  function withLock(stateDir, fn) {
78082
- const lockPath = join62(stateDir, ISSUES_LOCK);
78961
+ const lockPath = join63(stateDir, ISSUES_LOCK);
78083
78962
  const startedAt = Date.now();
78084
78963
  let fd = null;
78085
78964
  while (fd === null) {
@@ -78363,8 +79242,8 @@ function relTime(deltaMs) {
78363
79242
  // src/cli/deps.ts
78364
79243
  init_source();
78365
79244
  import { existsSync as existsSync63 } from "node:fs";
78366
- import { homedir as homedir39 } from "node:os";
78367
- import { join as join65, resolve as resolve38 } from "node:path";
79245
+ import { homedir as homedir40 } from "node:os";
79246
+ import { join as join66, resolve as resolve38 } from "node:path";
78368
79247
 
78369
79248
  // src/deps/python.ts
78370
79249
  import { createHash as createHash11 } from "node:crypto";
@@ -78375,8 +79254,8 @@ import {
78375
79254
  rmSync as rmSync13,
78376
79255
  writeFileSync as writeFileSync29
78377
79256
  } from "node:fs";
78378
- import { dirname as dirname16, join as join63 } from "node:path";
78379
- import { homedir as homedir37 } from "node:os";
79257
+ import { dirname as dirname16, join as join64 } from "node:path";
79258
+ import { homedir as homedir38 } from "node:os";
78380
79259
  import { execFileSync as execFileSync19 } from "node:child_process";
78381
79260
 
78382
79261
  class PythonEnvError extends Error {
@@ -78388,7 +79267,7 @@ class PythonEnvError extends Error {
78388
79267
  }
78389
79268
  }
78390
79269
  function defaultPythonCacheRoot() {
78391
- return join63(homedir37(), ".switchroom", "deps", "python");
79270
+ return join64(homedir38(), ".switchroom", "deps", "python");
78392
79271
  }
78393
79272
  function hashFile(path4) {
78394
79273
  return createHash11("sha256").update(readFileSync56(path4)).digest("hex");
@@ -78400,11 +79279,11 @@ function ensurePythonEnv(opts) {
78400
79279
  if (!existsSync61(requirementsPath)) {
78401
79280
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
78402
79281
  }
78403
- const venvDir = join63(cacheRoot, skillName);
78404
- const stampPath = join63(venvDir, ".requirements.sha256");
78405
- const binDir = join63(venvDir, "bin");
78406
- const pythonBin = join63(binDir, "python");
78407
- const pipBin = join63(binDir, "pip");
79282
+ const venvDir = join64(cacheRoot, skillName);
79283
+ const stampPath = join64(venvDir, ".requirements.sha256");
79284
+ const binDir = join64(venvDir, "bin");
79285
+ const pythonBin = join64(binDir, "python");
79286
+ const pipBin = join64(binDir, "pip");
78408
79287
  const targetHash = hashFile(requirementsPath);
78409
79288
  if (!force && existsSync61(stampPath) && existsSync61(pythonBin)) {
78410
79289
  const existingHash = readFileSync56(stampPath, "utf8").trim();
@@ -78463,8 +79342,8 @@ import {
78463
79342
  rmSync as rmSync14,
78464
79343
  writeFileSync as writeFileSync30
78465
79344
  } from "node:fs";
78466
- import { dirname as dirname17, join as join64 } from "node:path";
78467
- import { homedir as homedir38 } from "node:os";
79345
+ import { dirname as dirname17, join as join65 } from "node:path";
79346
+ import { homedir as homedir39 } from "node:os";
78468
79347
  import { execFileSync as execFileSync20 } from "node:child_process";
78469
79348
 
78470
79349
  class NodeEnvError extends Error {
@@ -78487,7 +79366,7 @@ var LOCKFILES_FOR = {
78487
79366
  npm: ["package-lock.json"]
78488
79367
  };
78489
79368
  function defaultNodeCacheRoot() {
78490
- return join64(homedir38(), ".switchroom", "deps", "node");
79369
+ return join65(homedir39(), ".switchroom", "deps", "node");
78491
79370
  }
78492
79371
  function hashDepInputs(packageJsonPath) {
78493
79372
  const sourceDir = dirname17(packageJsonPath);
@@ -78496,7 +79375,7 @@ function hashDepInputs(packageJsonPath) {
78496
79375
  `);
78497
79376
  hasher.update(readFileSync57(packageJsonPath));
78498
79377
  for (const lockName of ALL_LOCKFILES) {
78499
- const lockPath = join64(sourceDir, lockName);
79378
+ const lockPath = join65(sourceDir, lockName);
78500
79379
  if (existsSync62(lockPath)) {
78501
79380
  hasher.update(`
78502
79381
  `);
@@ -78516,10 +79395,10 @@ function ensureNodeEnv(opts) {
78516
79395
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
78517
79396
  }
78518
79397
  const sourceDir = dirname17(packageJsonPath);
78519
- const envDir = join64(cacheRoot, skillName);
78520
- const stampPath = join64(envDir, ".package.sha256");
78521
- const nodeModulesDir = join64(envDir, "node_modules");
78522
- const binDir = join64(nodeModulesDir, ".bin");
79398
+ const envDir = join65(cacheRoot, skillName);
79399
+ const stampPath = join65(envDir, ".package.sha256");
79400
+ const nodeModulesDir = join65(envDir, "node_modules");
79401
+ const binDir = join65(nodeModulesDir, ".bin");
78523
79402
  const targetHash = hashDepInputs(packageJsonPath);
78524
79403
  if (!force && existsSync62(stampPath) && existsSync62(nodeModulesDir)) {
78525
79404
  const existingHash = readFileSync57(stampPath, "utf8").trim();
@@ -78537,12 +79416,12 @@ function ensureNodeEnv(opts) {
78537
79416
  rmSync14(envDir, { recursive: true, force: true });
78538
79417
  }
78539
79418
  mkdirSync35(envDir, { recursive: true });
78540
- copyFileSync9(packageJsonPath, join64(envDir, "package.json"));
79419
+ copyFileSync9(packageJsonPath, join65(envDir, "package.json"));
78541
79420
  let copiedLockfile = false;
78542
79421
  for (const lockName of LOCKFILES_FOR[installer]) {
78543
- const lockPath = join64(sourceDir, lockName);
79422
+ const lockPath = join65(sourceDir, lockName);
78544
79423
  if (existsSync62(lockPath)) {
78545
- copyFileSync9(lockPath, join64(envDir, lockName));
79424
+ copyFileSync9(lockPath, join65(envDir, lockName));
78546
79425
  copiedLockfile = true;
78547
79426
  }
78548
79427
  }
@@ -78571,7 +79450,7 @@ function ensureNodeEnv(opts) {
78571
79450
 
78572
79451
  // src/cli/deps.ts
78573
79452
  function builtinSkillsRoot() {
78574
- return resolve38(homedir39(), ".switchroom/skills/_bundled");
79453
+ return resolve38(homedir40(), ".switchroom/skills/_bundled");
78575
79454
  }
78576
79455
  function registerDepsCommand(program3) {
78577
79456
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
@@ -78581,13 +79460,13 @@ function registerDepsCommand(program3) {
78581
79460
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
78582
79461
  process.exit(1);
78583
79462
  }
78584
- const skillDir = join65(skillsRoot, skill);
79463
+ const skillDir = join66(skillsRoot, skill);
78585
79464
  if (!existsSync63(skillDir)) {
78586
79465
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
78587
79466
  process.exit(1);
78588
79467
  }
78589
- const requirementsPath = join65(skillDir, "requirements.txt");
78590
- const packageJsonPath = join65(skillDir, "package.json");
79468
+ const requirementsPath = join66(skillDir, "requirements.txt");
79469
+ const packageJsonPath = join66(skillDir, "package.json");
78591
79470
  const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync63(requirementsPath));
78592
79471
  const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync63(packageJsonPath));
78593
79472
  let did = 0;
@@ -79542,7 +80421,7 @@ init_helpers();
79542
80421
  init_loader();
79543
80422
  init_merge();
79544
80423
  import { copyFileSync as copyFileSync10, existsSync as existsSync65, readFileSync as readFileSync58, writeFileSync as writeFileSync31 } from "node:fs";
79545
- import { join as join66, resolve as resolve40 } from "node:path";
80424
+ import { join as join67, resolve as resolve40 } from "node:path";
79546
80425
  init_schema();
79547
80426
  function resolveSoulTargetOrExit(program3, agentName) {
79548
80427
  const config = getConfig(program3);
@@ -79566,7 +80445,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
79566
80445
  profileName,
79567
80446
  profilePath,
79568
80447
  workspaceDir,
79569
- soulPath: join66(workspaceDir, "SOUL.md"),
80448
+ soulPath: join67(workspaceDir, "SOUL.md"),
79570
80449
  soul: merged.soul
79571
80450
  };
79572
80451
  }
@@ -79633,7 +80512,7 @@ function registerSoulCommand(program3) {
79633
80512
  init_helpers();
79634
80513
  init_loader();
79635
80514
  import { existsSync as existsSync66, readFileSync as readFileSync59, readdirSync as readdirSync23, statSync as statSync29 } from "node:fs";
79636
- import { resolve as resolve41, join as join67 } from "node:path";
80515
+ import { resolve as resolve41, join as join68 } from "node:path";
79637
80516
  import { createHash as createHash13 } from "node:crypto";
79638
80517
  init_merge();
79639
80518
  init_hindsight();
@@ -79644,7 +80523,7 @@ function estimateTokens(bytes) {
79644
80523
  return Math.round(bytes / 3.7);
79645
80524
  }
79646
80525
  function readMcpServerNames(agentDir) {
79647
- const mcpPath = join67(agentDir, ".mcp.json");
80526
+ const mcpPath = join68(agentDir, ".mcp.json");
79648
80527
  if (!existsSync66(mcpPath))
79649
80528
  return [];
79650
80529
  try {
@@ -79658,7 +80537,7 @@ function sha256(content) {
79658
80537
  return createHash13("sha256").update(content).digest("hex").slice(0, 16);
79659
80538
  }
79660
80539
  function findLatestTranscriptJsonl(claudeConfigDir) {
79661
- const projectsDir = join67(claudeConfigDir, "projects");
80540
+ const projectsDir = join68(claudeConfigDir, "projects");
79662
80541
  if (!existsSync66(projectsDir))
79663
80542
  return;
79664
80543
  try {
@@ -79667,8 +80546,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
79667
80546
  for (const entry of entries) {
79668
80547
  if (!entry.isDirectory())
79669
80548
  continue;
79670
- const projectPath = join67(projectsDir, entry.name);
79671
- const transcriptPath = join67(projectPath, "transcript.jsonl");
80549
+ const projectPath = join68(projectsDir, entry.name);
80550
+ const transcriptPath = join68(projectPath, "transcript.jsonl");
79672
80551
  if (!existsSync66(transcriptPath))
79673
80552
  continue;
79674
80553
  const stat3 = statSync29(transcriptPath);
@@ -79737,11 +80616,11 @@ function registerDebugCommand(program3) {
79737
80616
  process.exit(1);
79738
80617
  }
79739
80618
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
79740
- const claudeConfigDir = join67(agentDir, ".claude");
79741
- const claudeMdPath = join67(agentDir, "CLAUDE.md");
79742
- const soulMdPath = join67(agentDir, "SOUL.md");
79743
- const workspaceSoulMdPath = join67(workspaceDir, "SOUL.md");
79744
- const handoffPath = join67(agentDir, ".handoff.md");
80619
+ const claudeConfigDir = join68(agentDir, ".claude");
80620
+ const claudeMdPath = join68(agentDir, "CLAUDE.md");
80621
+ const soulMdPath = join68(agentDir, "SOUL.md");
80622
+ const workspaceSoulMdPath = join68(workspaceDir, "SOUL.md");
80623
+ const handoffPath = join68(agentDir, ".handoff.md");
79745
80624
  const lastN = parseInt(opts.last, 10);
79746
80625
  if (isNaN(lastN) || lastN < 1) {
79747
80626
  console.error("--last must be a positive integer");
@@ -79870,9 +80749,9 @@ function registerDebugCommand(program3) {
79870
80749
  const soulMdBytes = soulMdContent.length;
79871
80750
  const perTurnBytes = dynamicResult.concatenated.length;
79872
80751
  const userBytes = userMessage?.text.length ?? 0;
79873
- const fleetDir = join67(agentsDir, "..", "fleet");
79874
- const fleetInvPath = join67(fleetDir, "switchroom-invariants.md");
79875
- const fleetClaudePath = join67(fleetDir, "CLAUDE.md");
80752
+ const fleetDir = join68(agentsDir, "..", "fleet");
80753
+ const fleetInvPath = join68(fleetDir, "switchroom-invariants.md");
80754
+ const fleetClaudePath = join68(fleetDir, "CLAUDE.md");
79876
80755
  const fleetInvBytes = existsSync66(fleetInvPath) ? readFileSync59(fleetInvPath, "utf-8").length : 0;
79877
80756
  const fleetClaudeBytes = existsSync66(fleetClaudePath) ? readFileSync59(fleetClaudePath, "utf-8").length : 0;
79878
80757
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
@@ -79908,8 +80787,8 @@ init_source();
79908
80787
  // src/worktree/claim.ts
79909
80788
  import { execFileSync as execFileSync21 } from "node:child_process";
79910
80789
  import { closeSync as closeSync12, mkdirSync as mkdirSync37, openSync as openSync12, existsSync as existsSync68, unlinkSync as unlinkSync13 } from "node:fs";
79911
- import { join as join69, resolve as resolve43 } from "node:path";
79912
- import { homedir as homedir41 } from "node:os";
80790
+ import { join as join70, resolve as resolve43 } from "node:path";
80791
+ import { homedir as homedir42 } from "node:os";
79913
80792
  import { randomBytes as randomBytes13 } from "node:crypto";
79914
80793
 
79915
80794
  // src/worktree/registry.ts
@@ -79922,13 +80801,13 @@ import {
79922
80801
  existsSync as existsSync67,
79923
80802
  renameSync as renameSync13
79924
80803
  } from "node:fs";
79925
- import { join as join68, resolve as resolve42 } from "node:path";
79926
- import { homedir as homedir40 } from "node:os";
80804
+ import { join as join69, resolve as resolve42 } from "node:path";
80805
+ import { homedir as homedir41 } from "node:os";
79927
80806
  function registryDir() {
79928
- return resolve42(process.env.SWITCHROOM_WORKTREE_DIR ?? join68(homedir40(), ".switchroom", "worktrees"));
80807
+ return resolve42(process.env.SWITCHROOM_WORKTREE_DIR ?? join69(homedir41(), ".switchroom", "worktrees"));
79929
80808
  }
79930
80809
  function recordPath(id) {
79931
- return join68(registryDir(), `${id}.json`);
80810
+ return join69(registryDir(), `${id}.json`);
79932
80811
  }
79933
80812
  function ensureDir2() {
79934
80813
  mkdirSync36(registryDir(), { recursive: true });
@@ -79979,7 +80858,7 @@ function acquireRepoLock(repoPath) {
79979
80858
  const lockDir = registryDir();
79980
80859
  mkdirSync37(lockDir, { recursive: true });
79981
80860
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
79982
- const lockPath = join69(lockDir, `.lock-${lockName}`);
80861
+ const lockPath = join70(lockDir, `.lock-${lockName}`);
79983
80862
  const deadline = Date.now() + 5000;
79984
80863
  let fd = null;
79985
80864
  while (fd === null) {
@@ -80006,7 +80885,7 @@ function acquireRepoLock(repoPath) {
80006
80885
  }
80007
80886
  var DEFAULT_CONCURRENCY = 5;
80008
80887
  function worktreesBaseDir() {
80009
- return resolve43(process.env.SWITCHROOM_WORKTREE_BASE ?? join69(homedir41(), ".switchroom", "worktree-checkouts"));
80888
+ return resolve43(process.env.SWITCHROOM_WORKTREE_BASE ?? join70(homedir42(), ".switchroom", "worktree-checkouts"));
80010
80889
  }
80011
80890
  function shortId() {
80012
80891
  return randomBytes13(4).toString("hex");
@@ -80028,7 +80907,7 @@ function resolveRepoPath(repo, codeRepos) {
80028
80907
  }
80029
80908
  function expandHome(p) {
80030
80909
  if (p.startsWith("~/"))
80031
- return join69(homedir41(), p.slice(2));
80910
+ return join70(homedir42(), p.slice(2));
80032
80911
  return p;
80033
80912
  }
80034
80913
  async function claimWorktree(input, codeRepos) {
@@ -80056,7 +80935,7 @@ async function claimWorktree(input, codeRepos) {
80056
80935
  branch = `task/${taskSuffix}-${id}`;
80057
80936
  const baseDir = worktreesBaseDir();
80058
80937
  mkdirSync37(baseDir, { recursive: true });
80059
- worktreePath = join69(baseDir, `${id}-${taskSuffix}`);
80938
+ worktreePath = join70(baseDir, `${id}-${taskSuffix}`);
80060
80939
  const now = new Date().toISOString();
80061
80940
  const record2 = {
80062
80941
  id,
@@ -80311,7 +81190,7 @@ import {
80311
81190
  rmSync as rmSync15,
80312
81191
  writeFileSync as writeFileSync33
80313
81192
  } from "node:fs";
80314
- import { join as join70 } from "node:path";
81193
+ import { join as join71 } from "node:path";
80315
81194
  function encodeCredentialsFilename(email) {
80316
81195
  const SAFE = new Set([
80317
81196
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -80501,16 +81380,16 @@ function resolveCredentialsDir(env2) {
80501
81380
  if (explicit && explicit.length > 0)
80502
81381
  return explicit;
80503
81382
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
80504
- return join70(stateBase, "google-workspace-mcp", "credentials");
81383
+ return join71(stateBase, "google-workspace-mcp", "credentials");
80505
81384
  }
80506
81385
  function writeSeedFile(dir, email, seed) {
80507
81386
  mkdirSync38(dir, { recursive: true, mode: 448 });
80508
81387
  chmodSync9(dir, 448);
80509
81388
  for (const name of readdirSync25(dir)) {
80510
- rmSync15(join70(dir, name), { force: true, recursive: true });
81389
+ rmSync15(join71(dir, name), { force: true, recursive: true });
80511
81390
  }
80512
81391
  const filename = encodeCredentialsFilename(email);
80513
- const filePath = join70(dir, filename);
81392
+ const filePath = join71(dir, filename);
80514
81393
  writeFileSync33(filePath, JSON.stringify(seed), { mode: 384 });
80515
81394
  chmodSync9(filePath, 384);
80516
81395
  return filePath;
@@ -80631,9 +81510,9 @@ async function runDriveMcpLauncher(opts) {
80631
81510
  }
80632
81511
  const args = buildUvxArgs(tier);
80633
81512
  const env2 = buildChildEnv(process.env, credentialsDir, brokerCreds.accountEmail);
80634
- const { spawn: spawn6 } = await import("node:child_process");
81513
+ const { spawn: spawn5 } = await import("node:child_process");
80635
81514
  const os5 = await import("node:os");
80636
- const child = spawn6("uvx", args, {
81515
+ const child = spawn5("uvx", args, {
80637
81516
  stdio: ["pipe", "pipe", "inherit"],
80638
81517
  env: env2
80639
81518
  });
@@ -80668,9 +81547,9 @@ function registerDriveMcpLauncherCommand(program3) {
80668
81547
 
80669
81548
  // src/cli/m365-mcp-launcher.ts
80670
81549
  init_scaffold_integration();
80671
- import { spawn as spawn6 } from "node:child_process";
81550
+ import { spawn as spawn5 } from "node:child_process";
80672
81551
  import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync39 } from "node:fs";
80673
- import { dirname as dirname18, join as join71 } from "node:path";
81552
+ import { dirname as dirname18, join as join72 } from "node:path";
80674
81553
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
80675
81554
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
80676
81555
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
@@ -80702,7 +81581,7 @@ function writeRefreshHeartbeat(agentName, data) {
80702
81581
  function heartbeatPath(agentName) {
80703
81582
  const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
80704
81583
  if (override) {
80705
- return join71(override, `m365-launcher-${agentName}.heartbeat.json`);
81584
+ return join72(override, `m365-launcher-${agentName}.heartbeat.json`);
80706
81585
  }
80707
81586
  return "/state/agent/m365-launcher.heartbeat.json";
80708
81587
  }
@@ -80874,7 +81753,7 @@ function registerM365McpLauncherCommand(program3) {
80874
81753
  });
80875
81754
  },
80876
81755
  spawnSofteria: (env2) => {
80877
- return spawn6("npx", buildSofteriaArgs(opts), {
81756
+ return spawn5("npx", buildSofteriaArgs(opts), {
80878
81757
  env: env2,
80879
81758
  stdio: ["pipe", "pipe", "pipe"]
80880
81759
  });
@@ -80886,7 +81765,7 @@ function registerM365McpLauncherCommand(program3) {
80886
81765
 
80887
81766
  // src/cli/notion-mcp-launcher.ts
80888
81767
  init_scaffold_integration();
80889
- import { spawn as spawn7 } from "node:child_process";
81768
+ import { spawn as spawn6 } from "node:child_process";
80890
81769
  import { existsSync as existsSync71, mkdirSync as mkdirSync40, writeFileSync as writeFileSync35 } from "node:fs";
80891
81770
  import { dirname as dirname19 } from "node:path";
80892
81771
  var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
@@ -80999,7 +81878,7 @@ function registerNotionMcpLauncherCommand(program3) {
80999
81878
  return result.entry.value;
81000
81879
  },
81001
81880
  spawnMcp: (env2, args) => {
81002
- return spawn7("npx", args, {
81881
+ return spawn6("npx", args, {
81003
81882
  env: env2,
81004
81883
  stdio: ["pipe", "pipe", "pipe"]
81005
81884
  });
@@ -82030,8 +82909,8 @@ agents:
82030
82909
 
82031
82910
  // src/cli/apply.ts
82032
82911
  init_resolver();
82033
- import { dirname as dirname22, join as join74, resolve as resolve45 } from "node:path";
82034
- import { homedir as homedir43 } from "node:os";
82912
+ import { dirname as dirname22, join as join75, resolve as resolve45 } from "node:path";
82913
+ import { homedir as homedir44 } from "node:os";
82035
82914
  import { execFileSync as execFileSync24 } from "node:child_process";
82036
82915
  init_vault();
82037
82916
  init_loader();
@@ -82039,7 +82918,7 @@ init_loader();
82039
82918
 
82040
82919
  // src/cli/update-prompt-hook.ts
82041
82920
  import { existsSync as existsSync72, readFileSync as readFileSync62, writeFileSync as writeFileSync36, chmodSync as chmodSync10, mkdirSync as mkdirSync41 } from "node:fs";
82042
- import { join as join72 } from "node:path";
82921
+ import { join as join73 } from "node:path";
82043
82922
  var HOOK_FILENAME = "update-card-on-prompt.sh";
82044
82923
  function updatePromptHookScript() {
82045
82924
  return `#!/bin/bash
@@ -82105,9 +82984,9 @@ exit 0
82105
82984
  `;
82106
82985
  }
82107
82986
  function installUpdatePromptHook(agentDir) {
82108
- const hooksDir = join72(agentDir, ".claude", "hooks");
82987
+ const hooksDir = join73(agentDir, ".claude", "hooks");
82109
82988
  mkdirSync41(hooksDir, { recursive: true });
82110
- const scriptPath = join72(hooksDir, HOOK_FILENAME);
82989
+ const scriptPath = join73(hooksDir, HOOK_FILENAME);
82111
82990
  const desired = updatePromptHookScript();
82112
82991
  let installed = false;
82113
82992
  const existing = existsSync72(scriptPath) ? readFileSync62(scriptPath, "utf-8") : "";
@@ -82120,7 +82999,7 @@ function installUpdatePromptHook(agentDir) {
82120
82999
  chmodSync10(scriptPath, 493);
82121
83000
  } catch {}
82122
83001
  }
82123
- const settingsPath = join72(agentDir, ".claude", "settings.json");
83002
+ const settingsPath = join73(agentDir, ".claude", "settings.json");
82124
83003
  if (!existsSync72(settingsPath)) {
82125
83004
  return { scriptPath, settingsPath, installed };
82126
83005
  }
@@ -82222,14 +83101,14 @@ var EMBEDDED_EXAMPLES = {
82222
83101
  switchroom: switchroom_default,
82223
83102
  minimal: minimal_default
82224
83103
  };
82225
- var DEFAULT_COMPOSE_PATH2 = join74(homedir43(), ".switchroom", "compose", "docker-compose.yml");
83104
+ var DEFAULT_COMPOSE_PATH2 = join75(homedir44(), ".switchroom", "compose", "docker-compose.yml");
82226
83105
  var COMPOSE_PROJECT2 = "switchroom";
82227
83106
  function resolveVaultBindMountDir(homeDir, ctx) {
82228
83107
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
82229
83108
  if (isCustomPath && ctx.customVaultPath) {
82230
83109
  return dirname22(ctx.customVaultPath);
82231
83110
  }
82232
- return join74(homeDir, ".switchroom", "vault");
83111
+ return join75(homeDir, ".switchroom", "vault");
82233
83112
  }
82234
83113
  function inspectVaultBindMountDir(vaultDir) {
82235
83114
  if (!existsSync74(vaultDir))
@@ -82258,44 +83137,44 @@ function hasVaultRefs(value) {
82258
83137
  return false;
82259
83138
  }
82260
83139
  async function ensureHostMountSources(config) {
82261
- const home2 = homedir43();
83140
+ const home2 = homedir44();
82262
83141
  const dirs = [
82263
- join74(home2, ".switchroom", "approvals"),
82264
- join74(home2, ".switchroom", "scheduler"),
82265
- join74(home2, ".switchroom", "logs"),
82266
- join74(home2, ".switchroom", "compose"),
82267
- join74(home2, ".switchroom", "broker-operator")
83142
+ join75(home2, ".switchroom", "approvals"),
83143
+ join75(home2, ".switchroom", "scheduler"),
83144
+ join75(home2, ".switchroom", "logs"),
83145
+ join75(home2, ".switchroom", "compose"),
83146
+ join75(home2, ".switchroom", "broker-operator")
82268
83147
  ];
82269
83148
  for (const name of Object.keys(config.agents)) {
82270
- dirs.push(join74(home2, ".switchroom", "agents", name));
82271
- dirs.push(join74(home2, ".switchroom", "logs", name));
82272
- dirs.push(join74(home2, ".claude", "projects", name));
82273
- dirs.push(join74(home2, ".switchroom", "audit", name));
82274
- if (existsSync74(join74(home2, ".switchroom-config"))) {
82275
- dirs.push(join74(home2, ".switchroom-config", "agents", name, "personal-skills"));
83149
+ dirs.push(join75(home2, ".switchroom", "agents", name));
83150
+ dirs.push(join75(home2, ".switchroom", "logs", name));
83151
+ dirs.push(join75(home2, ".claude", "projects", name));
83152
+ dirs.push(join75(home2, ".switchroom", "audit", name));
83153
+ if (existsSync74(join75(home2, ".switchroom-config"))) {
83154
+ dirs.push(join75(home2, ".switchroom-config", "agents", name, "personal-skills"));
82276
83155
  }
82277
83156
  }
82278
83157
  for (const dir of dirs) {
82279
83158
  await mkdir2(dir, { recursive: true });
82280
83159
  }
82281
- const autoUnlockPath = join74(home2, ".switchroom", "vault-auto-unlock");
83160
+ const autoUnlockPath = join75(home2, ".switchroom", "vault-auto-unlock");
82282
83161
  if (!existsSync74(autoUnlockPath)) {
82283
83162
  writeFileSync37(autoUnlockPath, "", { mode: 384 });
82284
83163
  }
82285
- const auditLogPath = join74(home2, ".switchroom", "vault-audit.log");
83164
+ const auditLogPath = join75(home2, ".switchroom", "vault-audit.log");
82286
83165
  if (!existsSync74(auditLogPath)) {
82287
83166
  writeFileSync37(auditLogPath, "", { mode: 420 });
82288
83167
  }
82289
- const grantsDbPath = join74(home2, ".switchroom", "vault-grants.db");
83168
+ const grantsDbPath = join75(home2, ".switchroom", "vault-grants.db");
82290
83169
  if (!existsSync74(grantsDbPath)) {
82291
83170
  writeFileSync37(grantsDbPath, "", { mode: 384 });
82292
83171
  }
82293
- const hostdAuditLogPath = join74(home2, ".switchroom", "host-control-audit.log");
83172
+ const hostdAuditLogPath = join75(home2, ".switchroom", "host-control-audit.log");
82294
83173
  if (!existsSync74(hostdAuditLogPath)) {
82295
83174
  writeFileSync37(hostdAuditLogPath, "", { mode: 420 });
82296
83175
  }
82297
83176
  for (const name of Object.keys(config.agents)) {
82298
- const tokenPath = join74(home2, ".switchroom", "agents", name, ".vault-token");
83177
+ const tokenPath = join75(home2, ".switchroom", "agents", name, ".vault-token");
82299
83178
  if (!existsSync74(tokenPath)) {
82300
83179
  writeFileSync37(tokenPath, "", { mode: 384 });
82301
83180
  }
@@ -82304,15 +83183,15 @@ async function ensureHostMountSources(config) {
82304
83183
  chownSync7(tokenPath, uid, uid);
82305
83184
  } catch {}
82306
83185
  }
82307
- const fleetDir = join74(home2, ".switchroom", "fleet");
83186
+ const fleetDir = join75(home2, ".switchroom", "fleet");
82308
83187
  await mkdir2(fleetDir, { recursive: true });
82309
- const invariantsPath = join74(fleetDir, "switchroom-invariants.md");
83188
+ const invariantsPath = join75(fleetDir, "switchroom-invariants.md");
82310
83189
  const invariantsCanonical = renderFleetInvariants();
82311
83190
  const invariantsCurrent = existsSync74(invariantsPath) ? readFileSync63(invariantsPath, "utf-8") : null;
82312
83191
  if (invariantsCurrent !== invariantsCanonical) {
82313
83192
  writeFileSync37(invariantsPath, invariantsCanonical, { mode: 420 });
82314
83193
  }
82315
- const fleetClaudePath = join74(fleetDir, "CLAUDE.md");
83194
+ const fleetClaudePath = join75(fleetDir, "CLAUDE.md");
82316
83195
  if (!existsSync74(fleetClaudePath)) {
82317
83196
  writeFileSync37(fleetClaudePath, [
82318
83197
  "# Switchroom fleet defaults",
@@ -82395,10 +83274,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
82395
83274
  `));
82396
83275
  }
82397
83276
  }
82398
- function writeInstallTypeCache(homeDir = homedir43()) {
83277
+ function writeInstallTypeCache(homeDir = homedir44()) {
82399
83278
  const ctx = detectInstallType();
82400
- const dir = join74(homeDir, ".switchroom");
82401
- const out = join74(dir, "install-type.json");
83279
+ const dir = join75(homeDir, ".switchroom");
83280
+ const out = join75(dir, "install-type.json");
82402
83281
  const tmp = `${out}.tmp`;
82403
83282
  mkdirSync42(dir, { recursive: true });
82404
83283
  const payload = {
@@ -82447,14 +83326,14 @@ Applying switchroom config...
82447
83326
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
82448
83327
  `));
82449
83328
  try {
82450
- installUpdatePromptHook(join74(agentsDir, name));
83329
+ installUpdatePromptHook(join75(agentsDir, name));
82451
83330
  } catch (hookErr) {
82452
83331
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
82453
83332
  `));
82454
83333
  }
82455
83334
  try {
82456
83335
  const uid = allocateAgentUid(name);
82457
- alignAgentUid(name, join74(agentsDir, name), uid, {
83336
+ alignAgentUid(name, join75(agentsDir, name), uid, {
82458
83337
  confirm: !options.nonInteractive,
82459
83338
  writeOut
82460
83339
  });
@@ -82491,7 +83370,7 @@ Applying switchroom config...
82491
83370
  for (const name of agentNames) {
82492
83371
  try {
82493
83372
  const uid = allocateAgentUid(name);
82494
- alignAgentUid(name, join74(agentsDir, name), uid, {
83373
+ alignAgentUid(name, join75(agentsDir, name), uid, {
82495
83374
  confirm: !options.nonInteractive,
82496
83375
  writeOut
82497
83376
  });
@@ -82505,7 +83384,7 @@ Applying switchroom config...
82505
83384
  }
82506
83385
  const vaultPathConfigured = config.vault?.path;
82507
83386
  const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
82508
- const migrationResult = migrateVaultLayout(homedir43(), {
83387
+ const migrationResult = migrateVaultLayout(homedir44(), {
82509
83388
  customVaultPath
82510
83389
  });
82511
83390
  switch (migrationResult.kind) {
@@ -82531,7 +83410,7 @@ Applying switchroom config...
82531
83410
  writeErr(formatDivergentRecoveryMessage(migrationResult.details));
82532
83411
  process.exit(4);
82533
83412
  }
82534
- const postMigrationInspect = inspectVaultLayout(homedir43());
83413
+ const postMigrationInspect = inspectVaultLayout(homedir44());
82535
83414
  const acceptable = [
82536
83415
  "no-vault",
82537
83416
  "already-migrated",
@@ -82546,7 +83425,7 @@ Applying switchroom config...
82546
83425
  `));
82547
83426
  process.exit(5);
82548
83427
  }
82549
- const vaultDir = resolveVaultBindMountDir(homedir43(), {
83428
+ const vaultDir = resolveVaultBindMountDir(homedir44(), {
82550
83429
  migrationKind: migrationResult.kind,
82551
83430
  customVaultPath
82552
83431
  });
@@ -82585,7 +83464,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
82585
83464
  writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
82586
83465
  `));
82587
83466
  if (process.geteuid?.() === 0 && operatorUid !== undefined) {
82588
- const restored = restoreOperatorOwnership(homedir43(), operatorUid);
83467
+ const restored = restoreOperatorOwnership(homedir44(), operatorUid);
82589
83468
  if (restored.length > 0) {
82590
83469
  writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
82591
83470
  `));
@@ -82655,7 +83534,7 @@ function findUnwritableAgentDirs(config, opts) {
82655
83534
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
82656
83535
  const unwritable = [];
82657
83536
  for (const name of targets) {
82658
- const startSh = join74(agentsDir, name, "start.sh");
83537
+ const startSh = join75(agentsDir, name, "start.sh");
82659
83538
  if (!existsSync74(startSh))
82660
83539
  continue;
82661
83540
  try {
@@ -82835,8 +83714,8 @@ function runRedactStdin() {
82835
83714
 
82836
83715
  // src/cli/status-ask.ts
82837
83716
  import { readFileSync as readFileSync64, existsSync as existsSync75, readdirSync as readdirSync27 } from "node:fs";
82838
- import { join as join75 } from "node:path";
82839
- import { homedir as homedir44 } from "node:os";
83717
+ import { join as join76 } from "node:path";
83718
+ import { homedir as homedir45 } from "node:os";
82840
83719
 
82841
83720
  // src/status-ask/report.ts
82842
83721
  function parseJsonl(content) {
@@ -83171,7 +84050,7 @@ function resolveSources(explicitPath) {
83171
84050
  const config = loadConfig();
83172
84051
  agentsDir = resolveAgentsDir(config);
83173
84052
  } catch {
83174
- agentsDir = join75(homedir44(), ".switchroom", "agents");
84053
+ agentsDir = join76(homedir45(), ".switchroom", "agents");
83175
84054
  }
83176
84055
  if (!existsSync75(agentsDir))
83177
84056
  return [];
@@ -83183,7 +84062,7 @@ function resolveSources(explicitPath) {
83183
84062
  return [];
83184
84063
  }
83185
84064
  for (const name of entries) {
83186
- const path8 = join75(agentsDir, name, "runtime-metrics.jsonl");
84065
+ const path8 = join76(agentsDir, name, "runtime-metrics.jsonl");
83187
84066
  if (existsSync75(path8)) {
83188
84067
  sources.push({ path: path8, agent: name });
83189
84068
  }
@@ -83221,21 +84100,21 @@ import {
83221
84100
  unlinkSync as unlinkSync14,
83222
84101
  writeSync as writeSync8
83223
84102
  } from "node:fs";
83224
- import { join as join76, resolve as resolve46 } from "node:path";
84103
+ import { join as join77, resolve as resolve46 } from "node:path";
83225
84104
  var STAGING_SUBDIR = ".staging";
83226
84105
  function overlayPathsFor(agent, opts = {}) {
83227
84106
  const base = opts.root ? resolve46(opts.root, agent) : resolve46(resolveDualPath(`~/.switchroom/agents/${agent}`));
83228
- const scheduleDir = join76(base, "schedule.d");
83229
- const scheduleStagingDir = join76(scheduleDir, STAGING_SUBDIR);
83230
- const skillsDir = join76(base, "skills.d");
83231
- const skillsStagingDir = join76(skillsDir, STAGING_SUBDIR);
84107
+ const scheduleDir = join77(base, "schedule.d");
84108
+ const scheduleStagingDir = join77(scheduleDir, STAGING_SUBDIR);
84109
+ const skillsDir = join77(base, "skills.d");
84110
+ const skillsStagingDir = join77(skillsDir, STAGING_SUBDIR);
83232
84111
  return {
83233
84112
  agentRoot: base,
83234
84113
  scheduleDir,
83235
84114
  scheduleStagingDir,
83236
84115
  skillsDir,
83237
84116
  skillsStagingDir,
83238
- lockPath: join76(base, ".lock"),
84117
+ lockPath: join77(base, ".lock"),
83239
84118
  stagingDir: scheduleStagingDir
83240
84119
  };
83241
84120
  }
@@ -83289,8 +84168,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
83289
84168
  const paths = overlayPathsFor(agent, opts);
83290
84169
  return withAgentLock(paths, () => {
83291
84170
  ensureDirs(paths);
83292
- const stagingPath = join76(paths.scheduleStagingDir, `${slug}.yaml`);
83293
- const finalPath = join76(paths.scheduleDir, `${slug}.yaml`);
84171
+ const stagingPath = join77(paths.scheduleStagingDir, `${slug}.yaml`);
84172
+ const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
83294
84173
  const fd = openSync13(stagingPath, "w", 384);
83295
84174
  try {
83296
84175
  writeSync8(fd, yamlText);
@@ -83306,8 +84185,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
83306
84185
  const paths = overlayPathsFor(agent, opts);
83307
84186
  return withAgentLock(paths, () => {
83308
84187
  ensureSkillsDirs(paths);
83309
- const stagingPath = join76(paths.skillsStagingDir, `${slug}.yaml`);
83310
- const finalPath = join76(paths.skillsDir, `${slug}.yaml`);
84188
+ const stagingPath = join77(paths.skillsStagingDir, `${slug}.yaml`);
84189
+ const finalPath = join77(paths.skillsDir, `${slug}.yaml`);
83311
84190
  const fd = openSync13(stagingPath, "w", 384);
83312
84191
  try {
83313
84192
  writeSync8(fd, yamlText);
@@ -83322,7 +84201,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
83322
84201
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
83323
84202
  const paths = overlayPathsFor(agent, opts);
83324
84203
  return withAgentLock(paths, () => {
83325
- const finalPath = join76(paths.skillsDir, `${slug}.yaml`);
84204
+ const finalPath = join77(paths.skillsDir, `${slug}.yaml`);
83326
84205
  if (!existsSync76(finalPath))
83327
84206
  return false;
83328
84207
  unlinkSync14(finalPath);
@@ -83337,7 +84216,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
83337
84216
  for (const name of readdirSync28(paths.skillsDir)) {
83338
84217
  if (!/\.ya?ml$/i.test(name))
83339
84218
  continue;
83340
- const full = join76(paths.skillsDir, name);
84219
+ const full = join77(paths.skillsDir, name);
83341
84220
  try {
83342
84221
  const raw = readFileSync65(full, "utf-8");
83343
84222
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -83349,7 +84228,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
83349
84228
  function deleteOverlayEntry(agent, slug, opts = {}) {
83350
84229
  const paths = overlayPathsFor(agent, opts);
83351
84230
  return withAgentLock(paths, () => {
83352
- const finalPath = join76(paths.scheduleDir, `${slug}.yaml`);
84231
+ const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
83353
84232
  if (!existsSync76(finalPath))
83354
84233
  return false;
83355
84234
  unlinkSync14(finalPath);
@@ -83364,7 +84243,7 @@ function listOverlayEntries(agent, opts = {}) {
83364
84243
  for (const name of readdirSync28(paths.scheduleDir)) {
83365
84244
  if (!/\.ya?ml$/i.test(name))
83366
84245
  continue;
83367
- const full = join76(paths.scheduleDir, name);
84246
+ const full = join77(paths.scheduleDir, name);
83368
84247
  try {
83369
84248
  const raw = readFileSync65(full, "utf-8");
83370
84249
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -83520,12 +84399,12 @@ import {
83520
84399
  writeFileSync as writeFileSync38,
83521
84400
  writeSync as writeSync9
83522
84401
  } from "node:fs";
83523
- import { join as join77 } from "node:path";
84402
+ import { join as join78 } from "node:path";
83524
84403
  import { randomBytes as randomBytes14 } from "node:crypto";
83525
84404
  var STAGE_ID_PREFIX = "cap_";
83526
84405
  function pendingDir(agent, opts = {}) {
83527
84406
  const paths = overlayPathsFor(agent, opts);
83528
- return join77(paths.scheduleDir, ".pending");
84407
+ return join78(paths.scheduleDir, ".pending");
83529
84408
  }
83530
84409
  function ensurePendingDir(agent, opts = {}) {
83531
84410
  const dir = pendingDir(agent, opts);
@@ -83538,8 +84417,8 @@ function newStageId() {
83538
84417
  function stagePendingScheduleEntry(opts) {
83539
84418
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
83540
84419
  const stageId = opts.stageId ?? newStageId();
83541
- const yamlPath = join77(dir, `${stageId}.yaml`);
83542
- const metaPath = join77(dir, `${stageId}.meta.json`);
84420
+ const yamlPath = join78(dir, `${stageId}.yaml`);
84421
+ const metaPath = join78(dir, `${stageId}.meta.json`);
83543
84422
  const meta = {
83544
84423
  v: 1,
83545
84424
  stage_id: stageId,
@@ -83573,8 +84452,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
83573
84452
  if (!name.endsWith(".meta.json"))
83574
84453
  continue;
83575
84454
  const stageId = name.slice(0, -".meta.json".length);
83576
- const metaPath = join77(dir, name);
83577
- const yamlPath = join77(dir, `${stageId}.yaml`);
84455
+ const metaPath = join78(dir, name);
84456
+ const yamlPath = join78(dir, `${stageId}.yaml`);
83578
84457
  if (!existsSync77(yamlPath))
83579
84458
  continue;
83580
84459
  try {
@@ -83593,7 +84472,7 @@ function commitPendingScheduleEntry(opts) {
83593
84472
  return { committed: false, reason: "not_found" };
83594
84473
  const slug = match.meta.entry.name ?? match.stageId;
83595
84474
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
83596
- const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
84475
+ const finalPath = join78(paths.scheduleDir, `${slug}.yaml`);
83597
84476
  if (existsSync77(finalPath)) {
83598
84477
  return { committed: false, reason: "slug_collision" };
83599
84478
  }
@@ -84226,7 +85105,7 @@ var import_yaml21 = __toESM(require_dist(), 1);
84226
85105
  import { existsSync as existsSync79 } from "node:fs";
84227
85106
  init_reconcile_default_skills();
84228
85107
  var import_yaml22 = __toESM(require_dist(), 1);
84229
- import { join as join78 } from "node:path";
85108
+ import { join as join79 } from "node:path";
84230
85109
  var MAX_SKILLS_PER_AGENT = 20;
84231
85110
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
84232
85111
  function exitCodeFor2(code) {
@@ -84301,7 +85180,7 @@ function skillInstall(opts) {
84301
85180
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
84302
85181
  }
84303
85182
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
84304
- const skillPath = join78(poolDir, skillName);
85183
+ const skillPath = join79(poolDir, skillName);
84305
85184
  if (!existsSync79(skillPath)) {
84306
85185
  return err("E_SKILL_NOT_FOUND", `bundled skill not found at ${skillPath}. The operator needs to ` + `place the skill at this path before the agent can opt in.`);
84307
85186
  }
@@ -84479,8 +85358,8 @@ import {
84479
85358
  statSync as statSync32,
84480
85359
  writeFileSync as writeFileSync39
84481
85360
  } from "node:fs";
84482
- import { tmpdir as tmpdir5, homedir as homedir45 } from "node:os";
84483
- import { dirname as dirname23, join as join79, relative as relative2, resolve as resolve47 } from "node:path";
85361
+ import { tmpdir as tmpdir5, homedir as homedir46 } from "node:os";
85362
+ import { dirname as dirname23, join as join80, relative as relative2, resolve as resolve47 } from "node:path";
84484
85363
  import { spawnSync as spawnSync12 } from "node:child_process";
84485
85364
 
84486
85365
  // src/cli/skill-common.ts
@@ -84674,10 +85553,10 @@ function scanForClaudeP2(content) {
84674
85553
  function resolveSkillsPoolDir2(override) {
84675
85554
  const raw = override ?? "~/.switchroom/skills";
84676
85555
  if (raw.startsWith("~/")) {
84677
- return join79(homedir45(), raw.slice(2));
85556
+ return join80(homedir46(), raw.slice(2));
84678
85557
  }
84679
85558
  if (raw === "~")
84680
- return homedir45();
85559
+ return homedir46();
84681
85560
  return resolve47(raw);
84682
85561
  }
84683
85562
  function readStdinSync() {
@@ -84713,7 +85592,7 @@ function loadFromDir(dir) {
84713
85592
  const walk2 = (sub) => {
84714
85593
  const entries = readdirSync30(sub, { withFileTypes: true });
84715
85594
  for (const ent of entries) {
84716
- const full = join79(sub, ent.name);
85595
+ const full = join80(sub, ent.name);
84717
85596
  const rel = relative2(abs, full);
84718
85597
  if (ent.isSymbolicLink()) {
84719
85598
  fail3(`refusing to read symlink inside --from dir: ${rel}`);
@@ -84748,7 +85627,7 @@ function loadFromTarball(tarPath) {
84748
85627
  fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
84749
85628
  }
84750
85629
  }
84751
- const staging = mkdtempSync5(join79(tmpdir5(), "skill-apply-extract-"));
85630
+ const staging = mkdtempSync5(join80(tmpdir5(), "skill-apply-extract-"));
84752
85631
  try {
84753
85632
  const flags = isGz ? ["-xzf"] : ["-xf"];
84754
85633
  const r = spawnSync12("tar", [
@@ -84834,8 +85713,8 @@ function validatePayload(name, files) {
84834
85713
  errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
84835
85714
  }
84836
85715
  } else if (PY_SCRIPT_RE2.test(path8)) {
84837
- const tmp = mkdtempSync5(join79(tmpdir5(), "skill-apply-py-"));
84838
- const tmpPy = join79(tmp, "check.py");
85716
+ const tmp = mkdtempSync5(join80(tmpdir5(), "skill-apply-py-"));
85717
+ const tmpPy = join80(tmp, "check.py");
84839
85718
  try {
84840
85719
  writeFileSync39(tmpPy, content);
84841
85720
  const r = spawnSync12("python3", ["-m", "py_compile", tmpPy], {
@@ -84858,7 +85737,7 @@ function diffSummary(currentDir, files) {
84858
85737
  if (existsSync80(currentDir)) {
84859
85738
  const walk2 = (sub) => {
84860
85739
  for (const ent of readdirSync30(sub, { withFileTypes: true })) {
84861
- const full = join79(sub, ent.name);
85740
+ const full = join80(sub, ent.name);
84862
85741
  const rel = relative2(currentDir, full);
84863
85742
  if (ent.isDirectory()) {
84864
85743
  walk2(full);
@@ -84894,7 +85773,7 @@ function writePayload(poolDir, name, files) {
84894
85773
  if (!existsSync80(poolDir)) {
84895
85774
  mkdirSync45(poolDir, { recursive: true, mode: 493 });
84896
85775
  }
84897
- const target = join79(poolDir, name);
85776
+ const target = join80(poolDir, name);
84898
85777
  let targetIsSymlink = false;
84899
85778
  try {
84900
85779
  const st = lstatSync8(target);
@@ -84905,11 +85784,11 @@ function writePayload(poolDir, name, files) {
84905
85784
  if (targetIsSymlink) {
84906
85785
  fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
84907
85786
  }
84908
- const staging = mkdtempSync5(join79(poolDir, `.skill-apply-stage-${name}-`));
85787
+ const staging = mkdtempSync5(join80(poolDir, `.skill-apply-stage-${name}-`));
84909
85788
  let oldRename = null;
84910
85789
  try {
84911
85790
  for (const [path8, content] of Object.entries(files)) {
84912
- const full = join79(staging, path8);
85791
+ const full = join80(staging, path8);
84913
85792
  mkdirSync45(dirname23(full), { recursive: true, mode: 493 });
84914
85793
  const fd = openSync15(full, "wx");
84915
85794
  try {
@@ -84987,7 +85866,7 @@ function registerSkillCommand(program3) {
84987
85866
  }
84988
85867
  const config = loadConfig();
84989
85868
  const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
84990
- const currentDir = join79(poolDir, name);
85869
+ const currentDir = join80(poolDir, name);
84991
85870
  console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
84992
85871
  console.log(source_default.bold("Diff vs current pool content:"));
84993
85872
  console.log(diffSummary(currentDir, files));
@@ -85031,8 +85910,8 @@ import {
85031
85910
  utimesSync,
85032
85911
  writeFileSync as writeFileSync40
85033
85912
  } from "node:fs";
85034
- import { dirname as dirname24, join as join80, relative as relative3, resolve as resolve48 } from "node:path";
85035
- import { homedir as homedir46, tmpdir as tmpdir6 } from "node:os";
85913
+ import { dirname as dirname24, join as join81, relative as relative3, resolve as resolve48 } from "node:path";
85914
+ import { homedir as homedir47, tmpdir as tmpdir6 } from "node:os";
85036
85915
  import { spawnSync as spawnSync13 } from "node:child_process";
85037
85916
  init_helpers();
85038
85917
  init_source();
@@ -85042,10 +85921,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
85042
85921
  var PERSONAL_SKILLS_SUBPATH = "personal-skills";
85043
85922
  function resolveConfigSkillsDir(agent) {
85044
85923
  const override = process.env.SWITCHROOM_CONFIG_DIR;
85045
- const candidate = override ? resolve48(override) : join80(homedir46(), ".switchroom-config");
85924
+ const candidate = override ? resolve48(override) : join81(homedir47(), ".switchroom-config");
85046
85925
  if (!existsSync81(candidate))
85047
85926
  return null;
85048
- return join80(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
85927
+ return join81(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
85049
85928
  }
85050
85929
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
85051
85930
  function sweepMirrorPriors(configSkillsRoot) {
@@ -85063,7 +85942,7 @@ function sweepMirrorPriors(configSkillsRoot) {
85063
85942
  if (now - ts < MIRROR_PRIOR_TTL_MS)
85064
85943
  continue;
85065
85944
  try {
85066
- rmSync17(join80(configSkillsRoot, ent), { recursive: true, force: true });
85945
+ rmSync17(join81(configSkillsRoot, ent), { recursive: true, force: true });
85067
85946
  } catch {}
85068
85947
  }
85069
85948
  } catch {}
@@ -85072,7 +85951,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
85072
85951
  const configSkillsRoot = resolveConfigSkillsDir(agent);
85073
85952
  if (!configSkillsRoot)
85074
85953
  return;
85075
- const dest = join80(configSkillsRoot, name);
85954
+ const dest = join81(configSkillsRoot, name);
85076
85955
  try {
85077
85956
  if (liveSkillDir !== null) {
85078
85957
  try {
@@ -85087,19 +85966,19 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
85087
85966
  if (liveSkillDir === null) {
85088
85967
  sweepMirrorPriors(configSkillsRoot);
85089
85968
  if (existsSync81(dest)) {
85090
- const trash = join80(configSkillsRoot, `.${name}-trash-${Date.now()}`);
85969
+ const trash = join81(configSkillsRoot, `.${name}-trash-${Date.now()}`);
85091
85970
  renameSync18(dest, trash);
85092
85971
  }
85093
85972
  return;
85094
85973
  }
85095
85974
  mkdirSync46(configSkillsRoot, { recursive: true, mode: 493 });
85096
85975
  sweepMirrorPriors(configSkillsRoot);
85097
- const staging = mkdtempSync6(join80(configSkillsRoot, `.${name}-staging-`));
85976
+ const staging = mkdtempSync6(join81(configSkillsRoot, `.${name}-staging-`));
85098
85977
  const walk2 = (src, dst) => {
85099
85978
  mkdirSync46(dst, { recursive: true, mode: 493 });
85100
85979
  for (const ent of readdirSync31(src, { withFileTypes: true })) {
85101
- const s = join80(src, ent.name);
85102
- const d = join80(dst, ent.name);
85980
+ const s = join81(src, ent.name);
85981
+ const d = join81(dst, ent.name);
85103
85982
  if (ent.isSymbolicLink())
85104
85983
  continue;
85105
85984
  if (ent.isDirectory())
@@ -85111,7 +85990,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
85111
85990
  };
85112
85991
  walk2(liveSkillDir, staging);
85113
85992
  if (existsSync81(dest)) {
85114
- const prior = join80(configSkillsRoot, `.${name}-prior-${Date.now()}`);
85993
+ const prior = join81(configSkillsRoot, `.${name}-prior-${Date.now()}`);
85115
85994
  renameSync18(dest, prior);
85116
85995
  }
85117
85996
  renameSync18(staging, dest);
@@ -85138,13 +86017,13 @@ function resolveAgent(opts) {
85138
86017
  function resolveAgentsRoot(opts) {
85139
86018
  if (opts.root)
85140
86019
  return resolve48(opts.root);
85141
- return join80(homedir46(), ".switchroom", "agents");
86020
+ return join81(homedir47(), ".switchroom", "agents");
85142
86021
  }
85143
86022
  function personalSkillDir(agentsRoot, agent, name) {
85144
- return join80(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
86023
+ return join81(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
85145
86024
  }
85146
86025
  function trashDir(agentsRoot, agent) {
85147
- return join80(agentsRoot, agent, ".claude", TRASH_DIRNAME);
86026
+ return join81(agentsRoot, agent, ".claude", TRASH_DIRNAME);
85148
86027
  }
85149
86028
  function readStdinSync2() {
85150
86029
  const chunks = [];
@@ -85174,7 +86053,7 @@ function loadFromDir2(dir) {
85174
86053
  const files = {};
85175
86054
  const walk2 = (sub) => {
85176
86055
  for (const ent of readdirSync31(sub, { withFileTypes: true })) {
85177
- const full = join80(sub, ent.name);
86056
+ const full = join81(sub, ent.name);
85178
86057
  if (ent.isSymbolicLink()) {
85179
86058
  fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
85180
86059
  }
@@ -85227,8 +86106,8 @@ function behavioralValidate(files) {
85227
86106
  errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
85228
86107
  }
85229
86108
  } else if (PY_SCRIPT_RE.test(path8)) {
85230
- const tmp = mkdtempSync6(join80(tmpdir6(), "skill-personal-py-"));
85231
- const tmpPy = join80(tmp, "check.py");
86109
+ const tmp = mkdtempSync6(join81(tmpdir6(), "skill-personal-py-"));
86110
+ const tmpPy = join81(tmp, "check.py");
85232
86111
  try {
85233
86112
  writeFileSync40(tmpPy, content);
85234
86113
  const r = spawnSync13("python3", ["-m", "py_compile", tmpPy], {
@@ -85252,7 +86131,7 @@ function sweepTrash(agentsRoot, agent) {
85252
86131
  for (const ent of readdirSync31(trash, { withFileTypes: true })) {
85253
86132
  if (!ent.isDirectory())
85254
86133
  continue;
85255
- const entPath = join80(trash, ent.name);
86134
+ const entPath = join81(trash, ent.name);
85256
86135
  try {
85257
86136
  const st = statSync33(entPath);
85258
86137
  if (now - st.mtimeMs > TRASH_TTL_MS) {
@@ -85273,11 +86152,11 @@ function writePersonalSkill(targetDir, files) {
85273
86152
  fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
85274
86153
  }
85275
86154
  mkdirSync46(dirname24(targetDir), { recursive: true, mode: 493 });
85276
- const staging = mkdtempSync6(join80(dirname24(targetDir), `.skill-personal-stage-`));
86155
+ const staging = mkdtempSync6(join81(dirname24(targetDir), `.skill-personal-stage-`));
85277
86156
  let oldRename = null;
85278
86157
  try {
85279
86158
  for (const [path8, content] of Object.entries(files)) {
85280
- const full = join80(staging, path8);
86159
+ const full = join81(staging, path8);
85281
86160
  mkdirSync46(dirname24(full), { recursive: true, mode: 493 });
85282
86161
  const fd = openSync16(full, "wx");
85283
86162
  try {
@@ -85410,10 +86289,10 @@ function editPersonalAction(name, opts) {
85410
86289
  }
85411
86290
  var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
85412
86291
  function defaultSharedRoot() {
85413
- return join80(homedir46(), ".switchroom", "skills");
86292
+ return join81(homedir47(), ".switchroom", "skills");
85414
86293
  }
85415
86294
  function defaultBundledRoot() {
85416
- return join80(homedir46(), ".switchroom", "skills", "_bundled");
86295
+ return join81(homedir47(), ".switchroom", "skills", "_bundled");
85417
86296
  }
85418
86297
  function resolveCloneSource(source, opts) {
85419
86298
  const m = CLONE_SOURCE_RE.exec(source);
@@ -85423,7 +86302,7 @@ function resolveCloneSource(source, opts) {
85423
86302
  const tier = m[1];
85424
86303
  const slug = m[2];
85425
86304
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
85426
- const dir = join80(root, slug);
86305
+ const dir = join81(root, slug);
85427
86306
  if (!existsSync81(dir)) {
85428
86307
  fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
85429
86308
  }
@@ -85439,7 +86318,7 @@ function readSourceFiles(dir) {
85439
86318
  const skipped = [];
85440
86319
  const walk2 = (sub) => {
85441
86320
  for (const ent of readdirSync31(sub, { withFileTypes: true })) {
85442
- const full = join80(sub, ent.name);
86321
+ const full = join81(sub, ent.name);
85443
86322
  if (ent.isSymbolicLink()) {
85444
86323
  continue;
85445
86324
  }
@@ -85550,7 +86429,7 @@ function removePersonalAction(name, opts) {
85550
86429
  const trashRoot = trashDir(agentsRoot, agent);
85551
86430
  mkdirSync46(trashRoot, { recursive: true, mode: 493 });
85552
86431
  const ts = Date.now();
85553
- const trashTarget = join80(trashRoot, `${name}-${ts}`);
86432
+ const trashTarget = join81(trashRoot, `${name}-${ts}`);
85554
86433
  renameSync18(target, trashTarget);
85555
86434
  const now = new Date(ts);
85556
86435
  utimesSync(trashTarget, now, now);
@@ -85569,7 +86448,7 @@ function listPersonalAction(opts) {
85569
86448
  const agent = resolveAgent(opts);
85570
86449
  const agentsRoot = resolveAgentsRoot(opts);
85571
86450
  sweepTrash(agentsRoot, agent);
85572
- const skillsDir = join80(agentsRoot, agent, ".claude", "skills");
86451
+ const skillsDir = join81(agentsRoot, agent, ".claude", "skills");
85573
86452
  const personal = [];
85574
86453
  if (existsSync81(skillsDir)) {
85575
86454
  for (const ent of readdirSync31(skillsDir, { withFileTypes: true })) {
@@ -85578,7 +86457,7 @@ function listPersonalAction(opts) {
85578
86457
  if (!ent.name.startsWith(PERSONAL_PREFIX))
85579
86458
  continue;
85580
86459
  const skillName = ent.name.slice(PERSONAL_PREFIX.length);
85581
- const skillPath = join80(skillsDir, ent.name);
86460
+ const skillPath = join81(skillsDir, ent.name);
85582
86461
  let fileCount = 0;
85583
86462
  let totalBytes = 0;
85584
86463
  const walk2 = (sub) => {
@@ -85586,10 +86465,10 @@ function listPersonalAction(opts) {
85586
86465
  if (e.isFile()) {
85587
86466
  fileCount += 1;
85588
86467
  try {
85589
- totalBytes += statSync33(join80(sub, e.name)).size;
86468
+ totalBytes += statSync33(join81(sub, e.name)).size;
85590
86469
  } catch {}
85591
86470
  } else if (e.isDirectory()) {
85592
- walk2(join80(sub, e.name));
86471
+ walk2(join81(sub, e.name));
85593
86472
  }
85594
86473
  }
85595
86474
  };
@@ -85629,22 +86508,22 @@ function registerSkillPersonalCommands(program3) {
85629
86508
  init_helpers();
85630
86509
  var import_yaml24 = __toESM(require_dist(), 1);
85631
86510
  import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync70, statSync as statSync34 } from "node:fs";
85632
- import { homedir as homedir47 } from "node:os";
85633
- import { join as join81, resolve as resolve49 } from "node:path";
86511
+ import { homedir as homedir48 } from "node:os";
86512
+ import { join as join82, resolve as resolve49 } from "node:path";
85634
86513
  var PERSONAL_PREFIX2 = "personal-";
85635
86514
  var BUNDLED_SUBDIR = "_bundled";
85636
86515
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
85637
86516
  function defaultAgentsRoot() {
85638
- return resolve49(homedir47(), ".switchroom/agents");
86517
+ return resolve49(homedir48(), ".switchroom/agents");
85639
86518
  }
85640
86519
  function defaultSharedRoot2() {
85641
- return resolve49(homedir47(), ".switchroom/skills");
86520
+ return resolve49(homedir48(), ".switchroom/skills");
85642
86521
  }
85643
86522
  function defaultBundledRoot2() {
85644
- return resolve49(homedir47(), ".switchroom/skills/_bundled");
86523
+ return resolve49(homedir48(), ".switchroom/skills/_bundled");
85645
86524
  }
85646
86525
  function readSkillFrontmatter(skillDir) {
85647
- const mdPath = join81(skillDir, "SKILL.md");
86526
+ const mdPath = join82(skillDir, "SKILL.md");
85648
86527
  if (!existsSync82(mdPath))
85649
86528
  return null;
85650
86529
  let content;
@@ -85677,7 +86556,7 @@ function readSkillFrontmatter(skillDir) {
85677
86556
  return { fm: parsed };
85678
86557
  }
85679
86558
  function statSkillMd(skillDir) {
85680
- const mdPath = join81(skillDir, "SKILL.md");
86559
+ const mdPath = join82(skillDir, "SKILL.md");
85681
86560
  try {
85682
86561
  const st = statSync34(mdPath);
85683
86562
  return { size: st.size, mtime: st.mtime.toISOString() };
@@ -85688,7 +86567,7 @@ function statSkillMd(skillDir) {
85688
86567
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
85689
86568
  if (!AGENT_NAME_RE3.test(agent))
85690
86569
  return [];
85691
- const skillsDir = join81(agentsRoot, agent, ".claude/skills");
86570
+ const skillsDir = join82(agentsRoot, agent, ".claude/skills");
85692
86571
  if (!existsSync82(skillsDir))
85693
86572
  return [];
85694
86573
  const out = [];
@@ -85701,7 +86580,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
85701
86580
  for (const ent of entries) {
85702
86581
  if (!ent.startsWith(PERSONAL_PREFIX2))
85703
86582
  continue;
85704
- const dirPath = join81(skillsDir, ent);
86583
+ const dirPath = join82(skillsDir, ent);
85705
86584
  try {
85706
86585
  if (!statSync34(dirPath).isDirectory())
85707
86586
  continue;
@@ -85741,7 +86620,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
85741
86620
  continue;
85742
86621
  if (ent.startsWith("."))
85743
86622
  continue;
85744
- const dirPath = join81(sharedRoot, ent);
86623
+ const dirPath = join82(sharedRoot, ent);
85745
86624
  try {
85746
86625
  if (!statSync34(dirPath).isDirectory())
85747
86626
  continue;
@@ -85777,7 +86656,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
85777
86656
  for (const ent of entries) {
85778
86657
  if (ent.startsWith("."))
85779
86658
  continue;
85780
- const dirPath = join81(bundledRoot, ent);
86659
+ const dirPath = join82(bundledRoot, ent);
85781
86660
  try {
85782
86661
  if (!statSync34(dirPath).isDirectory())
85783
86662
  continue;
@@ -85922,8 +86801,8 @@ function registerHostdMcpCommand(program3) {
85922
86801
  init_source();
85923
86802
  init_helpers();
85924
86803
  import { existsSync as existsSync84, mkdirSync as mkdirSync47, readdirSync as readdirSync33, readFileSync as readFileSync72, writeFileSync as writeFileSync41, statSync as statSync35, copyFileSync as copyFileSync12 } from "node:fs";
85925
- import { homedir as homedir48 } from "node:os";
85926
- import { join as join82 } from "node:path";
86804
+ import { homedir as homedir49 } from "node:os";
86805
+ import { join as join83 } from "node:path";
85927
86806
  import { spawnSync as spawnSync15 } from "node:child_process";
85928
86807
  init_audit_reader();
85929
86808
  function resolveHostdImageTag(explicitTag, release) {
@@ -86036,7 +86915,7 @@ networks:
86036
86915
  # operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
86037
86916
  `;
86038
86917
  }
86039
- function resolveHostdHostHome(env2 = process.env, home2 = homedir48()) {
86918
+ function resolveHostdHostHome(env2 = process.env, home2 = homedir49()) {
86040
86919
  const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
86041
86920
  const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
86042
86921
  if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
@@ -86047,10 +86926,10 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir48()) {
86047
86926
  return resolved;
86048
86927
  }
86049
86928
  function hostdDir() {
86050
- return join82(homedir48(), ".switchroom", "hostd");
86929
+ return join83(homedir49(), ".switchroom", "hostd");
86051
86930
  }
86052
86931
  function hostdComposePath() {
86053
- return join82(hostdDir(), "docker-compose.yml");
86932
+ return join83(hostdDir(), "docker-compose.yml");
86054
86933
  }
86055
86934
  function backupExistingCompose() {
86056
86935
  const p = hostdComposePath();
@@ -86161,7 +87040,7 @@ function doStatus() {
86161
87040
  for (const name of readdirSync33(dir)) {
86162
87041
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
86163
87042
  continue;
86164
- const sockPath = join82(dir, name, "sock");
87043
+ const sockPath = join83(dir, name, "sock");
86165
87044
  if (existsSync84(sockPath)) {
86166
87045
  const st = statSync35(sockPath);
86167
87046
  if ((st.mode & 61440) === 49152) {
@@ -86253,8 +87132,8 @@ The log is created when hostd handles its first privileged-verb request.`));
86253
87132
  init_source();
86254
87133
  init_helpers();
86255
87134
  import { existsSync as existsSync85, mkdirSync as mkdirSync48, writeFileSync as writeFileSync42, copyFileSync as copyFileSync13 } from "node:fs";
86256
- import { homedir as homedir49 } from "node:os";
86257
- import { join as join83 } from "node:path";
87135
+ import { homedir as homedir50 } from "node:os";
87136
+ import { join as join84 } from "node:path";
86258
87137
  import { spawnSync as spawnSync16 } from "node:child_process";
86259
87138
  function resolveWebImageTag(explicitTag, release) {
86260
87139
  if (explicitTag)
@@ -86339,10 +87218,10 @@ services:
86339
87218
  `;
86340
87219
  }
86341
87220
  function webdDir() {
86342
- return join83(homedir49(), ".switchroom", "web");
87221
+ return join84(homedir50(), ".switchroom", "web");
86343
87222
  }
86344
87223
  function webdComposePath() {
86345
- return join83(webdDir(), "docker-compose.yml");
87224
+ return join84(webdDir(), "docker-compose.yml");
86346
87225
  }
86347
87226
  function backupExistingCompose2() {
86348
87227
  const p = webdComposePath();
@@ -86375,7 +87254,7 @@ async function doInstall2(opts, program3) {
86375
87254
  const cfg = getConfig(program3);
86376
87255
  const imageTag = resolveWebImageTag(opts.tag, cfg.release);
86377
87256
  const yaml = renderWebComposeFile({
86378
- hostHome: homedir49(),
87257
+ hostHome: homedir50(),
86379
87258
  imageTag,
86380
87259
  operatorUid
86381
87260
  });