switchroom 0.15.26 → 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.26";
50481
- var COMMIT_SHA = "497e0d23";
50497
+ var VERSION = "0.15.27";
50498
+ var COMMIT_SHA = "97160057";
50482
50499
 
50483
50500
  // src/cli/agent.ts
50484
50501
  init_source();
@@ -68874,7 +68891,6 @@ init_source();
68874
68891
  init_merge();
68875
68892
  init_loader();
68876
68893
  init_client();
68877
- init_lifecycle();
68878
68894
  import {
68879
68895
  readFileSync as readFileSync46,
68880
68896
  existsSync as existsSync50,
@@ -68885,9 +68901,8 @@ import {
68885
68901
  writeSync as writeSync6,
68886
68902
  constants as fsConstants3
68887
68903
  } from "node:fs";
68888
- import { resolve as resolve28, extname, join as join46, relative, dirname as dirname10 } from "node:path";
68889
- import { homedir as homedir25 } from "node:os";
68890
- 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";
68891
68906
  import { timingSafeEqual as timingSafeEqual3, randomBytes as randomBytes11 } from "node:crypto";
68892
68907
 
68893
68908
  // src/web/api.ts
@@ -68896,7 +68911,71 @@ init_manager();
68896
68911
  init_hindsight();
68897
68912
  import { spawnSync as spawnSync5 } from "node:child_process";
68898
68913
  import { existsSync as existsSync47, readFileSync as readFileSync43, statSync as statSync22 } from "node:fs";
68899
- 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
68900
68979
  init_audit_reader();
68901
68980
 
68902
68981
  // src/scheduler/dispatch.ts
@@ -68959,6 +69038,8 @@ function readRecentFires(jsonlPath) {
68959
69038
 
68960
69039
  // src/web/api.ts
68961
69040
  init_client3();
69041
+ init_client();
69042
+ import { homedir as homedir23 } from "node:os";
68962
69043
 
68963
69044
  // node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
68964
69045
  import { dirname as dirname8, posix, sep as sep2 } from "path";
@@ -73694,6 +73775,121 @@ async function proposeConfigEditViaHostd(args) {
73694
73775
  }
73695
73776
  }
73696
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
+
73697
73893
  // src/web/api.ts
73698
73894
  import { randomUUID as randomUUID4 } from "node:crypto";
73699
73895
 
@@ -73925,7 +74121,78 @@ function listSubagents(db, opts = {}) {
73925
74121
  return rows.map(mapSubagentRow);
73926
74122
  }
73927
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
+
73928
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
+ }
73929
74196
  function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now = Date.now()) {
73930
74197
  try {
73931
74198
  const f = resolve27(agentsDir, name, "telegram", ".bridge-alive");
@@ -73934,11 +74201,25 @@ function agentBridgeAlive(agentsDir, name, maxAgeMs = 30000, now = Date.now()) {
73934
74201
  return false;
73935
74202
  }
73936
74203
  }
73937
- 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;
73938
74217
  const statuses = getAllAgentStatuses(config);
73939
74218
  const authStatuses = getAllAuthStatuses(config);
73940
74219
  const agentsDir = resolveAgentsDir(config);
73941
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;
73942
74223
  for (const [name, agentConfig] of Object.entries(config.agents)) {
73943
74224
  const status = statuses[name];
73944
74225
  const auth = authStatuses[name];
@@ -73946,11 +74227,14 @@ function handleGetAgents(config) {
73946
74227
  const resolved = resolveAgentConfig(config.defaults, config.profiles, agentConfig);
73947
74228
  const primaryAccount = resolved.auth?.override ?? config.auth?.active;
73948
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;
73949
74233
  agents.push({
73950
74234
  name,
73951
74235
  active,
73952
- uptime: status?.uptime ?? null,
73953
- memory: status?.memory ?? null,
74236
+ uptime,
74237
+ memory,
73954
74238
  extends: agentConfig.extends ?? "default",
73955
74239
  topic_name: agentConfig.topic_name,
73956
74240
  topic_emoji: agentConfig.topic_emoji,
@@ -73961,54 +74245,40 @@ function handleGetAgents(config) {
73961
74245
  timeUntilExpiry: auth?.timeUntilExpiry,
73962
74246
  expiresAt: auth?.expiresAt
73963
74247
  },
73964
- memoryCollection: collection
74248
+ memoryCollection: collection,
74249
+ lastTurnAt: readLastTurn(agentsDir, name)
73965
74250
  });
73966
74251
  }
73967
74252
  return agents;
73968
74253
  }
73969
- function handleStartAgent(name) {
73970
- try {
73971
- startAgent(name);
73972
- captureEvent("agent_started", { agent: name, source: "web_api" });
73973
- return { ok: true };
73974
- } catch (err) {
73975
- captureException(err, { action: "start_agent", agent: name });
73976
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
73977
- }
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>"}\`.`;
73978
74256
  }
73979
- function handleStopAgent(name) {
73980
- try {
73981
- stopAgent(name);
73982
- captureEvent("agent_stopped", { agent: name, source: "web_api" });
73983
- return { ok: true };
73984
- } catch (err) {
73985
- captureException(err, { action: "stop_agent", agent: name });
73986
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
73987
- }
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 };
73988
74264
  }
73989
- function handleRestartAgent(name) {
74265
+ async function handleRestartAgent(name, deps = {}) {
74266
+ const restart = deps.restart ?? restartAgentViaHostd;
73990
74267
  try {
73991
- restartAgent(name);
73992
- captureEvent("agent_restarted", { agent: name, source: "web_api" });
73993
- 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 };
73994
74274
  } catch (err) {
73995
74275
  captureException(err, { action: "restart_agent", agent: name });
73996
74276
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
73997
74277
  }
73998
74278
  }
73999
- function handleGetLogs(name, lines = 50) {
74000
- const res = spawnSync5("docker", ["logs", "--tail", String(lines), containerName(name)], { encoding: "utf-8", timeout: 5000 });
74001
- if (res.error) {
74002
- return { ok: false, error: res.error.message };
74003
- }
74004
- if (res.status !== 0) {
74005
- const stderr = (res.stderr ?? "").trim();
74006
- return {
74007
- ok: false,
74008
- error: stderr || `docker logs exited ${res.status ?? "non-zero"}`
74009
- };
74010
- }
74011
- 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);
74012
74282
  }
74013
74283
  function handleGetTurns(config, agentName, limit) {
74014
74284
  try {
@@ -74052,6 +74322,31 @@ var quotaCache = new Map;
74052
74322
  function quotaEntryFresh(e, now) {
74053
74323
  return !!e && now - e.fetchedAt < QUOTA_CACHE_TTL_MS;
74054
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
+ }
74055
74350
  async function handleGetAccounts(config, home2) {
74056
74351
  const infos = getAccountInfos(Date.now(), home2);
74057
74352
  const brokerAccounts = new Map;
@@ -74078,12 +74373,15 @@ async function handleGetAccounts(config, home2) {
74078
74373
  usedBy.sort();
74079
74374
  }
74080
74375
  const now = Date.now();
74081
- const ce = quotaCache.get(info.label);
74376
+ const brokerState = brokerAccounts.get(info.label) ?? null;
74377
+ const view = deriveAccountQuotaView(brokerState, quotaCache.get(info.label), now);
74082
74378
  return {
74083
74379
  ...info,
74084
- quota: brokerAccounts.get(info.label) ?? null,
74085
- quotaUsage: quotaEntryFresh(ce, now) ? ce.usage ?? null : null,
74086
- 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,
74087
74385
  usedBy
74088
74386
  };
74089
74387
  });
@@ -74206,6 +74504,12 @@ function inspectEnv(container, keys) {
74206
74504
  } catch {}
74207
74505
  return out;
74208
74506
  }
74507
+ var HOSTD_NOISE_OPS = new Set([
74508
+ "agent_smoke",
74509
+ "get_status",
74510
+ "agent_status",
74511
+ "agent_logs"
74512
+ ]);
74209
74513
  async function handleGetSystemHealth(config, home2) {
74210
74514
  const broker = { reachable: false };
74211
74515
  try {
@@ -74257,7 +74561,7 @@ async function handleGetSystemHealth(config, home2) {
74257
74561
  if (existsSync47(logPath)) {
74258
74562
  hostd.auditLogPresent = true;
74259
74563
  const raw = readFileSync43(logPath, "utf-8");
74260
- hostd.recent = readAndFilter(raw, {}, 10);
74564
+ hostd.recent = readAndFilter(raw, {}, 1000).filter((e) => !HOSTD_NOISE_OPS.has(e.op)).slice(-10);
74261
74565
  }
74262
74566
  } catch (err) {
74263
74567
  hostd.error = err instanceof Error ? err.message : String(err);
@@ -74278,8 +74582,10 @@ async function handleGetGoogleAccounts(config) {
74278
74582
  }
74279
74583
  });
74280
74584
  } catch (err) {
74281
- if (!(err instanceof AuthBrokerUnreachableError))
74282
- 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
+ }
74283
74589
  }
74284
74590
  const cfgAccounts = config.google_accounts ?? {};
74285
74591
  const keys = new Set([
@@ -74316,8 +74622,10 @@ async function handleGetMicrosoftAccounts(config) {
74316
74622
  }
74317
74623
  });
74318
74624
  } catch (err) {
74319
- if (!(err instanceof AuthBrokerUnreachableError))
74320
- 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
+ }
74321
74629
  }
74322
74630
  const cfgAccounts = config.microsoft_accounts ?? {};
74323
74631
  const keys = new Set([
@@ -74522,7 +74830,16 @@ function handleSetConnectionAccess(configPath, config, args, deps = {}) {
74522
74830
  });
74523
74831
  return { ok: true, changed: true, requestId, pendingApproval: true };
74524
74832
  }
74525
- 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
+ }
74526
74843
  const entries = collectScheduleEntries(config);
74527
74844
  const agentsDir = resolveAgentsDir(config);
74528
74845
  const recentByAgent = {};
@@ -74532,7 +74849,12 @@ function handleGetSchedule(config) {
74532
74849
  if (rows.length > 0)
74533
74850
  recentByAgent[agent] = rows.slice(-10);
74534
74851
  }
74535
- 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
+ };
74536
74858
  }
74537
74859
  async function handleGetApprovals() {
74538
74860
  const opSock = resolveKernelOperatorSocket();
@@ -74554,6 +74876,48 @@ async function handleGetApprovals() {
74554
74876
  const sorted = [...decisions].sort((a, b) => b.granted_at - a.granted_at);
74555
74877
  return { reachable: true, decisions: sorted };
74556
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
+ }
74557
74921
  async function handleGetMemoryHealth(config, opts) {
74558
74922
  const url = config.memory?.config?.url ?? "http://127.0.0.1:18888/mcp/";
74559
74923
  const now = opts?.now ?? new Date;
@@ -74575,6 +74939,8 @@ async function handleGetMemoryHealth(config, opts) {
74575
74939
  const gaps = recentUnextracted(h.unextractedDocuments, 30, now);
74576
74940
  const stale = staleMentalModels(h.mentalModels, 7, now);
74577
74941
  const corrupted = corruptedMentalModels(h.mentalModels);
74942
+ const newestAge = ageDays(h.newestDocumentAt, now);
74943
+ const pendingStuck = h.pendingOperations > 0 && newestAge !== null && newestAge > 1;
74578
74944
  let status = "ok";
74579
74945
  let statusDetail = "facts flowing";
74580
74946
  if (!h.ok) {
@@ -74586,6 +74952,9 @@ async function handleGetMemoryHealth(config, opts) {
74586
74952
  } else if (gaps.length > 0) {
74587
74953
  status = "fail";
74588
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`;
74589
74958
  } else if (stale.length > 0) {
74590
74959
  status = "warn";
74591
74960
  statusDetail = `${stale.length} mental model(s) not refreshed in >7d`;
@@ -74604,6 +74973,7 @@ async function handleGetMemoryHealth(config, opts) {
74604
74973
  newestDocumentAt: h.newestDocumentAt,
74605
74974
  recentUnextractedCount: gaps.length,
74606
74975
  oldestUnextractedAt: gaps[0]?.createdAt ?? null,
74976
+ unextractedDocIds: gaps.slice(0, 20).map((d) => d.id),
74607
74977
  mentalModels: h.mentalModels,
74608
74978
  staleMentalModelCount: stale.length,
74609
74979
  corruptedMentalModelNames: corrupted.map((m) => m.name),
@@ -74614,11 +74984,236 @@ async function handleGetMemoryHealth(config, opts) {
74614
74984
  rows.sort((a, b) => a.bank.localeCompare(b.bank));
74615
74985
  return { reachable, url, banks: rows };
74616
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
+ }
74617
75212
 
74618
75213
  // src/web/webhook-handler.ts
74619
75214
  import { appendFileSync as appendFileSync4, existsSync as existsSync49, mkdirSync as mkdirSync28, readFileSync as readFileSync45, writeFileSync as writeFileSync26 } from "fs";
74620
- import { join as join45 } from "path";
74621
- import { homedir as homedir24 } from "os";
75215
+ import { join as join46 } from "path";
75216
+ import { homedir as homedir25 } from "os";
74622
75217
 
74623
75218
  // src/web/webhook-verify.ts
74624
75219
  import { createHmac as createHmac2, timingSafeEqual } from "crypto";
@@ -74824,12 +75419,12 @@ function forwardToGateway(socketPath, req, opts = {}) {
74824
75419
 
74825
75420
  // src/web/webhook-edge.ts
74826
75421
  import { existsSync as existsSync48, readFileSync as readFileSync44 } from "fs";
74827
- import { join as join44 } from "path";
74828
- import { homedir as homedir23 } from "os";
75422
+ import { join as join45 } from "path";
75423
+ import { homedir as homedir24 } from "os";
74829
75424
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
74830
75425
  var EDGE_HEADER = "x-switchroom-edge";
74831
75426
  function edgeSecretPath() {
74832
- return join44(homedir23(), ".switchroom", "webhook-edge-secret");
75427
+ return join45(homedir24(), ".switchroom", "webhook-edge-secret");
74833
75428
  }
74834
75429
  function loadEdgeSecret(path4) {
74835
75430
  const p = path4 ?? edgeSecretPath();
@@ -74892,8 +75487,8 @@ var agentDedupCache = new Map;
74892
75487
  function createFileDedupStore(resolveAgentDir) {
74893
75488
  return {
74894
75489
  check(agent, deliveryId, now) {
74895
- const telegramDir = join45(resolveAgentDir(agent), "telegram");
74896
- const filePath = join45(telegramDir, "webhook-dedup.json");
75490
+ const telegramDir = join46(resolveAgentDir(agent), "telegram");
75491
+ const filePath = join46(telegramDir, "webhook-dedup.json");
74897
75492
  if (!agentDedupCache.has(agent)) {
74898
75493
  agentDedupCache.set(agent, loadDedupFile(filePath));
74899
75494
  }
@@ -74945,7 +75540,7 @@ function shouldWriteThrottleIssue(agent, source, now, windowMap) {
74945
75540
  return true;
74946
75541
  }
74947
75542
  function writeThrottleIssue(agent, source, now, telegramDir, log) {
74948
- const issuesPath = join45(telegramDir, "issues.jsonl");
75543
+ const issuesPath = join46(telegramDir, "issues.jsonl");
74949
75544
  try {
74950
75545
  mkdirSync28(telegramDir, { recursive: true });
74951
75546
  const record = {
@@ -74970,7 +75565,7 @@ function writeThrottleIssue(agent, source, now, telegramDir, log) {
74970
75565
  async function handleWebhookIngest(args, deps = {}) {
74971
75566
  const log = deps.log ?? ((s) => process.stderr.write(s));
74972
75567
  const now = (deps.now ?? Date.now)();
74973
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join45(homedir24(), ".switchroom", "agents", a));
75568
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join46(homedir25(), ".switchroom", "agents", a));
74974
75569
  const rateLimiter = deps.rateLimiter ?? defaultRateLimiter;
74975
75570
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
74976
75571
  if (!args.agentExists) {
@@ -75037,7 +75632,7 @@ async function handleWebhookIngest(args, deps = {}) {
75037
75632
  if (retryAfter !== null) {
75038
75633
  if (!args.viaGateway) {
75039
75634
  const agentDir2 = resolveAgentDir(args.agent);
75040
- const telegramDir2 = join45(agentDir2, "telegram");
75635
+ const telegramDir2 = join46(agentDir2, "telegram");
75041
75636
  if (shouldWriteThrottleIssue(args.agent, source, now)) {
75042
75637
  writeThrottleIssue(args.agent, source, now, telegramDir2, log);
75043
75638
  }
@@ -75057,7 +75652,7 @@ async function handleWebhookIngest(args, deps = {}) {
75057
75652
  const eventType = source === "github" ? args.headers.get("x-github-event") ?? "unknown" : source === "linear" ? String(payload.type ?? "unknown").toLowerCase() : args.source;
75058
75653
  const rendered = source === "github" ? renderGithubEvent(eventType, payload) : source === "linear" ? renderLinearEvent(eventType, payload) : renderGenericEvent(args.source, payload);
75059
75654
  if (args.viaGateway) {
75060
- const socketPath = join45(resolveAgentDir(args.agent), "telegram", "webhook.sock");
75655
+ const socketPath = join46(resolveAgentDir(args.agent), "telegram", "webhook.sock");
75061
75656
  const forward = deps.forwardFn ?? forwardToGateway;
75062
75657
  const deliveryId = source === "github" ? args.headers.get("x-github-delivery") ?? undefined : undefined;
75063
75658
  let resp;
@@ -75096,8 +75691,8 @@ async function handleWebhookIngest(args, deps = {}) {
75096
75691
  return jsonReply(202, { ok: true, recorded: true, ts: resp.ts });
75097
75692
  }
75098
75693
  const agentDir = resolveAgentDir(args.agent);
75099
- const telegramDir = join45(agentDir, "telegram");
75100
- const logPath = join45(telegramDir, "webhook-events.jsonl");
75694
+ const telegramDir = join46(agentDir, "telegram");
75695
+ const logPath = join46(telegramDir, "webhook-events.jsonl");
75101
75696
  try {
75102
75697
  mkdirSync28(telegramDir, { recursive: true });
75103
75698
  const record = {
@@ -75120,6 +75715,8 @@ async function handleWebhookIngest(args, deps = {}) {
75120
75715
  }
75121
75716
 
75122
75717
  // src/web/server.ts
75718
+ var LOG_POLL_INTERVAL_MS = 3000;
75719
+ var LOG_POLL_TAIL_LINES = 400;
75123
75720
  var MIME_TYPES = {
75124
75721
  ".html": "text/html",
75125
75722
  ".css": "text/css",
@@ -75150,8 +75747,8 @@ function resolveWebToken() {
75150
75747
  const fromEnv = process.env.SWITCHROOM_WEB_TOKEN;
75151
75748
  if (fromEnv && fromEnv.length > 0)
75152
75749
  return fromEnv;
75153
- const home2 = process.env.HOME ?? homedir25();
75154
- const tokenPath = join46(home2, ".switchroom", "web-token");
75750
+ const home2 = process.env.HOME ?? homedir26();
75751
+ const tokenPath = join47(home2, ".switchroom", "web-token");
75155
75752
  if (existsSync50(tokenPath)) {
75156
75753
  const existing = readFileSync46(tokenPath, "utf8").trim();
75157
75754
  if (existing.length > 0)
@@ -75236,7 +75833,7 @@ function checkWsAuth(req, token, server) {
75236
75833
  return presented !== null && constantTimeEqual(presented, token);
75237
75834
  }
75238
75835
  function loadWebhookSecrets() {
75239
- const path4 = join46(homedir25(), ".switchroom", "webhook-secrets.json");
75836
+ const path4 = join47(homedir26(), ".switchroom", "webhook-secrets.json");
75240
75837
  if (!existsSync50(path4))
75241
75838
  return {};
75242
75839
  try {
@@ -75312,6 +75909,9 @@ function parseRoute(pathname, method) {
75312
75909
  if (method === "GET" && pathname === "/api/agents") {
75313
75910
  return { handler: "getAgents", params: {} };
75314
75911
  }
75912
+ if (method === "GET" && pathname === "/api/summary") {
75913
+ return { handler: "getSummary", params: {} };
75914
+ }
75315
75915
  const logsMatch = pathname.match(/^\/api\/agents\/([^/]+)\/logs$/);
75316
75916
  if (method === "GET" && logsMatch) {
75317
75917
  return { handler: "getLogs", params: { name: logsMatch[1] } };
@@ -75342,6 +75942,15 @@ function parseRoute(pathname, method) {
75342
75942
  if (method === "GET" && pathname === "/api/memory-health") {
75343
75943
  return { handler: "getMemoryHealth", params: {} };
75344
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
+ }
75345
75954
  if (method === "GET" && pathname === "/api/google-accounts") {
75346
75955
  return { handler: "getGoogleAccounts", params: {} };
75347
75956
  }
@@ -75357,6 +75966,9 @@ function parseRoute(pathname, method) {
75357
75966
  if (method === "GET" && pathname === "/api/approvals") {
75358
75967
  return { handler: "getApprovals", params: {} };
75359
75968
  }
75969
+ if (method === "GET" && pathname === "/api/grants") {
75970
+ return { handler: "getGrants", params: {} };
75971
+ }
75360
75972
  if (method === "GET" && pathname === "/api/accounts") {
75361
75973
  return { handler: "getAccounts", params: {} };
75362
75974
  }
@@ -75409,6 +76021,14 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75409
76021
  return config;
75410
76022
  }
75411
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 });
75412
76032
  const localhostOnly = hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
75413
76033
  const server = Bun.serve({
75414
76034
  port,
@@ -75445,7 +76065,16 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75445
76065
  return authError;
75446
76066
  switch (route.handler) {
75447
76067
  case "getAgents":
75448
- 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
+ })))();
75449
76078
  case "getLogs": {
75450
76079
  const agentName = route.params.name;
75451
76080
  if (!config.agents[agentName]) {
@@ -75453,28 +76082,28 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75453
76082
  }
75454
76083
  const rawLines = Number(url.searchParams.get("lines") ?? "50");
75455
76084
  const lines = Number.isInteger(rawLines) && rawLines >= 1 && rawLines <= 1e4 ? rawLines : 50;
75456
- return jsonResponse(handleGetLogs(agentName, lines));
76085
+ return (async () => jsonResponse(await handleGetLogs(agentName, lines)))();
75457
76086
  }
75458
76087
  case "startAgent": {
75459
76088
  const agentName = route.params.name;
75460
76089
  if (!config.agents[agentName]) {
75461
76090
  return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
75462
76091
  }
75463
- return jsonResponse(handleStartAgent(agentName));
76092
+ return (async () => jsonResponse(await handleStartAgent(agentName)))();
75464
76093
  }
75465
76094
  case "stopAgent": {
75466
76095
  const agentName = route.params.name;
75467
76096
  if (!config.agents[agentName]) {
75468
76097
  return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
75469
76098
  }
75470
- return jsonResponse(handleStopAgent(agentName));
76099
+ return (async () => jsonResponse(await handleStopAgent(agentName)))();
75471
76100
  }
75472
76101
  case "restartAgent": {
75473
76102
  const agentName = route.params.name;
75474
76103
  if (!config.agents[agentName]) {
75475
76104
  return jsonResponse({ ok: false, error: `Unknown agent: ${agentName}` }, 404);
75476
76105
  }
75477
- return jsonResponse(handleRestartAgent(agentName));
76106
+ return (async () => jsonResponse(await handleRestartAgent(agentName)))();
75478
76107
  }
75479
76108
  case "getTurns": {
75480
76109
  const agentName = route.params.name;
@@ -75502,9 +76131,9 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75502
76131
  return jsonResponse(result.subagents);
75503
76132
  }
75504
76133
  case "getSystemHealth":
75505
- return (async () => jsonResponse(await handleGetSystemHealth(config)))();
76134
+ return (async () => jsonResponse(withStamp(await cachedSystemHealth())))();
75506
76135
  case "getMemoryHealth":
75507
- return (async () => jsonResponse(await handleGetMemoryHealth(freshConfig())))();
76136
+ return (async () => jsonResponse(withStamp(await cachedMemoryHealth())))();
75508
76137
  case "getGoogleAccounts":
75509
76138
  return (async () => jsonResponse(await handleGetGoogleAccounts(freshConfig())))();
75510
76139
  case "getMicrosoftAccounts":
@@ -75512,11 +76141,13 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75512
76141
  case "getNotionWorkspace":
75513
76142
  return jsonResponse(handleGetNotionWorkspace(freshConfig()));
75514
76143
  case "getSchedule":
75515
- return jsonResponse(handleGetSchedule(config));
76144
+ return (async () => jsonResponse(withStamp(await cachedSchedule())))();
75516
76145
  case "getApprovals":
75517
- return (async () => jsonResponse(await handleGetApprovals()))();
76146
+ return (async () => jsonResponse(withStamp(await cachedApprovals())))();
76147
+ case "getGrants":
76148
+ return (async () => jsonResponse(withStamp(await cachedGrants())))();
75518
76149
  case "getAccounts":
75519
- return (async () => jsonResponse(await handleGetAccounts(config)))();
76150
+ return (async () => jsonResponse((await cachedAccounts()).value))();
75520
76151
  case "useAccount": {
75521
76152
  return (async () => {
75522
76153
  let body;
@@ -75580,6 +76211,45 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75580
76211
  return jsonResponse(result, result.ok ? 200 : 502);
75581
76212
  })();
75582
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
+ }
75583
76253
  case "getAgentAccounts": {
75584
76254
  const agentName = route.params.name;
75585
76255
  if (!config.agents[agentName]) {
@@ -75597,7 +76267,7 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75597
76267
  }
75598
76268
  }
75599
76269
  let filePath = pathname === "/" ? "/index.html" : pathname;
75600
- const fullPath = join46(uiDir, filePath);
76270
+ const fullPath = join47(uiDir, filePath);
75601
76271
  if (!existsSync50(fullPath)) {
75602
76272
  return new Response("Not Found", { status: 404 });
75603
76273
  }
@@ -75621,64 +76291,79 @@ function startWebServer(config, port, hostname = "127.0.0.1", configPath) {
75621
76291
  websocket: {
75622
76292
  open(_ws) {},
75623
76293
  close(ws) {
75624
- const proc = ws._logProcess;
75625
- if (proc) {
75626
- proc.kill();
75627
- ws._logProcess = null;
76294
+ const interval = ws._logInterval;
76295
+ if (interval) {
76296
+ clearInterval(interval);
76297
+ ws._logInterval = null;
75628
76298
  }
75629
76299
  },
75630
76300
  message(ws, message) {
76301
+ let data;
75631
76302
  try {
75632
- const data = JSON.parse(String(message));
75633
- if (data.type === "subscribe" && data.agent) {
75634
- const agentName = String(data.agent).replace(/[^a-zA-Z0-9_-]/g, "");
75635
- if (!agentName || !config.agents[agentName]) {
75636
- try {
75637
- ws.send(JSON.stringify({ type: "error", error: "Unknown agent" }));
75638
- } catch {}
75639
- return;
75640
- }
75641
- const existing = ws._logProcess;
75642
- if (existing) {
75643
- existing.kill();
75644
- ws._logProcess = null;
75645
- }
75646
- const child = spawn5("docker", ["logs", "-f", "--tail", "20", containerName(agentName)], { stdio: ["ignore", "pipe", "pipe"] });
75647
- 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) {
75648
76332
  try {
75649
76333
  ws.send(JSON.stringify({
75650
76334
  type: "log_error",
75651
76335
  agent: agentName,
75652
- data: `failed to stream logs: ${err.message}
75653
- `
76336
+ data: err instanceof Error ? err.message : String(err)
75654
76337
  }));
75655
76338
  } catch {}
75656
- });
75657
- child.stdout.on("data", (chunk) => {
76339
+ clearLogInterval();
76340
+ return;
76341
+ }
76342
+ if (result.ok) {
75658
76343
  try {
75659
76344
  ws.send(JSON.stringify({
75660
76345
  type: "log",
75661
76346
  agent: agentName,
75662
- data: chunk.toString("utf-8")
76347
+ data: result.logs,
76348
+ replace: true
75663
76349
  }));
75664
76350
  } catch {
75665
- child.kill();
76351
+ clearLogInterval();
75666
76352
  }
75667
- });
75668
- child.stderr.on("data", (chunk) => {
76353
+ } else {
75669
76354
  try {
75670
76355
  ws.send(JSON.stringify({
75671
76356
  type: "log_error",
75672
76357
  agent: agentName,
75673
- data: chunk.toString("utf-8")
76358
+ data: result.error
75674
76359
  }));
75675
- } catch {
75676
- child.kill();
75677
- }
75678
- });
75679
- ws._logProcess = child;
75680
- }
75681
- } catch {}
76360
+ } catch {}
76361
+ clearLogInterval();
76362
+ }
76363
+ };
76364
+ poll();
76365
+ ws._logInterval = setInterval(() => void poll(), LOG_POLL_INTERVAL_MS);
76366
+ }
75682
76367
  }
75683
76368
  }
75684
76369
  });
@@ -76641,8 +77326,8 @@ init_loader();
76641
77326
  init_lifecycle();
76642
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";
76643
77328
  import { spawnSync as spawnSync9 } from "node:child_process";
76644
- import { join as join60, dirname as dirname14, resolve as resolve34 } from "node:path";
76645
- 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";
76646
77331
 
76647
77332
  // src/cli/release-yaml.ts
76648
77333
  var import_yaml18 = __toESM(require_dist(), 1);
@@ -76681,13 +77366,13 @@ function defaultPersistPin(configPath) {
76681
77366
  } catch {}
76682
77367
  };
76683
77368
  }
76684
- var DEFAULT_COMPOSE_PATH = join60(homedir36(), ".switchroom", "compose", "docker-compose.yml");
77369
+ var DEFAULT_COMPOSE_PATH = join61(homedir37(), ".switchroom", "compose", "docker-compose.yml");
76685
77370
  function runningFromSwitchroomCheckout(scriptPath) {
76686
77371
  let dir = dirname14(scriptPath);
76687
77372
  for (let i = 0;i < 12; i++) {
76688
- if (existsSync58(join60(dir, ".git"))) {
77373
+ if (existsSync58(join61(dir, ".git"))) {
76689
77374
  try {
76690
- const pkg = JSON.parse(readFileSync52(join60(dir, "package.json"), "utf-8"));
77375
+ const pkg = JSON.parse(readFileSync52(join61(dir, "package.json"), "utf-8"));
76691
77376
  if (pkg.name === "switchroom")
76692
77377
  return true;
76693
77378
  } catch {}
@@ -76847,7 +77532,7 @@ function planUpdate(opts) {
76847
77532
  return;
76848
77533
  }
76849
77534
  const source = resolve34(import.meta.dirname, "../../skills");
76850
- const dest = join60(homedir36(), ".switchroom", "skills", "_bundled");
77535
+ const dest = join61(homedir37(), ".switchroom", "skills", "_bundled");
76851
77536
  if (!existsSync58(source)) {
76852
77537
  process.stderr.write(`switchroom update: sync-bundled-skills \u2014 CLI bundle has no adjacent skills/ at ${source}; skipping.
76853
77538
  `);
@@ -76961,7 +77646,7 @@ function defaultStatusProbe(composePath) {
76961
77646
  } catch {}
76962
77647
  let dir = dirname14(scriptPath);
76963
77648
  for (let i = 0;i < 8; i++) {
76964
- const pkgPath = join60(dir, "package.json");
77649
+ const pkgPath = join61(dir, "package.json");
76965
77650
  if (existsSync58(pkgPath)) {
76966
77651
  try {
76967
77652
  const pkg = JSON.parse(readFileSync52(pkgPath, "utf-8"));
@@ -77403,7 +78088,7 @@ init_helpers();
77403
78088
  init_lifecycle();
77404
78089
  import { execSync as execSync4 } from "node:child_process";
77405
78090
  import { existsSync as existsSync59, readFileSync as readFileSync54 } from "node:fs";
77406
- import { dirname as dirname15, join as join61 } from "node:path";
78091
+ import { dirname as dirname15, join as join62 } from "node:path";
77407
78092
  function getClaudeCodeVersion() {
77408
78093
  try {
77409
78094
  const out = execSync4("claude --version 2>/dev/null", {
@@ -77453,11 +78138,11 @@ function formatUptime3(timestamp) {
77453
78138
  function locateSwitchroomInstallDir() {
77454
78139
  let dir = import.meta.dirname;
77455
78140
  for (let i = 0;i < 10 && dir && dir !== "/"; i++) {
77456
- const pkgPath = join61(dir, "package.json");
78141
+ const pkgPath = join62(dir, "package.json");
77457
78142
  if (existsSync59(pkgPath)) {
77458
78143
  try {
77459
78144
  const pkg = JSON.parse(readFileSync54(pkgPath, "utf-8"));
77460
- if (pkg.name === "switchroom" && existsSync59(join61(dir, ".git"))) {
78145
+ if (pkg.name === "switchroom" && existsSync59(join62(dir, ".git"))) {
77461
78146
  return dir;
77462
78147
  }
77463
78148
  } catch {}
@@ -77694,7 +78379,7 @@ import {
77694
78379
  writeFileSync as writeFileSync28,
77695
78380
  writeSync as writeSync7
77696
78381
  } from "node:fs";
77697
- import { join as join62 } from "node:path";
78382
+ import { join as join63 } from "node:path";
77698
78383
  import { randomBytes as randomBytes12 } from "node:crypto";
77699
78384
  import { execSync as execSync5 } from "node:child_process";
77700
78385
 
@@ -78092,7 +78777,7 @@ function redactedMarker(ruleId) {
78092
78777
  var ISSUES_FILE = "issues.jsonl";
78093
78778
  var ISSUES_LOCK = "issues.lock";
78094
78779
  function readAll(stateDir) {
78095
- const path4 = join62(stateDir, ISSUES_FILE);
78780
+ const path4 = join63(stateDir, ISSUES_FILE);
78096
78781
  if (!existsSync60(path4))
78097
78782
  return [];
78098
78783
  let raw;
@@ -78170,7 +78855,7 @@ function record(stateDir, input, nowFn = Date.now) {
78170
78855
  });
78171
78856
  }
78172
78857
  function resolve37(stateDir, fingerprint, nowFn = Date.now) {
78173
- if (!existsSync60(join62(stateDir, ISSUES_FILE)))
78858
+ if (!existsSync60(join63(stateDir, ISSUES_FILE)))
78174
78859
  return 0;
78175
78860
  return withLock(stateDir, () => {
78176
78861
  const all = readAll(stateDir);
@@ -78188,7 +78873,7 @@ function resolve37(stateDir, fingerprint, nowFn = Date.now) {
78188
78873
  });
78189
78874
  }
78190
78875
  function resolveAllBySource(stateDir, source, nowFn = Date.now) {
78191
- if (!existsSync60(join62(stateDir, ISSUES_FILE)))
78876
+ if (!existsSync60(join63(stateDir, ISSUES_FILE)))
78192
78877
  return 0;
78193
78878
  return withLock(stateDir, () => {
78194
78879
  const all = readAll(stateDir);
@@ -78206,7 +78891,7 @@ function resolveAllBySource(stateDir, source, nowFn = Date.now) {
78206
78891
  });
78207
78892
  }
78208
78893
  function prune(stateDir, opts = {}) {
78209
- if (!existsSync60(join62(stateDir, ISSUES_FILE)))
78894
+ if (!existsSync60(join63(stateDir, ISSUES_FILE)))
78210
78895
  return 0;
78211
78896
  return withLock(stateDir, () => {
78212
78897
  const all = readAll(stateDir);
@@ -78239,7 +78924,7 @@ function ensureDir(stateDir) {
78239
78924
  mkdirSync33(stateDir, { recursive: true });
78240
78925
  }
78241
78926
  function writeAll(stateDir, events) {
78242
- const path4 = join62(stateDir, ISSUES_FILE);
78927
+ const path4 = join63(stateDir, ISSUES_FILE);
78243
78928
  sweepOrphanTmpFiles(stateDir);
78244
78929
  const tmp = `${path4}.tmp-${process.pid}-${randomBytes12(4).toString("hex")}`;
78245
78930
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -78261,7 +78946,7 @@ function sweepOrphanTmpFiles(stateDir) {
78261
78946
  for (const entry of entries) {
78262
78947
  if (!entry.startsWith(TMP_PREFIX))
78263
78948
  continue;
78264
- const tmpPath = join62(stateDir, entry);
78949
+ const tmpPath = join63(stateDir, entry);
78265
78950
  try {
78266
78951
  const stat = statSync28(tmpPath);
78267
78952
  if (stat.mtimeMs < cutoff) {
@@ -78273,7 +78958,7 @@ function sweepOrphanTmpFiles(stateDir) {
78273
78958
  var LOCK_RETRY_MS = 25;
78274
78959
  var LOCK_TIMEOUT_MS = 1e4;
78275
78960
  function withLock(stateDir, fn) {
78276
- const lockPath = join62(stateDir, ISSUES_LOCK);
78961
+ const lockPath = join63(stateDir, ISSUES_LOCK);
78277
78962
  const startedAt = Date.now();
78278
78963
  let fd = null;
78279
78964
  while (fd === null) {
@@ -78557,8 +79242,8 @@ function relTime(deltaMs) {
78557
79242
  // src/cli/deps.ts
78558
79243
  init_source();
78559
79244
  import { existsSync as existsSync63 } from "node:fs";
78560
- import { homedir as homedir39 } from "node:os";
78561
- 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";
78562
79247
 
78563
79248
  // src/deps/python.ts
78564
79249
  import { createHash as createHash11 } from "node:crypto";
@@ -78569,8 +79254,8 @@ import {
78569
79254
  rmSync as rmSync13,
78570
79255
  writeFileSync as writeFileSync29
78571
79256
  } from "node:fs";
78572
- import { dirname as dirname16, join as join63 } from "node:path";
78573
- 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";
78574
79259
  import { execFileSync as execFileSync19 } from "node:child_process";
78575
79260
 
78576
79261
  class PythonEnvError extends Error {
@@ -78582,7 +79267,7 @@ class PythonEnvError extends Error {
78582
79267
  }
78583
79268
  }
78584
79269
  function defaultPythonCacheRoot() {
78585
- return join63(homedir37(), ".switchroom", "deps", "python");
79270
+ return join64(homedir38(), ".switchroom", "deps", "python");
78586
79271
  }
78587
79272
  function hashFile(path4) {
78588
79273
  return createHash11("sha256").update(readFileSync56(path4)).digest("hex");
@@ -78594,11 +79279,11 @@ function ensurePythonEnv(opts) {
78594
79279
  if (!existsSync61(requirementsPath)) {
78595
79280
  throw new PythonEnvError(`requirements file not found: ${requirementsPath}`);
78596
79281
  }
78597
- const venvDir = join63(cacheRoot, skillName);
78598
- const stampPath = join63(venvDir, ".requirements.sha256");
78599
- const binDir = join63(venvDir, "bin");
78600
- const pythonBin = join63(binDir, "python");
78601
- 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");
78602
79287
  const targetHash = hashFile(requirementsPath);
78603
79288
  if (!force && existsSync61(stampPath) && existsSync61(pythonBin)) {
78604
79289
  const existingHash = readFileSync56(stampPath, "utf8").trim();
@@ -78657,8 +79342,8 @@ import {
78657
79342
  rmSync as rmSync14,
78658
79343
  writeFileSync as writeFileSync30
78659
79344
  } from "node:fs";
78660
- import { dirname as dirname17, join as join64 } from "node:path";
78661
- 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";
78662
79347
  import { execFileSync as execFileSync20 } from "node:child_process";
78663
79348
 
78664
79349
  class NodeEnvError extends Error {
@@ -78681,7 +79366,7 @@ var LOCKFILES_FOR = {
78681
79366
  npm: ["package-lock.json"]
78682
79367
  };
78683
79368
  function defaultNodeCacheRoot() {
78684
- return join64(homedir38(), ".switchroom", "deps", "node");
79369
+ return join65(homedir39(), ".switchroom", "deps", "node");
78685
79370
  }
78686
79371
  function hashDepInputs(packageJsonPath) {
78687
79372
  const sourceDir = dirname17(packageJsonPath);
@@ -78690,7 +79375,7 @@ function hashDepInputs(packageJsonPath) {
78690
79375
  `);
78691
79376
  hasher.update(readFileSync57(packageJsonPath));
78692
79377
  for (const lockName of ALL_LOCKFILES) {
78693
- const lockPath = join64(sourceDir, lockName);
79378
+ const lockPath = join65(sourceDir, lockName);
78694
79379
  if (existsSync62(lockPath)) {
78695
79380
  hasher.update(`
78696
79381
  `);
@@ -78710,10 +79395,10 @@ function ensureNodeEnv(opts) {
78710
79395
  throw new NodeEnvError(`package.json not found: ${packageJsonPath}`);
78711
79396
  }
78712
79397
  const sourceDir = dirname17(packageJsonPath);
78713
- const envDir = join64(cacheRoot, skillName);
78714
- const stampPath = join64(envDir, ".package.sha256");
78715
- const nodeModulesDir = join64(envDir, "node_modules");
78716
- 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");
78717
79402
  const targetHash = hashDepInputs(packageJsonPath);
78718
79403
  if (!force && existsSync62(stampPath) && existsSync62(nodeModulesDir)) {
78719
79404
  const existingHash = readFileSync57(stampPath, "utf8").trim();
@@ -78731,12 +79416,12 @@ function ensureNodeEnv(opts) {
78731
79416
  rmSync14(envDir, { recursive: true, force: true });
78732
79417
  }
78733
79418
  mkdirSync35(envDir, { recursive: true });
78734
- copyFileSync9(packageJsonPath, join64(envDir, "package.json"));
79419
+ copyFileSync9(packageJsonPath, join65(envDir, "package.json"));
78735
79420
  let copiedLockfile = false;
78736
79421
  for (const lockName of LOCKFILES_FOR[installer]) {
78737
- const lockPath = join64(sourceDir, lockName);
79422
+ const lockPath = join65(sourceDir, lockName);
78738
79423
  if (existsSync62(lockPath)) {
78739
- copyFileSync9(lockPath, join64(envDir, lockName));
79424
+ copyFileSync9(lockPath, join65(envDir, lockName));
78740
79425
  copiedLockfile = true;
78741
79426
  }
78742
79427
  }
@@ -78765,7 +79450,7 @@ function ensureNodeEnv(opts) {
78765
79450
 
78766
79451
  // src/cli/deps.ts
78767
79452
  function builtinSkillsRoot() {
78768
- return resolve38(homedir39(), ".switchroom/skills/_bundled");
79453
+ return resolve38(homedir40(), ".switchroom/skills/_bundled");
78769
79454
  }
78770
79455
  function registerDepsCommand(program3) {
78771
79456
  const deps = program3.command("deps").description("Manage cached per-skill dependency environments");
@@ -78775,13 +79460,13 @@ function registerDepsCommand(program3) {
78775
79460
  console.error(source_default.red(`Bundled skills pool dir not found at ${skillsRoot} \u2014 run \`switchroom update\` to install it.`));
78776
79461
  process.exit(1);
78777
79462
  }
78778
- const skillDir = join65(skillsRoot, skill);
79463
+ const skillDir = join66(skillsRoot, skill);
78779
79464
  if (!existsSync63(skillDir)) {
78780
79465
  console.error(source_default.red(`Unknown skill: ${skill} (no dir at ${skillDir})`));
78781
79466
  process.exit(1);
78782
79467
  }
78783
- const requirementsPath = join65(skillDir, "requirements.txt");
78784
- const packageJsonPath = join65(skillDir, "package.json");
79468
+ const requirementsPath = join66(skillDir, "requirements.txt");
79469
+ const packageJsonPath = join66(skillDir, "package.json");
78785
79470
  const wantPython = opts.python ?? (!opts.python && !opts.node && existsSync63(requirementsPath));
78786
79471
  const wantNode = opts.node ?? (!opts.python && !opts.node && existsSync63(packageJsonPath));
78787
79472
  let did = 0;
@@ -79736,7 +80421,7 @@ init_helpers();
79736
80421
  init_loader();
79737
80422
  init_merge();
79738
80423
  import { copyFileSync as copyFileSync10, existsSync as existsSync65, readFileSync as readFileSync58, writeFileSync as writeFileSync31 } from "node:fs";
79739
- import { join as join66, resolve as resolve40 } from "node:path";
80424
+ import { join as join67, resolve as resolve40 } from "node:path";
79740
80425
  init_schema();
79741
80426
  function resolveSoulTargetOrExit(program3, agentName) {
79742
80427
  const config = getConfig(program3);
@@ -79760,7 +80445,7 @@ function resolveSoulTargetOrExit(program3, agentName) {
79760
80445
  profileName,
79761
80446
  profilePath,
79762
80447
  workspaceDir,
79763
- soulPath: join66(workspaceDir, "SOUL.md"),
80448
+ soulPath: join67(workspaceDir, "SOUL.md"),
79764
80449
  soul: merged.soul
79765
80450
  };
79766
80451
  }
@@ -79827,7 +80512,7 @@ function registerSoulCommand(program3) {
79827
80512
  init_helpers();
79828
80513
  init_loader();
79829
80514
  import { existsSync as existsSync66, readFileSync as readFileSync59, readdirSync as readdirSync23, statSync as statSync29 } from "node:fs";
79830
- import { resolve as resolve41, join as join67 } from "node:path";
80515
+ import { resolve as resolve41, join as join68 } from "node:path";
79831
80516
  import { createHash as createHash13 } from "node:crypto";
79832
80517
  init_merge();
79833
80518
  init_hindsight();
@@ -79838,7 +80523,7 @@ function estimateTokens(bytes) {
79838
80523
  return Math.round(bytes / 3.7);
79839
80524
  }
79840
80525
  function readMcpServerNames(agentDir) {
79841
- const mcpPath = join67(agentDir, ".mcp.json");
80526
+ const mcpPath = join68(agentDir, ".mcp.json");
79842
80527
  if (!existsSync66(mcpPath))
79843
80528
  return [];
79844
80529
  try {
@@ -79852,7 +80537,7 @@ function sha256(content) {
79852
80537
  return createHash13("sha256").update(content).digest("hex").slice(0, 16);
79853
80538
  }
79854
80539
  function findLatestTranscriptJsonl(claudeConfigDir) {
79855
- const projectsDir = join67(claudeConfigDir, "projects");
80540
+ const projectsDir = join68(claudeConfigDir, "projects");
79856
80541
  if (!existsSync66(projectsDir))
79857
80542
  return;
79858
80543
  try {
@@ -79861,8 +80546,8 @@ function findLatestTranscriptJsonl(claudeConfigDir) {
79861
80546
  for (const entry of entries) {
79862
80547
  if (!entry.isDirectory())
79863
80548
  continue;
79864
- const projectPath = join67(projectsDir, entry.name);
79865
- const transcriptPath = join67(projectPath, "transcript.jsonl");
80549
+ const projectPath = join68(projectsDir, entry.name);
80550
+ const transcriptPath = join68(projectPath, "transcript.jsonl");
79866
80551
  if (!existsSync66(transcriptPath))
79867
80552
  continue;
79868
80553
  const stat3 = statSync29(transcriptPath);
@@ -79931,11 +80616,11 @@ function registerDebugCommand(program3) {
79931
80616
  process.exit(1);
79932
80617
  }
79933
80618
  const workspaceDir = resolveAgentWorkspaceDir(agentDir);
79934
- const claudeConfigDir = join67(agentDir, ".claude");
79935
- const claudeMdPath = join67(agentDir, "CLAUDE.md");
79936
- const soulMdPath = join67(agentDir, "SOUL.md");
79937
- const workspaceSoulMdPath = join67(workspaceDir, "SOUL.md");
79938
- 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");
79939
80624
  const lastN = parseInt(opts.last, 10);
79940
80625
  if (isNaN(lastN) || lastN < 1) {
79941
80626
  console.error("--last must be a positive integer");
@@ -80064,9 +80749,9 @@ function registerDebugCommand(program3) {
80064
80749
  const soulMdBytes = soulMdContent.length;
80065
80750
  const perTurnBytes = dynamicResult.concatenated.length;
80066
80751
  const userBytes = userMessage?.text.length ?? 0;
80067
- const fleetDir = join67(agentsDir, "..", "fleet");
80068
- const fleetInvPath = join67(fleetDir, "switchroom-invariants.md");
80069
- 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");
80070
80755
  const fleetInvBytes = existsSync66(fleetInvPath) ? readFileSync59(fleetInvPath, "utf-8").length : 0;
80071
80756
  const fleetClaudeBytes = existsSync66(fleetClaudePath) ? readFileSync59(fleetClaudePath, "utf-8").length : 0;
80072
80757
  const fleetBytes = fleetInvBytes + fleetClaudeBytes;
@@ -80102,8 +80787,8 @@ init_source();
80102
80787
  // src/worktree/claim.ts
80103
80788
  import { execFileSync as execFileSync21 } from "node:child_process";
80104
80789
  import { closeSync as closeSync12, mkdirSync as mkdirSync37, openSync as openSync12, existsSync as existsSync68, unlinkSync as unlinkSync13 } from "node:fs";
80105
- import { join as join69, resolve as resolve43 } from "node:path";
80106
- 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";
80107
80792
  import { randomBytes as randomBytes13 } from "node:crypto";
80108
80793
 
80109
80794
  // src/worktree/registry.ts
@@ -80116,13 +80801,13 @@ import {
80116
80801
  existsSync as existsSync67,
80117
80802
  renameSync as renameSync13
80118
80803
  } from "node:fs";
80119
- import { join as join68, resolve as resolve42 } from "node:path";
80120
- 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";
80121
80806
  function registryDir() {
80122
- return resolve42(process.env.SWITCHROOM_WORKTREE_DIR ?? join68(homedir40(), ".switchroom", "worktrees"));
80807
+ return resolve42(process.env.SWITCHROOM_WORKTREE_DIR ?? join69(homedir41(), ".switchroom", "worktrees"));
80123
80808
  }
80124
80809
  function recordPath(id) {
80125
- return join68(registryDir(), `${id}.json`);
80810
+ return join69(registryDir(), `${id}.json`);
80126
80811
  }
80127
80812
  function ensureDir2() {
80128
80813
  mkdirSync36(registryDir(), { recursive: true });
@@ -80173,7 +80858,7 @@ function acquireRepoLock(repoPath) {
80173
80858
  const lockDir = registryDir();
80174
80859
  mkdirSync37(lockDir, { recursive: true });
80175
80860
  const lockName = repoPath.replace(/[^A-Za-z0-9]/g, "_");
80176
- const lockPath = join69(lockDir, `.lock-${lockName}`);
80861
+ const lockPath = join70(lockDir, `.lock-${lockName}`);
80177
80862
  const deadline = Date.now() + 5000;
80178
80863
  let fd = null;
80179
80864
  while (fd === null) {
@@ -80200,7 +80885,7 @@ function acquireRepoLock(repoPath) {
80200
80885
  }
80201
80886
  var DEFAULT_CONCURRENCY = 5;
80202
80887
  function worktreesBaseDir() {
80203
- 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"));
80204
80889
  }
80205
80890
  function shortId() {
80206
80891
  return randomBytes13(4).toString("hex");
@@ -80222,7 +80907,7 @@ function resolveRepoPath(repo, codeRepos) {
80222
80907
  }
80223
80908
  function expandHome(p) {
80224
80909
  if (p.startsWith("~/"))
80225
- return join69(homedir41(), p.slice(2));
80910
+ return join70(homedir42(), p.slice(2));
80226
80911
  return p;
80227
80912
  }
80228
80913
  async function claimWorktree(input, codeRepos) {
@@ -80250,7 +80935,7 @@ async function claimWorktree(input, codeRepos) {
80250
80935
  branch = `task/${taskSuffix}-${id}`;
80251
80936
  const baseDir = worktreesBaseDir();
80252
80937
  mkdirSync37(baseDir, { recursive: true });
80253
- worktreePath = join69(baseDir, `${id}-${taskSuffix}`);
80938
+ worktreePath = join70(baseDir, `${id}-${taskSuffix}`);
80254
80939
  const now = new Date().toISOString();
80255
80940
  const record2 = {
80256
80941
  id,
@@ -80505,7 +81190,7 @@ import {
80505
81190
  rmSync as rmSync15,
80506
81191
  writeFileSync as writeFileSync33
80507
81192
  } from "node:fs";
80508
- import { join as join70 } from "node:path";
81193
+ import { join as join71 } from "node:path";
80509
81194
  function encodeCredentialsFilename(email) {
80510
81195
  const SAFE = new Set([
80511
81196
  ..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
@@ -80695,16 +81380,16 @@ function resolveCredentialsDir(env2) {
80695
81380
  if (explicit && explicit.length > 0)
80696
81381
  return explicit;
80697
81382
  const stateBase = env2.SWITCHROOM_CONTAINER === "1" ? "/state/agent" : env2.HOME ?? ".";
80698
- return join70(stateBase, "google-workspace-mcp", "credentials");
81383
+ return join71(stateBase, "google-workspace-mcp", "credentials");
80699
81384
  }
80700
81385
  function writeSeedFile(dir, email, seed) {
80701
81386
  mkdirSync38(dir, { recursive: true, mode: 448 });
80702
81387
  chmodSync9(dir, 448);
80703
81388
  for (const name of readdirSync25(dir)) {
80704
- rmSync15(join70(dir, name), { force: true, recursive: true });
81389
+ rmSync15(join71(dir, name), { force: true, recursive: true });
80705
81390
  }
80706
81391
  const filename = encodeCredentialsFilename(email);
80707
- const filePath = join70(dir, filename);
81392
+ const filePath = join71(dir, filename);
80708
81393
  writeFileSync33(filePath, JSON.stringify(seed), { mode: 384 });
80709
81394
  chmodSync9(filePath, 384);
80710
81395
  return filePath;
@@ -80825,9 +81510,9 @@ async function runDriveMcpLauncher(opts) {
80825
81510
  }
80826
81511
  const args = buildUvxArgs(tier);
80827
81512
  const env2 = buildChildEnv(process.env, credentialsDir, brokerCreds.accountEmail);
80828
- const { spawn: spawn6 } = await import("node:child_process");
81513
+ const { spawn: spawn5 } = await import("node:child_process");
80829
81514
  const os5 = await import("node:os");
80830
- const child = spawn6("uvx", args, {
81515
+ const child = spawn5("uvx", args, {
80831
81516
  stdio: ["pipe", "pipe", "inherit"],
80832
81517
  env: env2
80833
81518
  });
@@ -80862,9 +81547,9 @@ function registerDriveMcpLauncherCommand(program3) {
80862
81547
 
80863
81548
  // src/cli/m365-mcp-launcher.ts
80864
81549
  init_scaffold_integration();
80865
- import { spawn as spawn6 } from "node:child_process";
81550
+ import { spawn as spawn5 } from "node:child_process";
80866
81551
  import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync39 } from "node:fs";
80867
- import { dirname as dirname18, join as join71 } from "node:path";
81552
+ import { dirname as dirname18, join as join72 } from "node:path";
80868
81553
  var SOFTERIA_TOKEN_ENV = "MS365_MCP_OAUTH_TOKEN";
80869
81554
  var DEFAULT_REFRESH_LEAD_MS = 5 * 60 * 1000;
80870
81555
  var MAX_REFRESH_INTERVAL_MS = 60 * 60 * 1000;
@@ -80896,7 +81581,7 @@ function writeRefreshHeartbeat(agentName, data) {
80896
81581
  function heartbeatPath(agentName) {
80897
81582
  const override = process.env.SWITCHROOM_M365_HEARTBEAT_DIR;
80898
81583
  if (override) {
80899
- return join71(override, `m365-launcher-${agentName}.heartbeat.json`);
81584
+ return join72(override, `m365-launcher-${agentName}.heartbeat.json`);
80900
81585
  }
80901
81586
  return "/state/agent/m365-launcher.heartbeat.json";
80902
81587
  }
@@ -81068,7 +81753,7 @@ function registerM365McpLauncherCommand(program3) {
81068
81753
  });
81069
81754
  },
81070
81755
  spawnSofteria: (env2) => {
81071
- return spawn6("npx", buildSofteriaArgs(opts), {
81756
+ return spawn5("npx", buildSofteriaArgs(opts), {
81072
81757
  env: env2,
81073
81758
  stdio: ["pipe", "pipe", "pipe"]
81074
81759
  });
@@ -81080,7 +81765,7 @@ function registerM365McpLauncherCommand(program3) {
81080
81765
 
81081
81766
  // src/cli/notion-mcp-launcher.ts
81082
81767
  init_scaffold_integration();
81083
- import { spawn as spawn7 } from "node:child_process";
81768
+ import { spawn as spawn6 } from "node:child_process";
81084
81769
  import { existsSync as existsSync71, mkdirSync as mkdirSync40, writeFileSync as writeFileSync35 } from "node:fs";
81085
81770
  import { dirname as dirname19 } from "node:path";
81086
81771
  var HEARTBEAT_WRITE_INTERVAL_MS = 30 * 1000;
@@ -81193,7 +81878,7 @@ function registerNotionMcpLauncherCommand(program3) {
81193
81878
  return result.entry.value;
81194
81879
  },
81195
81880
  spawnMcp: (env2, args) => {
81196
- return spawn7("npx", args, {
81881
+ return spawn6("npx", args, {
81197
81882
  env: env2,
81198
81883
  stdio: ["pipe", "pipe", "pipe"]
81199
81884
  });
@@ -82224,8 +82909,8 @@ agents:
82224
82909
 
82225
82910
  // src/cli/apply.ts
82226
82911
  init_resolver();
82227
- import { dirname as dirname22, join as join74, resolve as resolve45 } from "node:path";
82228
- 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";
82229
82914
  import { execFileSync as execFileSync24 } from "node:child_process";
82230
82915
  init_vault();
82231
82916
  init_loader();
@@ -82233,7 +82918,7 @@ init_loader();
82233
82918
 
82234
82919
  // src/cli/update-prompt-hook.ts
82235
82920
  import { existsSync as existsSync72, readFileSync as readFileSync62, writeFileSync as writeFileSync36, chmodSync as chmodSync10, mkdirSync as mkdirSync41 } from "node:fs";
82236
- import { join as join72 } from "node:path";
82921
+ import { join as join73 } from "node:path";
82237
82922
  var HOOK_FILENAME = "update-card-on-prompt.sh";
82238
82923
  function updatePromptHookScript() {
82239
82924
  return `#!/bin/bash
@@ -82299,9 +82984,9 @@ exit 0
82299
82984
  `;
82300
82985
  }
82301
82986
  function installUpdatePromptHook(agentDir) {
82302
- const hooksDir = join72(agentDir, ".claude", "hooks");
82987
+ const hooksDir = join73(agentDir, ".claude", "hooks");
82303
82988
  mkdirSync41(hooksDir, { recursive: true });
82304
- const scriptPath = join72(hooksDir, HOOK_FILENAME);
82989
+ const scriptPath = join73(hooksDir, HOOK_FILENAME);
82305
82990
  const desired = updatePromptHookScript();
82306
82991
  let installed = false;
82307
82992
  const existing = existsSync72(scriptPath) ? readFileSync62(scriptPath, "utf-8") : "";
@@ -82314,7 +82999,7 @@ function installUpdatePromptHook(agentDir) {
82314
82999
  chmodSync10(scriptPath, 493);
82315
83000
  } catch {}
82316
83001
  }
82317
- const settingsPath = join72(agentDir, ".claude", "settings.json");
83002
+ const settingsPath = join73(agentDir, ".claude", "settings.json");
82318
83003
  if (!existsSync72(settingsPath)) {
82319
83004
  return { scriptPath, settingsPath, installed };
82320
83005
  }
@@ -82416,14 +83101,14 @@ var EMBEDDED_EXAMPLES = {
82416
83101
  switchroom: switchroom_default,
82417
83102
  minimal: minimal_default
82418
83103
  };
82419
- var DEFAULT_COMPOSE_PATH2 = join74(homedir43(), ".switchroom", "compose", "docker-compose.yml");
83104
+ var DEFAULT_COMPOSE_PATH2 = join75(homedir44(), ".switchroom", "compose", "docker-compose.yml");
82420
83105
  var COMPOSE_PROJECT2 = "switchroom";
82421
83106
  function resolveVaultBindMountDir(homeDir, ctx) {
82422
83107
  const isCustomPath = ctx.migrationKind === "custom-path-skipped";
82423
83108
  if (isCustomPath && ctx.customVaultPath) {
82424
83109
  return dirname22(ctx.customVaultPath);
82425
83110
  }
82426
- return join74(homeDir, ".switchroom", "vault");
83111
+ return join75(homeDir, ".switchroom", "vault");
82427
83112
  }
82428
83113
  function inspectVaultBindMountDir(vaultDir) {
82429
83114
  if (!existsSync74(vaultDir))
@@ -82452,44 +83137,44 @@ function hasVaultRefs(value) {
82452
83137
  return false;
82453
83138
  }
82454
83139
  async function ensureHostMountSources(config) {
82455
- const home2 = homedir43();
83140
+ const home2 = homedir44();
82456
83141
  const dirs = [
82457
- join74(home2, ".switchroom", "approvals"),
82458
- join74(home2, ".switchroom", "scheduler"),
82459
- join74(home2, ".switchroom", "logs"),
82460
- join74(home2, ".switchroom", "compose"),
82461
- 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")
82462
83147
  ];
82463
83148
  for (const name of Object.keys(config.agents)) {
82464
- dirs.push(join74(home2, ".switchroom", "agents", name));
82465
- dirs.push(join74(home2, ".switchroom", "logs", name));
82466
- dirs.push(join74(home2, ".claude", "projects", name));
82467
- dirs.push(join74(home2, ".switchroom", "audit", name));
82468
- if (existsSync74(join74(home2, ".switchroom-config"))) {
82469
- 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"));
82470
83155
  }
82471
83156
  }
82472
83157
  for (const dir of dirs) {
82473
83158
  await mkdir2(dir, { recursive: true });
82474
83159
  }
82475
- const autoUnlockPath = join74(home2, ".switchroom", "vault-auto-unlock");
83160
+ const autoUnlockPath = join75(home2, ".switchroom", "vault-auto-unlock");
82476
83161
  if (!existsSync74(autoUnlockPath)) {
82477
83162
  writeFileSync37(autoUnlockPath, "", { mode: 384 });
82478
83163
  }
82479
- const auditLogPath = join74(home2, ".switchroom", "vault-audit.log");
83164
+ const auditLogPath = join75(home2, ".switchroom", "vault-audit.log");
82480
83165
  if (!existsSync74(auditLogPath)) {
82481
83166
  writeFileSync37(auditLogPath, "", { mode: 420 });
82482
83167
  }
82483
- const grantsDbPath = join74(home2, ".switchroom", "vault-grants.db");
83168
+ const grantsDbPath = join75(home2, ".switchroom", "vault-grants.db");
82484
83169
  if (!existsSync74(grantsDbPath)) {
82485
83170
  writeFileSync37(grantsDbPath, "", { mode: 384 });
82486
83171
  }
82487
- const hostdAuditLogPath = join74(home2, ".switchroom", "host-control-audit.log");
83172
+ const hostdAuditLogPath = join75(home2, ".switchroom", "host-control-audit.log");
82488
83173
  if (!existsSync74(hostdAuditLogPath)) {
82489
83174
  writeFileSync37(hostdAuditLogPath, "", { mode: 420 });
82490
83175
  }
82491
83176
  for (const name of Object.keys(config.agents)) {
82492
- const tokenPath = join74(home2, ".switchroom", "agents", name, ".vault-token");
83177
+ const tokenPath = join75(home2, ".switchroom", "agents", name, ".vault-token");
82493
83178
  if (!existsSync74(tokenPath)) {
82494
83179
  writeFileSync37(tokenPath, "", { mode: 384 });
82495
83180
  }
@@ -82498,15 +83183,15 @@ async function ensureHostMountSources(config) {
82498
83183
  chownSync7(tokenPath, uid, uid);
82499
83184
  } catch {}
82500
83185
  }
82501
- const fleetDir = join74(home2, ".switchroom", "fleet");
83186
+ const fleetDir = join75(home2, ".switchroom", "fleet");
82502
83187
  await mkdir2(fleetDir, { recursive: true });
82503
- const invariantsPath = join74(fleetDir, "switchroom-invariants.md");
83188
+ const invariantsPath = join75(fleetDir, "switchroom-invariants.md");
82504
83189
  const invariantsCanonical = renderFleetInvariants();
82505
83190
  const invariantsCurrent = existsSync74(invariantsPath) ? readFileSync63(invariantsPath, "utf-8") : null;
82506
83191
  if (invariantsCurrent !== invariantsCanonical) {
82507
83192
  writeFileSync37(invariantsPath, invariantsCanonical, { mode: 420 });
82508
83193
  }
82509
- const fleetClaudePath = join74(fleetDir, "CLAUDE.md");
83194
+ const fleetClaudePath = join75(fleetDir, "CLAUDE.md");
82510
83195
  if (!existsSync74(fleetClaudePath)) {
82511
83196
  writeFileSync37(fleetClaudePath, [
82512
83197
  "# Switchroom fleet defaults",
@@ -82589,10 +83274,10 @@ function detectAndReportLegacyGdriveSlots(vaultPath) {
82589
83274
  `));
82590
83275
  }
82591
83276
  }
82592
- function writeInstallTypeCache(homeDir = homedir43()) {
83277
+ function writeInstallTypeCache(homeDir = homedir44()) {
82593
83278
  const ctx = detectInstallType();
82594
- const dir = join74(homeDir, ".switchroom");
82595
- const out = join74(dir, "install-type.json");
83279
+ const dir = join75(homeDir, ".switchroom");
83280
+ const out = join75(dir, "install-type.json");
82596
83281
  const tmp = `${out}.tmp`;
82597
83282
  mkdirSync42(dir, { recursive: true });
82598
83283
  const payload = {
@@ -82641,14 +83326,14 @@ Applying switchroom config...
82641
83326
  writeOut(source_default.green(` + ${name}`) + source_default.gray(` (${agentConfig.extends ?? "default"}) \u2014 ${detail}
82642
83327
  `));
82643
83328
  try {
82644
- installUpdatePromptHook(join74(agentsDir, name));
83329
+ installUpdatePromptHook(join75(agentsDir, name));
82645
83330
  } catch (hookErr) {
82646
83331
  writeOut(source_default.gray(` (update-prompt hook install failed for ${name}: ${hookErr.message})
82647
83332
  `));
82648
83333
  }
82649
83334
  try {
82650
83335
  const uid = allocateAgentUid(name);
82651
- alignAgentUid(name, join74(agentsDir, name), uid, {
83336
+ alignAgentUid(name, join75(agentsDir, name), uid, {
82652
83337
  confirm: !options.nonInteractive,
82653
83338
  writeOut
82654
83339
  });
@@ -82685,7 +83370,7 @@ Applying switchroom config...
82685
83370
  for (const name of agentNames) {
82686
83371
  try {
82687
83372
  const uid = allocateAgentUid(name);
82688
- alignAgentUid(name, join74(agentsDir, name), uid, {
83373
+ alignAgentUid(name, join75(agentsDir, name), uid, {
82689
83374
  confirm: !options.nonInteractive,
82690
83375
  writeOut
82691
83376
  });
@@ -82699,7 +83384,7 @@ Applying switchroom config...
82699
83384
  }
82700
83385
  const vaultPathConfigured = config.vault?.path;
82701
83386
  const customVaultPath = vaultPathConfigured ? resolvePath(vaultPathConfigured) : undefined;
82702
- const migrationResult = migrateVaultLayout(homedir43(), {
83387
+ const migrationResult = migrateVaultLayout(homedir44(), {
82703
83388
  customVaultPath
82704
83389
  });
82705
83390
  switch (migrationResult.kind) {
@@ -82725,7 +83410,7 @@ Applying switchroom config...
82725
83410
  writeErr(formatDivergentRecoveryMessage(migrationResult.details));
82726
83411
  process.exit(4);
82727
83412
  }
82728
- const postMigrationInspect = inspectVaultLayout(homedir43());
83413
+ const postMigrationInspect = inspectVaultLayout(homedir44());
82729
83414
  const acceptable = [
82730
83415
  "no-vault",
82731
83416
  "already-migrated",
@@ -82740,7 +83425,7 @@ Applying switchroom config...
82740
83425
  `));
82741
83426
  process.exit(5);
82742
83427
  }
82743
- const vaultDir = resolveVaultBindMountDir(homedir43(), {
83428
+ const vaultDir = resolveVaultBindMountDir(homedir44(), {
82744
83429
  migrationKind: migrationResult.kind,
82745
83430
  customVaultPath
82746
83431
  });
@@ -82779,7 +83464,7 @@ Wrote `) + displayComposePath + source_default.gray(` (${composeBytes} bytes)
82779
83464
  writeOut(source_default.gray(` (If pull returns 401, login to ghcr.io first: see docs/operators/install.md#ghcr-auth)
82780
83465
  `));
82781
83466
  if (process.geteuid?.() === 0 && operatorUid !== undefined) {
82782
- const restored = restoreOperatorOwnership(homedir43(), operatorUid);
83467
+ const restored = restoreOperatorOwnership(homedir44(), operatorUid);
82783
83468
  if (restored.length > 0) {
82784
83469
  writeOut(source_default.gray(` Restored operator ownership of ${restored.length} ~/.switchroom path(s)
82785
83470
  `));
@@ -82849,7 +83534,7 @@ function findUnwritableAgentDirs(config, opts) {
82849
83534
  const targets = opts.only ? [opts.only] : Object.keys(config.agents ?? {});
82850
83535
  const unwritable = [];
82851
83536
  for (const name of targets) {
82852
- const startSh = join74(agentsDir, name, "start.sh");
83537
+ const startSh = join75(agentsDir, name, "start.sh");
82853
83538
  if (!existsSync74(startSh))
82854
83539
  continue;
82855
83540
  try {
@@ -83029,8 +83714,8 @@ function runRedactStdin() {
83029
83714
 
83030
83715
  // src/cli/status-ask.ts
83031
83716
  import { readFileSync as readFileSync64, existsSync as existsSync75, readdirSync as readdirSync27 } from "node:fs";
83032
- import { join as join75 } from "node:path";
83033
- import { homedir as homedir44 } from "node:os";
83717
+ import { join as join76 } from "node:path";
83718
+ import { homedir as homedir45 } from "node:os";
83034
83719
 
83035
83720
  // src/status-ask/report.ts
83036
83721
  function parseJsonl(content) {
@@ -83365,7 +84050,7 @@ function resolveSources(explicitPath) {
83365
84050
  const config = loadConfig();
83366
84051
  agentsDir = resolveAgentsDir(config);
83367
84052
  } catch {
83368
- agentsDir = join75(homedir44(), ".switchroom", "agents");
84053
+ agentsDir = join76(homedir45(), ".switchroom", "agents");
83369
84054
  }
83370
84055
  if (!existsSync75(agentsDir))
83371
84056
  return [];
@@ -83377,7 +84062,7 @@ function resolveSources(explicitPath) {
83377
84062
  return [];
83378
84063
  }
83379
84064
  for (const name of entries) {
83380
- const path8 = join75(agentsDir, name, "runtime-metrics.jsonl");
84065
+ const path8 = join76(agentsDir, name, "runtime-metrics.jsonl");
83381
84066
  if (existsSync75(path8)) {
83382
84067
  sources.push({ path: path8, agent: name });
83383
84068
  }
@@ -83415,21 +84100,21 @@ import {
83415
84100
  unlinkSync as unlinkSync14,
83416
84101
  writeSync as writeSync8
83417
84102
  } from "node:fs";
83418
- import { join as join76, resolve as resolve46 } from "node:path";
84103
+ import { join as join77, resolve as resolve46 } from "node:path";
83419
84104
  var STAGING_SUBDIR = ".staging";
83420
84105
  function overlayPathsFor(agent, opts = {}) {
83421
84106
  const base = opts.root ? resolve46(opts.root, agent) : resolve46(resolveDualPath(`~/.switchroom/agents/${agent}`));
83422
- const scheduleDir = join76(base, "schedule.d");
83423
- const scheduleStagingDir = join76(scheduleDir, STAGING_SUBDIR);
83424
- const skillsDir = join76(base, "skills.d");
83425
- 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);
83426
84111
  return {
83427
84112
  agentRoot: base,
83428
84113
  scheduleDir,
83429
84114
  scheduleStagingDir,
83430
84115
  skillsDir,
83431
84116
  skillsStagingDir,
83432
- lockPath: join76(base, ".lock"),
84117
+ lockPath: join77(base, ".lock"),
83433
84118
  stagingDir: scheduleStagingDir
83434
84119
  };
83435
84120
  }
@@ -83483,8 +84168,8 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
83483
84168
  const paths = overlayPathsFor(agent, opts);
83484
84169
  return withAgentLock(paths, () => {
83485
84170
  ensureDirs(paths);
83486
- const stagingPath = join76(paths.scheduleStagingDir, `${slug}.yaml`);
83487
- const finalPath = join76(paths.scheduleDir, `${slug}.yaml`);
84171
+ const stagingPath = join77(paths.scheduleStagingDir, `${slug}.yaml`);
84172
+ const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
83488
84173
  const fd = openSync13(stagingPath, "w", 384);
83489
84174
  try {
83490
84175
  writeSync8(fd, yamlText);
@@ -83500,8 +84185,8 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
83500
84185
  const paths = overlayPathsFor(agent, opts);
83501
84186
  return withAgentLock(paths, () => {
83502
84187
  ensureSkillsDirs(paths);
83503
- const stagingPath = join76(paths.skillsStagingDir, `${slug}.yaml`);
83504
- const finalPath = join76(paths.skillsDir, `${slug}.yaml`);
84188
+ const stagingPath = join77(paths.skillsStagingDir, `${slug}.yaml`);
84189
+ const finalPath = join77(paths.skillsDir, `${slug}.yaml`);
83505
84190
  const fd = openSync13(stagingPath, "w", 384);
83506
84191
  try {
83507
84192
  writeSync8(fd, yamlText);
@@ -83516,7 +84201,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
83516
84201
  function deleteSkillsOverlayEntry(agent, slug, opts = {}) {
83517
84202
  const paths = overlayPathsFor(agent, opts);
83518
84203
  return withAgentLock(paths, () => {
83519
- const finalPath = join76(paths.skillsDir, `${slug}.yaml`);
84204
+ const finalPath = join77(paths.skillsDir, `${slug}.yaml`);
83520
84205
  if (!existsSync76(finalPath))
83521
84206
  return false;
83522
84207
  unlinkSync14(finalPath);
@@ -83531,7 +84216,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
83531
84216
  for (const name of readdirSync28(paths.skillsDir)) {
83532
84217
  if (!/\.ya?ml$/i.test(name))
83533
84218
  continue;
83534
- const full = join76(paths.skillsDir, name);
84219
+ const full = join77(paths.skillsDir, name);
83535
84220
  try {
83536
84221
  const raw = readFileSync65(full, "utf-8");
83537
84222
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -83543,7 +84228,7 @@ function listSkillsOverlayEntries(agent, opts = {}) {
83543
84228
  function deleteOverlayEntry(agent, slug, opts = {}) {
83544
84229
  const paths = overlayPathsFor(agent, opts);
83545
84230
  return withAgentLock(paths, () => {
83546
- const finalPath = join76(paths.scheduleDir, `${slug}.yaml`);
84231
+ const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
83547
84232
  if (!existsSync76(finalPath))
83548
84233
  return false;
83549
84234
  unlinkSync14(finalPath);
@@ -83558,7 +84243,7 @@ function listOverlayEntries(agent, opts = {}) {
83558
84243
  for (const name of readdirSync28(paths.scheduleDir)) {
83559
84244
  if (!/\.ya?ml$/i.test(name))
83560
84245
  continue;
83561
- const full = join76(paths.scheduleDir, name);
84246
+ const full = join77(paths.scheduleDir, name);
83562
84247
  try {
83563
84248
  const raw = readFileSync65(full, "utf-8");
83564
84249
  const slug = name.replace(/\.ya?ml$/i, "");
@@ -83714,12 +84399,12 @@ import {
83714
84399
  writeFileSync as writeFileSync38,
83715
84400
  writeSync as writeSync9
83716
84401
  } from "node:fs";
83717
- import { join as join77 } from "node:path";
84402
+ import { join as join78 } from "node:path";
83718
84403
  import { randomBytes as randomBytes14 } from "node:crypto";
83719
84404
  var STAGE_ID_PREFIX = "cap_";
83720
84405
  function pendingDir(agent, opts = {}) {
83721
84406
  const paths = overlayPathsFor(agent, opts);
83722
- return join77(paths.scheduleDir, ".pending");
84407
+ return join78(paths.scheduleDir, ".pending");
83723
84408
  }
83724
84409
  function ensurePendingDir(agent, opts = {}) {
83725
84410
  const dir = pendingDir(agent, opts);
@@ -83732,8 +84417,8 @@ function newStageId() {
83732
84417
  function stagePendingScheduleEntry(opts) {
83733
84418
  const dir = ensurePendingDir(opts.agent, { root: opts.root });
83734
84419
  const stageId = opts.stageId ?? newStageId();
83735
- const yamlPath = join77(dir, `${stageId}.yaml`);
83736
- const metaPath = join77(dir, `${stageId}.meta.json`);
84420
+ const yamlPath = join78(dir, `${stageId}.yaml`);
84421
+ const metaPath = join78(dir, `${stageId}.meta.json`);
83737
84422
  const meta = {
83738
84423
  v: 1,
83739
84424
  stage_id: stageId,
@@ -83767,8 +84452,8 @@ function listPendingScheduleEntries(agent, opts = {}) {
83767
84452
  if (!name.endsWith(".meta.json"))
83768
84453
  continue;
83769
84454
  const stageId = name.slice(0, -".meta.json".length);
83770
- const metaPath = join77(dir, name);
83771
- const yamlPath = join77(dir, `${stageId}.yaml`);
84455
+ const metaPath = join78(dir, name);
84456
+ const yamlPath = join78(dir, `${stageId}.yaml`);
83772
84457
  if (!existsSync77(yamlPath))
83773
84458
  continue;
83774
84459
  try {
@@ -83787,7 +84472,7 @@ function commitPendingScheduleEntry(opts) {
83787
84472
  return { committed: false, reason: "not_found" };
83788
84473
  const slug = match.meta.entry.name ?? match.stageId;
83789
84474
  const paths = overlayPathsFor(opts.agent, { root: opts.root });
83790
- const finalPath = join77(paths.scheduleDir, `${slug}.yaml`);
84475
+ const finalPath = join78(paths.scheduleDir, `${slug}.yaml`);
83791
84476
  if (existsSync77(finalPath)) {
83792
84477
  return { committed: false, reason: "slug_collision" };
83793
84478
  }
@@ -84420,7 +85105,7 @@ var import_yaml21 = __toESM(require_dist(), 1);
84420
85105
  import { existsSync as existsSync79 } from "node:fs";
84421
85106
  init_reconcile_default_skills();
84422
85107
  var import_yaml22 = __toESM(require_dist(), 1);
84423
- import { join as join78 } from "node:path";
85108
+ import { join as join79 } from "node:path";
84424
85109
  var MAX_SKILLS_PER_AGENT = 20;
84425
85110
  var V1_ALLOWED_SOURCE_PREFIX = "bundled:";
84426
85111
  function exitCodeFor2(code) {
@@ -84495,7 +85180,7 @@ function skillInstall(opts) {
84495
85180
  return err("E_SKILL_QUOTA_EXCEEDED", `agent ${agent} already has ${used} overlay-installed skills (cap ${MAX_SKILLS_PER_AGENT})`);
84496
85181
  }
84497
85182
  const poolDir = opts.bundledSkillsPoolDir ?? getBundledSkillsPoolDir();
84498
- const skillPath = join78(poolDir, skillName);
85183
+ const skillPath = join79(poolDir, skillName);
84499
85184
  if (!existsSync79(skillPath)) {
84500
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.`);
84501
85186
  }
@@ -84673,8 +85358,8 @@ import {
84673
85358
  statSync as statSync32,
84674
85359
  writeFileSync as writeFileSync39
84675
85360
  } from "node:fs";
84676
- import { tmpdir as tmpdir5, homedir as homedir45 } from "node:os";
84677
- 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";
84678
85363
  import { spawnSync as spawnSync12 } from "node:child_process";
84679
85364
 
84680
85365
  // src/cli/skill-common.ts
@@ -84868,10 +85553,10 @@ function scanForClaudeP2(content) {
84868
85553
  function resolveSkillsPoolDir2(override) {
84869
85554
  const raw = override ?? "~/.switchroom/skills";
84870
85555
  if (raw.startsWith("~/")) {
84871
- return join79(homedir45(), raw.slice(2));
85556
+ return join80(homedir46(), raw.slice(2));
84872
85557
  }
84873
85558
  if (raw === "~")
84874
- return homedir45();
85559
+ return homedir46();
84875
85560
  return resolve47(raw);
84876
85561
  }
84877
85562
  function readStdinSync() {
@@ -84907,7 +85592,7 @@ function loadFromDir(dir) {
84907
85592
  const walk2 = (sub) => {
84908
85593
  const entries = readdirSync30(sub, { withFileTypes: true });
84909
85594
  for (const ent of entries) {
84910
- const full = join79(sub, ent.name);
85595
+ const full = join80(sub, ent.name);
84911
85596
  const rel = relative2(abs, full);
84912
85597
  if (ent.isSymbolicLink()) {
84913
85598
  fail3(`refusing to read symlink inside --from dir: ${rel}`);
@@ -84942,7 +85627,7 @@ function loadFromTarball(tarPath) {
84942
85627
  fail3(`tarball contains disallowed path: ${JSON.stringify(entry)} \u2014 ` + `refusing to extract before any file is written`);
84943
85628
  }
84944
85629
  }
84945
- const staging = mkdtempSync5(join79(tmpdir5(), "skill-apply-extract-"));
85630
+ const staging = mkdtempSync5(join80(tmpdir5(), "skill-apply-extract-"));
84946
85631
  try {
84947
85632
  const flags = isGz ? ["-xzf"] : ["-xf"];
84948
85633
  const r = spawnSync12("tar", [
@@ -85028,8 +85713,8 @@ function validatePayload(name, files) {
85028
85713
  errors2.push(`${path8} fails \`bash -n\` syntax check: ${(r.stderr ?? "").trim()}`);
85029
85714
  }
85030
85715
  } else if (PY_SCRIPT_RE2.test(path8)) {
85031
- const tmp = mkdtempSync5(join79(tmpdir5(), "skill-apply-py-"));
85032
- const tmpPy = join79(tmp, "check.py");
85716
+ const tmp = mkdtempSync5(join80(tmpdir5(), "skill-apply-py-"));
85717
+ const tmpPy = join80(tmp, "check.py");
85033
85718
  try {
85034
85719
  writeFileSync39(tmpPy, content);
85035
85720
  const r = spawnSync12("python3", ["-m", "py_compile", tmpPy], {
@@ -85052,7 +85737,7 @@ function diffSummary(currentDir, files) {
85052
85737
  if (existsSync80(currentDir)) {
85053
85738
  const walk2 = (sub) => {
85054
85739
  for (const ent of readdirSync30(sub, { withFileTypes: true })) {
85055
- const full = join79(sub, ent.name);
85740
+ const full = join80(sub, ent.name);
85056
85741
  const rel = relative2(currentDir, full);
85057
85742
  if (ent.isDirectory()) {
85058
85743
  walk2(full);
@@ -85088,7 +85773,7 @@ function writePayload(poolDir, name, files) {
85088
85773
  if (!existsSync80(poolDir)) {
85089
85774
  mkdirSync45(poolDir, { recursive: true, mode: 493 });
85090
85775
  }
85091
- const target = join79(poolDir, name);
85776
+ const target = join80(poolDir, name);
85092
85777
  let targetIsSymlink = false;
85093
85778
  try {
85094
85779
  const st = lstatSync8(target);
@@ -85099,11 +85784,11 @@ function writePayload(poolDir, name, files) {
85099
85784
  if (targetIsSymlink) {
85100
85785
  fail3(`refusing to overwrite symlink at ${target}; investigate manually`);
85101
85786
  }
85102
- const staging = mkdtempSync5(join79(poolDir, `.skill-apply-stage-${name}-`));
85787
+ const staging = mkdtempSync5(join80(poolDir, `.skill-apply-stage-${name}-`));
85103
85788
  let oldRename = null;
85104
85789
  try {
85105
85790
  for (const [path8, content] of Object.entries(files)) {
85106
- const full = join79(staging, path8);
85791
+ const full = join80(staging, path8);
85107
85792
  mkdirSync45(dirname23(full), { recursive: true, mode: 493 });
85108
85793
  const fd = openSync15(full, "wx");
85109
85794
  try {
@@ -85181,7 +85866,7 @@ function registerSkillCommand(program3) {
85181
85866
  }
85182
85867
  const config = loadConfig();
85183
85868
  const poolDir = resolveSkillsPoolDir2(config.switchroom?.skills_dir);
85184
- const currentDir = join79(poolDir, name);
85869
+ const currentDir = join80(poolDir, name);
85185
85870
  console.log(source_default.bold(`Skill: ${name}`) + source_default.gray(` (${Object.keys(files).length} files, ${sumBytes(files)} bytes)`));
85186
85871
  console.log(source_default.bold("Diff vs current pool content:"));
85187
85872
  console.log(diffSummary(currentDir, files));
@@ -85225,8 +85910,8 @@ import {
85225
85910
  utimesSync,
85226
85911
  writeFileSync as writeFileSync40
85227
85912
  } from "node:fs";
85228
- import { dirname as dirname24, join as join80, relative as relative3, resolve as resolve48 } from "node:path";
85229
- 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";
85230
85915
  import { spawnSync as spawnSync13 } from "node:child_process";
85231
85916
  init_helpers();
85232
85917
  init_source();
@@ -85236,10 +85921,10 @@ var TRASH_TTL_MS = 24 * 60 * 60 * 1000;
85236
85921
  var PERSONAL_SKILLS_SUBPATH = "personal-skills";
85237
85922
  function resolveConfigSkillsDir(agent) {
85238
85923
  const override = process.env.SWITCHROOM_CONFIG_DIR;
85239
- const candidate = override ? resolve48(override) : join80(homedir46(), ".switchroom-config");
85924
+ const candidate = override ? resolve48(override) : join81(homedir47(), ".switchroom-config");
85240
85925
  if (!existsSync81(candidate))
85241
85926
  return null;
85242
- return join80(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
85927
+ return join81(candidate, "agents", agent, PERSONAL_SKILLS_SUBPATH);
85243
85928
  }
85244
85929
  var MIRROR_PRIOR_TTL_MS = 24 * 60 * 60 * 1000;
85245
85930
  function sweepMirrorPriors(configSkillsRoot) {
@@ -85257,7 +85942,7 @@ function sweepMirrorPriors(configSkillsRoot) {
85257
85942
  if (now - ts < MIRROR_PRIOR_TTL_MS)
85258
85943
  continue;
85259
85944
  try {
85260
- rmSync17(join80(configSkillsRoot, ent), { recursive: true, force: true });
85945
+ rmSync17(join81(configSkillsRoot, ent), { recursive: true, force: true });
85261
85946
  } catch {}
85262
85947
  }
85263
85948
  } catch {}
@@ -85266,7 +85951,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
85266
85951
  const configSkillsRoot = resolveConfigSkillsDir(agent);
85267
85952
  if (!configSkillsRoot)
85268
85953
  return;
85269
- const dest = join80(configSkillsRoot, name);
85954
+ const dest = join81(configSkillsRoot, name);
85270
85955
  try {
85271
85956
  if (liveSkillDir !== null) {
85272
85957
  try {
@@ -85281,19 +85966,19 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
85281
85966
  if (liveSkillDir === null) {
85282
85967
  sweepMirrorPriors(configSkillsRoot);
85283
85968
  if (existsSync81(dest)) {
85284
- const trash = join80(configSkillsRoot, `.${name}-trash-${Date.now()}`);
85969
+ const trash = join81(configSkillsRoot, `.${name}-trash-${Date.now()}`);
85285
85970
  renameSync18(dest, trash);
85286
85971
  }
85287
85972
  return;
85288
85973
  }
85289
85974
  mkdirSync46(configSkillsRoot, { recursive: true, mode: 493 });
85290
85975
  sweepMirrorPriors(configSkillsRoot);
85291
- const staging = mkdtempSync6(join80(configSkillsRoot, `.${name}-staging-`));
85976
+ const staging = mkdtempSync6(join81(configSkillsRoot, `.${name}-staging-`));
85292
85977
  const walk2 = (src, dst) => {
85293
85978
  mkdirSync46(dst, { recursive: true, mode: 493 });
85294
85979
  for (const ent of readdirSync31(src, { withFileTypes: true })) {
85295
- const s = join80(src, ent.name);
85296
- const d = join80(dst, ent.name);
85980
+ const s = join81(src, ent.name);
85981
+ const d = join81(dst, ent.name);
85297
85982
  if (ent.isSymbolicLink())
85298
85983
  continue;
85299
85984
  if (ent.isDirectory())
@@ -85305,7 +85990,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
85305
85990
  };
85306
85991
  walk2(liveSkillDir, staging);
85307
85992
  if (existsSync81(dest)) {
85308
- const prior = join80(configSkillsRoot, `.${name}-prior-${Date.now()}`);
85993
+ const prior = join81(configSkillsRoot, `.${name}-prior-${Date.now()}`);
85309
85994
  renameSync18(dest, prior);
85310
85995
  }
85311
85996
  renameSync18(staging, dest);
@@ -85332,13 +86017,13 @@ function resolveAgent(opts) {
85332
86017
  function resolveAgentsRoot(opts) {
85333
86018
  if (opts.root)
85334
86019
  return resolve48(opts.root);
85335
- return join80(homedir46(), ".switchroom", "agents");
86020
+ return join81(homedir47(), ".switchroom", "agents");
85336
86021
  }
85337
86022
  function personalSkillDir(agentsRoot, agent, name) {
85338
- return join80(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
86023
+ return join81(agentsRoot, agent, ".claude", "skills", PERSONAL_PREFIX + name);
85339
86024
  }
85340
86025
  function trashDir(agentsRoot, agent) {
85341
- return join80(agentsRoot, agent, ".claude", TRASH_DIRNAME);
86026
+ return join81(agentsRoot, agent, ".claude", TRASH_DIRNAME);
85342
86027
  }
85343
86028
  function readStdinSync2() {
85344
86029
  const chunks = [];
@@ -85368,7 +86053,7 @@ function loadFromDir2(dir) {
85368
86053
  const files = {};
85369
86054
  const walk2 = (sub) => {
85370
86055
  for (const ent of readdirSync31(sub, { withFileTypes: true })) {
85371
- const full = join80(sub, ent.name);
86056
+ const full = join81(sub, ent.name);
85372
86057
  if (ent.isSymbolicLink()) {
85373
86058
  fail4(`refusing to read symlink in --from dir: ${relative3(abs, full)}`);
85374
86059
  }
@@ -85421,8 +86106,8 @@ function behavioralValidate(files) {
85421
86106
  errors2.push(`${path8} fails \`bash -n\`: ${(r.stderr ?? "").trim()}`);
85422
86107
  }
85423
86108
  } else if (PY_SCRIPT_RE.test(path8)) {
85424
- const tmp = mkdtempSync6(join80(tmpdir6(), "skill-personal-py-"));
85425
- const tmpPy = join80(tmp, "check.py");
86109
+ const tmp = mkdtempSync6(join81(tmpdir6(), "skill-personal-py-"));
86110
+ const tmpPy = join81(tmp, "check.py");
85426
86111
  try {
85427
86112
  writeFileSync40(tmpPy, content);
85428
86113
  const r = spawnSync13("python3", ["-m", "py_compile", tmpPy], {
@@ -85446,7 +86131,7 @@ function sweepTrash(agentsRoot, agent) {
85446
86131
  for (const ent of readdirSync31(trash, { withFileTypes: true })) {
85447
86132
  if (!ent.isDirectory())
85448
86133
  continue;
85449
- const entPath = join80(trash, ent.name);
86134
+ const entPath = join81(trash, ent.name);
85450
86135
  try {
85451
86136
  const st = statSync33(entPath);
85452
86137
  if (now - st.mtimeMs > TRASH_TTL_MS) {
@@ -85467,11 +86152,11 @@ function writePersonalSkill(targetDir, files) {
85467
86152
  fail4(`refusing to overwrite symlink at ${targetDir}; investigate manually`);
85468
86153
  }
85469
86154
  mkdirSync46(dirname24(targetDir), { recursive: true, mode: 493 });
85470
- const staging = mkdtempSync6(join80(dirname24(targetDir), `.skill-personal-stage-`));
86155
+ const staging = mkdtempSync6(join81(dirname24(targetDir), `.skill-personal-stage-`));
85471
86156
  let oldRename = null;
85472
86157
  try {
85473
86158
  for (const [path8, content] of Object.entries(files)) {
85474
- const full = join80(staging, path8);
86159
+ const full = join81(staging, path8);
85475
86160
  mkdirSync46(dirname24(full), { recursive: true, mode: 493 });
85476
86161
  const fd = openSync16(full, "wx");
85477
86162
  try {
@@ -85604,10 +86289,10 @@ function editPersonalAction(name, opts) {
85604
86289
  }
85605
86290
  var CLONE_SOURCE_RE = /^(shared|bundled):([a-z0-9][a-z0-9_-]{0,62})$/;
85606
86291
  function defaultSharedRoot() {
85607
- return join80(homedir46(), ".switchroom", "skills");
86292
+ return join81(homedir47(), ".switchroom", "skills");
85608
86293
  }
85609
86294
  function defaultBundledRoot() {
85610
- return join80(homedir46(), ".switchroom", "skills", "_bundled");
86295
+ return join81(homedir47(), ".switchroom", "skills", "_bundled");
85611
86296
  }
85612
86297
  function resolveCloneSource(source, opts) {
85613
86298
  const m = CLONE_SOURCE_RE.exec(source);
@@ -85617,7 +86302,7 @@ function resolveCloneSource(source, opts) {
85617
86302
  const tier = m[1];
85618
86303
  const slug = m[2];
85619
86304
  const root = tier === "bundled" ? opts.bundledRoot ?? defaultBundledRoot() : opts.sharedRoot ?? defaultSharedRoot();
85620
- const dir = join80(root, slug);
86305
+ const dir = join81(root, slug);
85621
86306
  if (!existsSync81(dir)) {
85622
86307
  fail4(`clone source ${JSON.stringify(source)} not found at ${dir}; ` + `check \`switchroom skill search --tier ${tier}\``, 1);
85623
86308
  }
@@ -85633,7 +86318,7 @@ function readSourceFiles(dir) {
85633
86318
  const skipped = [];
85634
86319
  const walk2 = (sub) => {
85635
86320
  for (const ent of readdirSync31(sub, { withFileTypes: true })) {
85636
- const full = join80(sub, ent.name);
86321
+ const full = join81(sub, ent.name);
85637
86322
  if (ent.isSymbolicLink()) {
85638
86323
  continue;
85639
86324
  }
@@ -85744,7 +86429,7 @@ function removePersonalAction(name, opts) {
85744
86429
  const trashRoot = trashDir(agentsRoot, agent);
85745
86430
  mkdirSync46(trashRoot, { recursive: true, mode: 493 });
85746
86431
  const ts = Date.now();
85747
- const trashTarget = join80(trashRoot, `${name}-${ts}`);
86432
+ const trashTarget = join81(trashRoot, `${name}-${ts}`);
85748
86433
  renameSync18(target, trashTarget);
85749
86434
  const now = new Date(ts);
85750
86435
  utimesSync(trashTarget, now, now);
@@ -85763,7 +86448,7 @@ function listPersonalAction(opts) {
85763
86448
  const agent = resolveAgent(opts);
85764
86449
  const agentsRoot = resolveAgentsRoot(opts);
85765
86450
  sweepTrash(agentsRoot, agent);
85766
- const skillsDir = join80(agentsRoot, agent, ".claude", "skills");
86451
+ const skillsDir = join81(agentsRoot, agent, ".claude", "skills");
85767
86452
  const personal = [];
85768
86453
  if (existsSync81(skillsDir)) {
85769
86454
  for (const ent of readdirSync31(skillsDir, { withFileTypes: true })) {
@@ -85772,7 +86457,7 @@ function listPersonalAction(opts) {
85772
86457
  if (!ent.name.startsWith(PERSONAL_PREFIX))
85773
86458
  continue;
85774
86459
  const skillName = ent.name.slice(PERSONAL_PREFIX.length);
85775
- const skillPath = join80(skillsDir, ent.name);
86460
+ const skillPath = join81(skillsDir, ent.name);
85776
86461
  let fileCount = 0;
85777
86462
  let totalBytes = 0;
85778
86463
  const walk2 = (sub) => {
@@ -85780,10 +86465,10 @@ function listPersonalAction(opts) {
85780
86465
  if (e.isFile()) {
85781
86466
  fileCount += 1;
85782
86467
  try {
85783
- totalBytes += statSync33(join80(sub, e.name)).size;
86468
+ totalBytes += statSync33(join81(sub, e.name)).size;
85784
86469
  } catch {}
85785
86470
  } else if (e.isDirectory()) {
85786
- walk2(join80(sub, e.name));
86471
+ walk2(join81(sub, e.name));
85787
86472
  }
85788
86473
  }
85789
86474
  };
@@ -85823,22 +86508,22 @@ function registerSkillPersonalCommands(program3) {
85823
86508
  init_helpers();
85824
86509
  var import_yaml24 = __toESM(require_dist(), 1);
85825
86510
  import { existsSync as existsSync82, readdirSync as readdirSync32, readFileSync as readFileSync70, statSync as statSync34 } from "node:fs";
85826
- import { homedir as homedir47 } from "node:os";
85827
- 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";
85828
86513
  var PERSONAL_PREFIX2 = "personal-";
85829
86514
  var BUNDLED_SUBDIR = "_bundled";
85830
86515
  var AGENT_NAME_RE3 = /^[a-z][a-z0-9_-]{0,62}$/;
85831
86516
  function defaultAgentsRoot() {
85832
- return resolve49(homedir47(), ".switchroom/agents");
86517
+ return resolve49(homedir48(), ".switchroom/agents");
85833
86518
  }
85834
86519
  function defaultSharedRoot2() {
85835
- return resolve49(homedir47(), ".switchroom/skills");
86520
+ return resolve49(homedir48(), ".switchroom/skills");
85836
86521
  }
85837
86522
  function defaultBundledRoot2() {
85838
- return resolve49(homedir47(), ".switchroom/skills/_bundled");
86523
+ return resolve49(homedir48(), ".switchroom/skills/_bundled");
85839
86524
  }
85840
86525
  function readSkillFrontmatter(skillDir) {
85841
- const mdPath = join81(skillDir, "SKILL.md");
86526
+ const mdPath = join82(skillDir, "SKILL.md");
85842
86527
  if (!existsSync82(mdPath))
85843
86528
  return null;
85844
86529
  let content;
@@ -85871,7 +86556,7 @@ function readSkillFrontmatter(skillDir) {
85871
86556
  return { fm: parsed };
85872
86557
  }
85873
86558
  function statSkillMd(skillDir) {
85874
- const mdPath = join81(skillDir, "SKILL.md");
86559
+ const mdPath = join82(skillDir, "SKILL.md");
85875
86560
  try {
85876
86561
  const st = statSync34(mdPath);
85877
86562
  return { size: st.size, mtime: st.mtime.toISOString() };
@@ -85882,7 +86567,7 @@ function statSkillMd(skillDir) {
85882
86567
  function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
85883
86568
  if (!AGENT_NAME_RE3.test(agent))
85884
86569
  return [];
85885
- const skillsDir = join81(agentsRoot, agent, ".claude/skills");
86570
+ const skillsDir = join82(agentsRoot, agent, ".claude/skills");
85886
86571
  if (!existsSync82(skillsDir))
85887
86572
  return [];
85888
86573
  const out = [];
@@ -85895,7 +86580,7 @@ function listPersonalSkills(agent, agentsRoot = defaultAgentsRoot()) {
85895
86580
  for (const ent of entries) {
85896
86581
  if (!ent.startsWith(PERSONAL_PREFIX2))
85897
86582
  continue;
85898
- const dirPath = join81(skillsDir, ent);
86583
+ const dirPath = join82(skillsDir, ent);
85899
86584
  try {
85900
86585
  if (!statSync34(dirPath).isDirectory())
85901
86586
  continue;
@@ -85935,7 +86620,7 @@ function listSharedSkills(sharedRoot = defaultSharedRoot2()) {
85935
86620
  continue;
85936
86621
  if (ent.startsWith("."))
85937
86622
  continue;
85938
- const dirPath = join81(sharedRoot, ent);
86623
+ const dirPath = join82(sharedRoot, ent);
85939
86624
  try {
85940
86625
  if (!statSync34(dirPath).isDirectory())
85941
86626
  continue;
@@ -85971,7 +86656,7 @@ function listBundledSkills(bundledRoot = defaultBundledRoot2()) {
85971
86656
  for (const ent of entries) {
85972
86657
  if (ent.startsWith("."))
85973
86658
  continue;
85974
- const dirPath = join81(bundledRoot, ent);
86659
+ const dirPath = join82(bundledRoot, ent);
85975
86660
  try {
85976
86661
  if (!statSync34(dirPath).isDirectory())
85977
86662
  continue;
@@ -86116,8 +86801,8 @@ function registerHostdMcpCommand(program3) {
86116
86801
  init_source();
86117
86802
  init_helpers();
86118
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";
86119
- import { homedir as homedir48 } from "node:os";
86120
- import { join as join82 } from "node:path";
86804
+ import { homedir as homedir49 } from "node:os";
86805
+ import { join as join83 } from "node:path";
86121
86806
  import { spawnSync as spawnSync15 } from "node:child_process";
86122
86807
  init_audit_reader();
86123
86808
  function resolveHostdImageTag(explicitTag, release) {
@@ -86230,7 +86915,7 @@ networks:
86230
86915
  # operator surface; the daemon's stderr lands in \`docker logs switchroom-hostd\`.
86231
86916
  `;
86232
86917
  }
86233
- function resolveHostdHostHome(env2 = process.env, home2 = homedir48()) {
86918
+ function resolveHostdHostHome(env2 = process.env, home2 = homedir49()) {
86234
86919
  const fromEnv = env2.SWITCHROOM_HOST_HOME?.trim();
86235
86920
  const resolved = fromEnv && fromEnv.length > 0 ? fromEnv : home2;
86236
86921
  if (resolved === "/host-home" || resolved.startsWith("/host-home/")) {
@@ -86241,10 +86926,10 @@ function resolveHostdHostHome(env2 = process.env, home2 = homedir48()) {
86241
86926
  return resolved;
86242
86927
  }
86243
86928
  function hostdDir() {
86244
- return join82(homedir48(), ".switchroom", "hostd");
86929
+ return join83(homedir49(), ".switchroom", "hostd");
86245
86930
  }
86246
86931
  function hostdComposePath() {
86247
- return join82(hostdDir(), "docker-compose.yml");
86932
+ return join83(hostdDir(), "docker-compose.yml");
86248
86933
  }
86249
86934
  function backupExistingCompose() {
86250
86935
  const p = hostdComposePath();
@@ -86355,7 +87040,7 @@ function doStatus() {
86355
87040
  for (const name of readdirSync33(dir)) {
86356
87041
  if (name === "docker-compose.yml" || name.startsWith("docker-compose.yml."))
86357
87042
  continue;
86358
- const sockPath = join82(dir, name, "sock");
87043
+ const sockPath = join83(dir, name, "sock");
86359
87044
  if (existsSync84(sockPath)) {
86360
87045
  const st = statSync35(sockPath);
86361
87046
  if ((st.mode & 61440) === 49152) {
@@ -86447,8 +87132,8 @@ The log is created when hostd handles its first privileged-verb request.`));
86447
87132
  init_source();
86448
87133
  init_helpers();
86449
87134
  import { existsSync as existsSync85, mkdirSync as mkdirSync48, writeFileSync as writeFileSync42, copyFileSync as copyFileSync13 } from "node:fs";
86450
- import { homedir as homedir49 } from "node:os";
86451
- import { join as join83 } from "node:path";
87135
+ import { homedir as homedir50 } from "node:os";
87136
+ import { join as join84 } from "node:path";
86452
87137
  import { spawnSync as spawnSync16 } from "node:child_process";
86453
87138
  function resolveWebImageTag(explicitTag, release) {
86454
87139
  if (explicitTag)
@@ -86533,10 +87218,10 @@ services:
86533
87218
  `;
86534
87219
  }
86535
87220
  function webdDir() {
86536
- return join83(homedir49(), ".switchroom", "web");
87221
+ return join84(homedir50(), ".switchroom", "web");
86537
87222
  }
86538
87223
  function webdComposePath() {
86539
- return join83(webdDir(), "docker-compose.yml");
87224
+ return join84(webdDir(), "docker-compose.yml");
86540
87225
  }
86541
87226
  function backupExistingCompose2() {
86542
87227
  const p = webdComposePath();
@@ -86569,7 +87254,7 @@ async function doInstall2(opts, program3) {
86569
87254
  const cfg = getConfig(program3);
86570
87255
  const imageTag = resolveWebImageTag(opts.tag, cfg.release);
86571
87256
  const yaml = renderWebComposeFile({
86572
- hostHome: homedir49(),
87257
+ hostHome: homedir50(),
86573
87258
  imageTag,
86574
87259
  operatorUid
86575
87260
  });