@phnx-labs/agents-cli 1.20.63 → 1.20.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/CHANGELOG.md +56 -3
  2. package/README.md +46 -2
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/apply.d.ts +12 -0
  5. package/dist/commands/apply.js +274 -0
  6. package/dist/commands/browser.js +2 -2
  7. package/dist/commands/cloud.js +32 -2
  8. package/dist/commands/doctor.js +4 -1
  9. package/dist/commands/exec.js +149 -77
  10. package/dist/commands/feed.d.ts +4 -0
  11. package/dist/commands/feed.js +52 -19
  12. package/dist/commands/hosts.js +44 -6
  13. package/dist/commands/lease.d.ts +23 -0
  14. package/dist/commands/lease.js +201 -0
  15. package/dist/commands/mailboxes.d.ts +20 -0
  16. package/dist/commands/mailboxes.js +390 -0
  17. package/dist/commands/mcp.js +55 -5
  18. package/dist/commands/monitors.d.ts +12 -0
  19. package/dist/commands/monitors.js +740 -0
  20. package/dist/commands/output.js +2 -2
  21. package/dist/commands/routines.js +43 -16
  22. package/dist/commands/secrets.d.ts +16 -0
  23. package/dist/commands/secrets.js +215 -64
  24. package/dist/commands/serve.js +31 -0
  25. package/dist/commands/sessions-export.d.ts +2 -0
  26. package/dist/commands/sessions-export.js +284 -0
  27. package/dist/commands/sessions-import.d.ts +2 -0
  28. package/dist/commands/sessions-import.js +230 -0
  29. package/dist/commands/sessions.d.ts +16 -0
  30. package/dist/commands/sessions.js +40 -6
  31. package/dist/commands/ssh.js +143 -5
  32. package/dist/commands/usage.d.ts +2 -0
  33. package/dist/commands/usage.js +7 -2
  34. package/dist/commands/versions.js +7 -3
  35. package/dist/commands/view.d.ts +27 -1
  36. package/dist/commands/view.js +32 -9
  37. package/dist/commands/webhook.js +10 -2
  38. package/dist/index.js +35 -14
  39. package/dist/lib/agents.d.ts +36 -0
  40. package/dist/lib/agents.js +80 -20
  41. package/dist/lib/auto-dispatch-provider.js +7 -2
  42. package/dist/lib/auto-dispatch.d.ts +3 -0
  43. package/dist/lib/auto-dispatch.js +3 -0
  44. package/dist/lib/browser/chrome.js +2 -2
  45. package/dist/lib/browser/drivers/ssh.js +19 -2
  46. package/dist/lib/cloud/antigravity.js +2 -2
  47. package/dist/lib/cloud/host.d.ts +59 -0
  48. package/dist/lib/cloud/host.js +224 -0
  49. package/dist/lib/cloud/registry.js +4 -0
  50. package/dist/lib/cloud/types.d.ts +6 -4
  51. package/dist/lib/comms-render.d.ts +37 -0
  52. package/dist/lib/comms-render.js +89 -0
  53. package/dist/lib/computer-rpc.js +3 -1
  54. package/dist/lib/crabbox/cli.d.ts +72 -0
  55. package/dist/lib/crabbox/cli.js +162 -9
  56. package/dist/lib/crabbox/runtimes.d.ts +13 -0
  57. package/dist/lib/crabbox/runtimes.js +35 -2
  58. package/dist/lib/daemon.d.ts +20 -4
  59. package/dist/lib/daemon.js +68 -20
  60. package/dist/lib/devices/fleet.d.ts +3 -2
  61. package/dist/lib/devices/fleet.js +9 -0
  62. package/dist/lib/devices/health.d.ts +77 -0
  63. package/dist/lib/devices/health.js +186 -0
  64. package/dist/lib/devices/registry.d.ts +15 -0
  65. package/dist/lib/devices/registry.js +9 -0
  66. package/dist/lib/exec.d.ts +19 -2
  67. package/dist/lib/exec.js +41 -13
  68. package/dist/lib/fleet/apply.d.ts +63 -0
  69. package/dist/lib/fleet/apply.js +214 -0
  70. package/dist/lib/fleet/auth-sync.d.ts +67 -0
  71. package/dist/lib/fleet/auth-sync.js +142 -0
  72. package/dist/lib/fleet/manifest.d.ts +29 -0
  73. package/dist/lib/fleet/manifest.js +127 -0
  74. package/dist/lib/fleet/types.d.ts +129 -0
  75. package/dist/lib/fleet/types.js +13 -0
  76. package/dist/lib/git.d.ts +27 -0
  77. package/dist/lib/git.js +34 -2
  78. package/dist/lib/hosts/dispatch.d.ts +29 -8
  79. package/dist/lib/hosts/dispatch.js +46 -18
  80. package/dist/lib/hosts/passthrough.js +2 -0
  81. package/dist/lib/hosts/providers/devices.d.ts +27 -0
  82. package/dist/lib/hosts/providers/devices.js +98 -0
  83. package/dist/lib/hosts/registry.d.ts +10 -16
  84. package/dist/lib/hosts/registry.js +17 -50
  85. package/dist/lib/hosts/remote-cmd.d.ts +23 -0
  86. package/dist/lib/hosts/remote-cmd.js +71 -0
  87. package/dist/lib/hosts/run-target.d.ts +84 -0
  88. package/dist/lib/hosts/run-target.js +99 -0
  89. package/dist/lib/hosts/types.d.ts +23 -5
  90. package/dist/lib/hosts/types.js +22 -4
  91. package/dist/lib/linear-autoclose.d.ts +30 -0
  92. package/dist/lib/linear-autoclose.js +22 -0
  93. package/dist/lib/mailbox.d.ts +39 -0
  94. package/dist/lib/mailbox.js +112 -0
  95. package/dist/lib/mcp.d.ts +27 -1
  96. package/dist/lib/mcp.js +126 -12
  97. package/dist/lib/monitors/config.d.ts +161 -0
  98. package/dist/lib/monitors/config.js +372 -0
  99. package/dist/lib/monitors/dispatch.d.ts +28 -0
  100. package/dist/lib/monitors/dispatch.js +91 -0
  101. package/dist/lib/monitors/engine.d.ts +61 -0
  102. package/dist/lib/monitors/engine.js +201 -0
  103. package/dist/lib/monitors/sources/command.d.ts +11 -0
  104. package/dist/lib/monitors/sources/command.js +31 -0
  105. package/dist/lib/monitors/sources/device.d.ts +13 -0
  106. package/dist/lib/monitors/sources/device.js +35 -0
  107. package/dist/lib/monitors/sources/file.d.ts +14 -0
  108. package/dist/lib/monitors/sources/file.js +57 -0
  109. package/dist/lib/monitors/sources/http.d.ts +10 -0
  110. package/dist/lib/monitors/sources/http.js +34 -0
  111. package/dist/lib/monitors/sources/index.d.ts +14 -0
  112. package/dist/lib/monitors/sources/index.js +31 -0
  113. package/dist/lib/monitors/sources/poll.d.ts +9 -0
  114. package/dist/lib/monitors/sources/poll.js +9 -0
  115. package/dist/lib/monitors/sources/types.d.ts +18 -0
  116. package/dist/lib/monitors/sources/types.js +9 -0
  117. package/dist/lib/monitors/sources/webhook.d.ts +23 -0
  118. package/dist/lib/monitors/sources/webhook.js +47 -0
  119. package/dist/lib/monitors/sources/ws.d.ts +14 -0
  120. package/dist/lib/monitors/sources/ws.js +45 -0
  121. package/dist/lib/monitors/state.d.ts +69 -0
  122. package/dist/lib/monitors/state.js +144 -0
  123. package/dist/lib/paths.d.ts +13 -0
  124. package/dist/lib/paths.js +26 -4
  125. package/dist/lib/platform/exec.d.ts +16 -0
  126. package/dist/lib/platform/exec.js +17 -0
  127. package/dist/lib/plugins.js +101 -2
  128. package/dist/lib/redact.d.ts +14 -1
  129. package/dist/lib/redact.js +47 -1
  130. package/dist/lib/remote-agents-json.js +7 -1
  131. package/dist/lib/rotate.d.ts +6 -3
  132. package/dist/lib/rotate.js +0 -1
  133. package/dist/lib/routines.d.ts +37 -2
  134. package/dist/lib/routines.js +54 -12
  135. package/dist/lib/runner.d.ts +1 -0
  136. package/dist/lib/runner.js +348 -13
  137. package/dist/lib/sandbox.d.ts +9 -1
  138. package/dist/lib/sandbox.js +11 -2
  139. package/dist/lib/secrets/agent.d.ts +48 -10
  140. package/dist/lib/secrets/agent.js +123 -15
  141. package/dist/lib/secrets/bundles.d.ts +26 -0
  142. package/dist/lib/secrets/bundles.js +59 -8
  143. package/dist/lib/secrets/mcp.js +4 -2
  144. package/dist/lib/secrets/remote.d.ts +17 -0
  145. package/dist/lib/secrets/remote.js +40 -0
  146. package/dist/lib/self-update.d.ts +20 -0
  147. package/dist/lib/self-update.js +54 -1
  148. package/dist/lib/serve/control.d.ts +95 -0
  149. package/dist/lib/serve/control.js +260 -0
  150. package/dist/lib/serve/server.d.ts +35 -1
  151. package/dist/lib/serve/server.js +106 -76
  152. package/dist/lib/serve/stream.d.ts +43 -0
  153. package/dist/lib/serve/stream.js +116 -0
  154. package/dist/lib/serve/token.d.ts +35 -0
  155. package/dist/lib/serve/token.js +85 -0
  156. package/dist/lib/session/bundle.d.ts +164 -0
  157. package/dist/lib/session/bundle.js +200 -0
  158. package/dist/lib/session/remote-bundle.d.ts +12 -0
  159. package/dist/lib/session/remote-bundle.js +61 -0
  160. package/dist/lib/session/remote-list.js +5 -1
  161. package/dist/lib/session/state.d.ts +7 -25
  162. package/dist/lib/session/state.js +16 -6
  163. package/dist/lib/session/sync/agents.d.ts +54 -6
  164. package/dist/lib/session/sync/agents.js +0 -0
  165. package/dist/lib/session/sync/config.js +8 -2
  166. package/dist/lib/session/sync/manifest.d.ts +14 -3
  167. package/dist/lib/session/sync/manifest.js +4 -0
  168. package/dist/lib/session/sync/sync.d.ts +23 -2
  169. package/dist/lib/session/sync/sync.js +177 -74
  170. package/dist/lib/session/types.d.ts +30 -0
  171. package/dist/lib/ssh-tunnel.d.ts +19 -1
  172. package/dist/lib/ssh-tunnel.js +99 -8
  173. package/dist/lib/staleness/detectors/subagents.d.ts +5 -0
  174. package/dist/lib/staleness/detectors/subagents.js +5 -192
  175. package/dist/lib/staleness/writers/subagents.d.ts +10 -0
  176. package/dist/lib/staleness/writers/subagents.js +11 -102
  177. package/dist/lib/startup/command-registry.d.ts +4 -0
  178. package/dist/lib/startup/command-registry.js +9 -0
  179. package/dist/lib/state.d.ts +5 -0
  180. package/dist/lib/state.js +12 -0
  181. package/dist/lib/subagents-registry.d.ts +85 -0
  182. package/dist/lib/subagents-registry.js +393 -0
  183. package/dist/lib/subagents.d.ts +8 -8
  184. package/dist/lib/subagents.js +32 -663
  185. package/dist/lib/sync-umbrella.d.ts +1 -0
  186. package/dist/lib/sync-umbrella.js +14 -3
  187. package/dist/lib/tmux/session.d.ts +7 -0
  188. package/dist/lib/tmux/session.js +3 -1
  189. package/dist/lib/triggers/webhook.d.ts +18 -0
  190. package/dist/lib/triggers/webhook.js +105 -0
  191. package/dist/lib/types.d.ts +35 -1
  192. package/dist/lib/usage.d.ts +42 -3
  193. package/dist/lib/usage.js +163 -21
  194. package/dist/lib/versions.js +14 -11
  195. package/dist/lib/workflows.d.ts +20 -0
  196. package/dist/lib/workflows.js +24 -0
  197. package/package.json +2 -1
@@ -24,7 +24,7 @@ import { fileURLToPath } from 'url';
24
24
  import { randomBytes, createHash } from 'crypto';
25
25
  import { Readable } from 'stream';
26
26
  import { pipeline } from 'stream/promises';
27
- import { sshExec, SSH_OPTS } from './ssh-exec.js';
27
+ import { sshExec, SSH_OPTS, assertValidSshTarget } from './ssh-exec.js';
28
28
  import { backgroundSpawnOptions } from './platform/process.js';
29
29
  import { encodePowerShell } from './browser/drivers/ssh.js';
30
30
  import { getDevice } from './devices/registry.js';
@@ -57,6 +57,18 @@ export function buildTunnelArgs(user, host, localPort, remotePort) {
57
57
  */
58
58
  export function startSSHTunnel(user, host, localPort, remotePort, opts = {}) {
59
59
  return new Promise((resolve, reject) => {
60
+ // `user`/`host` can originate from a browser ssh:// profile or a device
61
+ // record. buildTunnelArgs places `${user}@${host}` before `-N`/SSH_OPTS, so
62
+ // a `-`-leading user would be parsed as an ssh option flag (option
63
+ // injection). Validate at the spawn sink so every caller is covered; reject
64
+ // (rather than throw synchronously) to keep the Promise contract.
65
+ try {
66
+ assertValidSshTarget(`${user}@${host}`);
67
+ }
68
+ catch (err) {
69
+ reject(err);
70
+ return;
71
+ }
60
72
  const args = buildTunnelArgs(user, host, localPort, remotePort);
61
73
  const tunnel = spawn('ssh', args, {
62
74
  stdio: opts.detached ? 'ignore' : ['ignore', 'ignore', 'pipe'],
@@ -91,6 +103,8 @@ export const REMOTE_HELPER_PORT = 8765;
91
103
  export const REMOTE_TASK_NAME = 'AgentsComputerHelper';
92
104
  /** Basename of the cross-published exe under native/computer-win/dist. */
93
105
  export const WIN_HELPER_EXE = 'computer-helper-win.exe';
106
+ /** Basename of the daemon's shared-secret token file under %LOCALAPPDATA%\agents. */
107
+ export const WIN_HELPER_TOKEN_FILE = 'helper-token';
94
108
  /**
95
109
  * Locate a locally built Windows daemon exe — a repo-checkout build output from
96
110
  * `scripts/build-win.sh`, or one bundled next to the package. Local paths are
@@ -222,6 +236,37 @@ export function clearRemoteState(device) {
222
236
  /* already gone */
223
237
  }
224
238
  }
239
+ /**
240
+ * Local file holding the shared-secret token for a device's helper daemon.
241
+ * Written at `setup --host` (0600), read at `start --host` so the RPC client
242
+ * authenticates. The remote daemon reads the same value from its own
243
+ * `--token-file`; the CLI is the source of truth and never round-trips it back.
244
+ */
245
+ export function helperTokenPath(device) {
246
+ return path.join(remoteStateDir(), `${device}.token`);
247
+ }
248
+ export function readHelperToken(device) {
249
+ try {
250
+ const t = fs.readFileSync(helperTokenPath(device), 'utf-8').trim();
251
+ return t.length > 0 ? t : null;
252
+ }
253
+ catch {
254
+ return null;
255
+ }
256
+ }
257
+ export function writeHelperToken(device, token) {
258
+ const dir = remoteStateDir();
259
+ fs.mkdirSync(dir, { recursive: true });
260
+ fs.writeFileSync(helperTokenPath(device), token, { mode: 0o600 });
261
+ }
262
+ export function clearHelperToken(device) {
263
+ try {
264
+ fs.unlinkSync(helperTokenPath(device));
265
+ }
266
+ catch {
267
+ /* already gone */
268
+ }
269
+ }
225
270
  /** Resolve a registered device to its ssh pieces, or throw a clear error. */
226
271
  export async function resolveRemoteDevice(name) {
227
272
  const device = await getDevice(name);
@@ -272,10 +317,26 @@ export function buildVerifyPushScript(remotePath, expectedBytes) {
272
317
  * survives ssh disconnect — the same rationale as the browser WMI launch. The
273
318
  * task is started immediately so the caller need not log out/in.
274
319
  */
275
- export function buildRegisterTaskScript(port, taskName) {
320
+ /**
321
+ * PowerShell that writes the shared-secret token under %LOCALAPPDATA%\agents
322
+ * with an owner-only ACL (inheritance removed, read granted only to the current
323
+ * user) and echoes the resolved path so the caller can pass it to `--token-file`.
324
+ */
325
+ export function buildWriteTokenScript(token) {
326
+ return [
327
+ `$dir = Join-Path $env:LOCALAPPDATA 'agents'`,
328
+ `New-Item -ItemType Directory -Force -Path $dir | Out-Null`,
329
+ `$tok = Join-Path $dir '${WIN_HELPER_TOKEN_FILE}'`,
330
+ `Set-Content -LiteralPath $tok -Value ${psSingleQuote(token)} -NoNewline -Encoding ascii`,
331
+ `icacls $tok /inheritance:r /grant:r ("$($env:USERNAME):(R)") | Out-Null`,
332
+ `Write-Output $tok`,
333
+ ].join('; ');
334
+ }
335
+ export function buildRegisterTaskScript(port, taskName, tokenPath) {
336
+ const argument = `--port ${port} --token-file "${tokenPath}"`;
276
337
  return [
277
338
  `$exe = Join-Path (Join-Path $env:LOCALAPPDATA 'agents') '${WIN_HELPER_EXE}'`,
278
- `$action = New-ScheduledTaskAction -Execute $exe -Argument '--port ${port}'`,
339
+ `$action = New-ScheduledTaskAction -Execute $exe -Argument ${psSingleQuote(argument)}`,
279
340
  `$trigger = New-ScheduledTaskTrigger -AtLogOn`,
280
341
  `$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest`,
281
342
  `$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero)`,
@@ -357,13 +418,28 @@ export async function setupRemoteHelper(name) {
357
418
  if (verify.code !== 0) {
358
419
  throw new Error(`verifying helper exe on '${name}' failed (exit ${verify.code ?? 'null'}): ${verify.stderr.trim() || verify.stdout.trim()}`);
359
420
  }
360
- // Register + start the LOGON task.
361
- const reg = sshExec(target, encodePowerShell(buildRegisterTaskScript(REMOTE_HELPER_PORT, REMOTE_TASK_NAME)), {
421
+ // Provision the auth token: generate it locally, write it on the remote with
422
+ // an owner-only ACL, and register the task with --token-file. The daemon now
423
+ // refuses to start without a token, so a token-less (open-to-any-local-process)
424
+ // daemon can no longer be stood up through the CLI.
425
+ const token = generateToken();
426
+ const tokWrite = sshExec(target, encodePowerShell(buildWriteTokenScript(token)), { timeoutMs: 60_000 });
427
+ if (tokWrite.code !== 0) {
428
+ throw new Error(`writing helper token on '${name}' failed (exit ${tokWrite.code ?? 'null'}): ${tokWrite.stderr.trim() || tokWrite.stdout.trim()}`);
429
+ }
430
+ const remoteTokenPath = tokWrite.stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
431
+ if (!remoteTokenPath) {
432
+ throw new Error(`writing helper token on '${name}' did not return a destination path`);
433
+ }
434
+ // Register + start the LOGON task, pointing it at the token file.
435
+ const reg = sshExec(target, encodePowerShell(buildRegisterTaskScript(REMOTE_HELPER_PORT, REMOTE_TASK_NAME, remoteTokenPath)), {
362
436
  timeoutMs: 60_000,
363
437
  });
364
438
  if (reg.code !== 0) {
365
439
  throw new Error(`registering scheduled task on '${name}' failed (exit ${reg.code ?? 'null'}): ${reg.stderr.trim() || reg.stdout.trim()}`);
366
440
  }
441
+ // Persist the token locally so `start --host` authenticates to the daemon.
442
+ writeHelperToken(name, token);
367
443
  return { target, taskName: REMOTE_TASK_NAME };
368
444
  }
369
445
  /** Reserve a free local TCP port by binding :0 and reading the assigned port. */
@@ -391,9 +467,17 @@ export async function startRemoteTunnel(name) {
391
467
  const tunnelPid = tunnel.pid ?? 0;
392
468
  // Verify the daemon answers through the tunnel before we record it. This is
393
469
  // the real end-to-end check: tunnel up + daemon listening + RPC round-trips.
394
- const token = null; // tunnel-gated; the daemon runs token-less
470
+ // The daemon now requires a token, so the probe must authenticate with the one
471
+ // provisioned at `setup`. A missing token means setup predates auth — guide
472
+ // the user to re-run it rather than silently failing the RPC.
473
+ const token = readHelperToken(name);
395
474
  const prevTcp = process.env.COMPUTER_HELPER_TCP;
475
+ const prevTok = process.env.COMPUTER_HELPER_TOKEN;
396
476
  process.env.COMPUTER_HELPER_TCP = `127.0.0.1:${localPort}`;
477
+ if (token)
478
+ process.env.COMPUTER_HELPER_TOKEN = token;
479
+ else
480
+ delete process.env.COMPUTER_HELPER_TOKEN;
397
481
  const client = openComputerClient();
398
482
  let ok = false;
399
483
  let probeErr = '';
@@ -412,6 +496,10 @@ export async function startRemoteTunnel(name) {
412
496
  delete process.env.COMPUTER_HELPER_TCP;
413
497
  else
414
498
  process.env.COMPUTER_HELPER_TCP = prevTcp;
499
+ if (prevTok === undefined)
500
+ delete process.env.COMPUTER_HELPER_TOKEN;
501
+ else
502
+ process.env.COMPUTER_HELPER_TOKEN = prevTok;
415
503
  }
416
504
  if (!ok) {
417
505
  try {
@@ -419,8 +507,10 @@ export async function startRemoteTunnel(name) {
419
507
  process.kill(tunnelPid);
420
508
  }
421
509
  catch { /* gone */ }
422
- throw new Error(`tunnel to '${name}' opened but the daemon did not answer (${probeErr}). ` +
423
- `Is it installed? Run: agents computer setup --host ${name}`);
510
+ const hint = token
511
+ ? `Is it installed? Run: agents computer setup --host ${name}`
512
+ : `No auth token on record for '${name}' — re-run: agents computer setup --host ${name}`;
513
+ throw new Error(`tunnel to '${name}' opened but the daemon did not answer (${probeErr}). ${hint}`);
424
514
  }
425
515
  const state = {
426
516
  device: name,
@@ -461,6 +551,7 @@ export async function stopRemoteHelper(name) {
461
551
  /* device gone / offline — local teardown still succeeds */
462
552
  }
463
553
  clearRemoteState(name);
554
+ clearHelperToken(name);
464
555
  return { tunnelKilled, taskRemoved };
465
556
  }
466
557
  /**
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Subagents detector. The installed-name enumeration for every agent's on-disk
3
+ * layout is declared once in the subagent registry; this detector is generic
4
+ * and delegates to `listInstalledSubagentNames` instead of a per-agent builder.
5
+ */
1
6
  import type { AgentId } from '../../types.js';
2
7
  import type { ResourceDetector } from './types.js';
3
8
  export declare const subagentsDetectors: Partial<Record<AgentId, ResourceDetector>>;
@@ -1,205 +1,18 @@
1
- /**
2
- * Subagents detector. Claude/Gemini/Grok: flat .md files under `<agentDir>/agents/`.
3
- * Codex: flat .toml files under `<versionHome>/.codex/agents/`.
4
- * Droid: flat .md files under `<versionHome>/.factory/droids/`.
5
- * Cursor: flat .md files under `<versionHome>/.cursor/agents/`.
6
- * ForgeCode: flat .md files under `<versionHome>/.forge/agents/`.
7
- * OpenClaw: subdirectories containing AGENTS.md under `<versionHome>/.openclaw/`.
8
- * Mirrors versions.ts:521-539.
9
- */
10
- import * as fs from 'fs';
11
- import * as path from 'path';
12
1
  import { capableAgents } from '../../capabilities.js';
2
+ import { listInstalledSubagentNames } from '../../subagents-registry.js';
13
3
  import { lazyAgentMap } from '../writers/lazy-map.js';
14
- function buildFlatMdAgentsDetector(agent, agentsRoot) {
4
+ function buildSubagentsDetector(agent) {
15
5
  return {
16
6
  kind: 'subagents',
17
7
  agent,
18
8
  list({ versionHome }) {
19
- const agentsDir = path.join(versionHome, agentsRoot, 'agents');
20
- if (!fs.existsSync(agentsDir))
21
- return [];
22
- return fs.readdirSync(agentsDir)
23
- .filter(f => f.endsWith('.md'))
24
- .map(f => f.replace('.md', ''));
9
+ return listInstalledSubagentNames(agent, versionHome);
25
10
  },
26
11
  };
27
12
  }
28
- function buildClaudeDetector() {
29
- return buildFlatMdAgentsDetector('claude', '.claude');
30
- }
31
- function buildGrokDetector() {
32
- return buildFlatMdAgentsDetector('grok', '.grok');
33
- }
34
- function buildGeminiDetector() {
35
- return buildFlatMdAgentsDetector('gemini', '.gemini');
36
- }
37
- function buildCodexDetector() {
38
- return {
39
- kind: 'subagents',
40
- agent: 'codex',
41
- list({ versionHome }) {
42
- const agentsDir = path.join(versionHome, '.codex', 'agents');
43
- if (!fs.existsSync(agentsDir))
44
- return [];
45
- return fs.readdirSync(agentsDir)
46
- .filter(f => f.endsWith('.toml'))
47
- .map(f => f.replace(/\.toml$/, ''));
48
- },
49
- };
50
- }
51
- function buildDroidDetector() {
52
- return {
53
- kind: 'subagents',
54
- agent: 'droid',
55
- list({ versionHome }) {
56
- const droidsDir = path.join(versionHome, '.factory', 'droids');
57
- if (!fs.existsSync(droidsDir))
58
- return [];
59
- return fs.readdirSync(droidsDir)
60
- .filter(f => f.endsWith('.md'))
61
- .map(f => f.replace('.md', ''));
62
- },
63
- };
64
- }
65
- function buildCopilotDetector() {
66
- return {
67
- kind: 'subagents',
68
- agent: 'copilot',
69
- list({ versionHome }) {
70
- const agentsDir = path.join(versionHome, '.copilot', 'agents');
71
- if (!fs.existsSync(agentsDir))
72
- return [];
73
- return fs.readdirSync(agentsDir)
74
- .filter(f => f.endsWith('.agent.md'))
75
- .map(f => f.replace('.agent.md', ''));
76
- },
77
- };
78
- }
79
- function buildCursorDetector() {
80
- return {
81
- kind: 'subagents',
82
- agent: 'cursor',
83
- list({ versionHome }) {
84
- const agentsDir = path.join(versionHome, '.cursor', 'agents');
85
- if (!fs.existsSync(agentsDir))
86
- return [];
87
- return fs.readdirSync(agentsDir)
88
- .filter(f => f.endsWith('.md'))
89
- .map(f => f.replace(/\.md$/, ''));
90
- },
91
- };
92
- }
93
- function buildOpenclawDetector() {
94
- return {
95
- kind: 'subagents',
96
- agent: 'openclaw',
97
- list({ versionHome }) {
98
- const openclawDir = path.join(versionHome, '.openclaw');
99
- if (!fs.existsSync(openclawDir))
100
- return [];
101
- return fs.readdirSync(openclawDir, { withFileTypes: true })
102
- .filter(d => d.isDirectory() && fs.existsSync(path.join(openclawDir, d.name, 'AGENTS.md')))
103
- .map(d => d.name);
104
- },
105
- };
106
- }
107
- function buildKimiDetector() {
108
- return {
109
- kind: 'subagents',
110
- agent: 'kimi',
111
- list({ versionHome }) {
112
- const agentsDir = path.join(versionHome, '.kimi-code', 'agents');
113
- if (!fs.existsSync(agentsDir))
114
- return [];
115
- // Parent is `_agents-cli.yaml` (underscore-prefixed reserved name).
116
- return fs.readdirSync(agentsDir)
117
- .filter(f => f.endsWith('.yaml') && !f.startsWith('_'))
118
- .map(f => f.replace(/\.yaml$/, ''));
119
- },
120
- };
121
- }
122
- function buildKiroDetector() {
123
- return {
124
- kind: 'subagents',
125
- agent: 'kiro',
126
- list({ versionHome }) {
127
- const agentsDir = path.join(versionHome, '.kiro', 'agents');
128
- if (!fs.existsSync(agentsDir))
129
- return [];
130
- return fs.readdirSync(agentsDir)
131
- .filter(f => f.endsWith('.json'))
132
- .map(f => f.replace(/\.json$/, ''));
133
- },
134
- };
135
- }
136
- function buildForgeDetector() {
137
- return buildFlatMdAgentsDetector('forge', '.forge');
138
- }
139
- function buildGooseDetector() {
140
- return {
141
- kind: 'subagents',
142
- agent: 'goose',
143
- list({ versionHome }) {
144
- const agentsDir = path.join(versionHome, '.config', 'goose', 'agents');
145
- if (!fs.existsSync(agentsDir))
146
- return [];
147
- return fs.readdirSync(agentsDir)
148
- .filter(f => f.endsWith('.yaml'))
149
- .map(f => f.replace(/\.yaml$/, ''));
150
- },
151
- };
152
- }
153
- function buildOpenCodeDetector() {
154
- return {
155
- kind: 'subagents',
156
- agent: 'opencode',
157
- list({ versionHome }) {
158
- const agentsDir = path.join(versionHome, '.config', 'opencode', 'agents');
159
- if (!fs.existsSync(agentsDir))
160
- return [];
161
- return fs.readdirSync(agentsDir)
162
- .filter(f => f.endsWith('.md'))
163
- .map(f => f.replace(/\.md$/, ''));
164
- },
165
- };
166
- }
167
- function buildAntigravityDetector() {
168
- return {
169
- kind: 'subagents',
170
- agent: 'antigravity',
171
- list({ versionHome }) {
172
- const agentsDir = path.join(versionHome, '.gemini', 'config', 'agents');
173
- if (!fs.existsSync(agentsDir))
174
- return [];
175
- return fs.readdirSync(agentsDir, { withFileTypes: true })
176
- .filter(d => d.isDirectory() && fs.existsSync(path.join(agentsDir, d.name, 'agent.md')))
177
- .map(d => d.name);
178
- },
179
- };
180
- }
181
- const handlers = {
182
- claude: buildClaudeDetector,
183
- copilot: buildCopilotDetector,
184
- gemini: buildGeminiDetector,
185
- grok: buildGrokDetector,
186
- codex: buildCodexDetector,
187
- kimi: buildKimiDetector,
188
- opencode: buildOpenCodeDetector,
189
- antigravity: buildAntigravityDetector,
190
- droid: buildDroidDetector,
191
- openclaw: buildOpenclawDetector,
192
- kiro: buildKiroDetector,
193
- cursor: buildCursorDetector,
194
- forge: buildForgeDetector,
195
- goose: buildGooseDetector,
196
- };
197
13
  export const subagentsDetectors = lazyAgentMap(() => {
198
14
  const m = {};
199
- for (const agent of capableAgents('subagents')) {
200
- const f = handlers[agent];
201
- if (f)
202
- m[agent] = f();
203
- }
15
+ for (const agent of capableAgents('subagents'))
16
+ m[agent] = buildSubagentsDetector(agent);
204
17
  return m;
205
18
  });
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Subagents writer. Every agent's on-disk layout (target dir, file/dir shape,
3
+ * transform, and any post-sync finalize such as Kimi's parent index) is
4
+ * declared once in the subagent registry; this writer is generic and iterates
5
+ * the registry instead of a per-agent `else if` chain.
6
+ *
7
+ * Source-side discovery is `listInstalledSubagents` from lib/subagents.ts —
8
+ * it reads user + system layers only (project layer excluded for the same
9
+ * defense as commands/skills/hooks).
10
+ */
1
11
  import type { AgentId } from '../../types.js';
2
12
  import type { ResourceWriter } from './types.js';
3
13
  export declare const subagentsWriters: Partial<Record<AgentId, ResourceWriter<string[]>>>;
@@ -1,123 +1,32 @@
1
- /**
2
- * Subagents writer. Claude/Gemini/Grok flatten each subagent into a single
3
- * .md file under their native agents directory. Codex writes TOML under
4
- * `.codex/agents/`.
5
- * Droid (Factory AI) flattens each into a custom droid .md under
6
- * `<versionHome>/.factory/droids/`. Cursor flattens each into a custom
7
- * subagent .md under `<versionHome>/.cursor/agents/`. OpenClaw copies the
8
- * full subagent directory (with AGENT.md renamed to AGENTS.md) into
9
- * `<versionHome>/.openclaw/<name>/`.
10
- *
11
- * Source-side discovery is `listInstalledSubagents` from lib/subagents.ts —
12
- * it reads user + system layers only (project layer excluded for the same
13
- * defense as commands/skills/hooks).
14
- */
15
- import * as fs from 'fs';
16
- import * as path from 'path';
17
1
  import { capableAgents } from '../../capabilities.js';
18
- import { listInstalledSubagents, transformSubagentForClaude, transformSubagentForCodex, transformSubagentForCopilot, writeKimiSubagentFiles, buildKimiSubagentsParentYaml, KIMI_SUBAGENTS_PARENT_FILE, transformSubagentForOpenCode, transformSubagentForAntigravity, transformSubagentForDroid, transformSubagentForForge, transformSubagentForKiro, transformSubagentForCursor, transformSubagentForGoose, syncSubagentToOpenclaw, parseSubagentFrontmatter, } from '../../subagents.js';
19
- import { safeJoin } from '../../paths.js';
2
+ import { listInstalledSubagents } from '../../subagents.js';
3
+ import { subagentTarget } from '../../subagents-registry.js';
20
4
  import { lazyAgentMap } from './lazy-map.js';
21
5
  function buildSubagentsWriter(agent) {
22
6
  return {
23
7
  kind: 'subagents',
24
8
  agent,
25
9
  write({ versionHome, selection }) {
10
+ const target = subagentTarget(agent);
11
+ if (!target)
12
+ return { synced: [] };
26
13
  const all = listInstalledSubagents();
27
14
  const map = new Map(all.map(s => [s.name, s]));
15
+ const dir = target.dir(versionHome);
28
16
  const synced = [];
29
17
  for (const name of selection) {
30
18
  const sub = map.get(name);
31
19
  if (!sub)
32
20
  continue;
33
21
  try {
34
- if (agent === 'claude' || agent === 'gemini' || agent === 'grok') {
35
- const agentsRoot = agent === 'grok' ? '.grok' : agent === 'gemini' ? '.gemini' : '.claude';
36
- const agentsDir = path.join(versionHome, agentsRoot, 'agents');
37
- fs.mkdirSync(agentsDir, { recursive: true });
38
- fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.md`), transformSubagentForClaude(sub.path));
39
- synced.push(sub.name);
40
- }
41
- else if (agent === 'codex') {
42
- const agentsDir = path.join(versionHome, '.codex', 'agents');
43
- fs.mkdirSync(agentsDir, { recursive: true });
44
- fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.toml`), transformSubagentForCodex(sub.path));
45
- synced.push(sub.name);
46
- }
47
- else if (agent === 'kimi') {
48
- writeKimiSubagentFiles(path.join(versionHome, '.kimi-code', 'agents'), sub.path, sub.name);
49
- synced.push(sub.name);
50
- }
51
- else if (agent === 'opencode') {
52
- const agentsDir = path.join(versionHome, '.config', 'opencode', 'agents');
53
- fs.mkdirSync(agentsDir, { recursive: true });
54
- fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.md`), transformSubagentForOpenCode(sub.path));
55
- synced.push(sub.name);
56
- }
57
- else if (agent === 'antigravity') {
58
- const agentDir = safeJoin(path.join(versionHome, '.gemini', 'config', 'agents'), sub.name);
59
- fs.mkdirSync(agentDir, { recursive: true });
60
- fs.writeFileSync(safeJoin(agentDir, 'agent.md'), transformSubagentForAntigravity(sub.path));
61
- synced.push(sub.name);
62
- }
63
- else if (agent === 'droid') {
64
- const droidsDir = path.join(versionHome, '.factory', 'droids');
65
- fs.mkdirSync(droidsDir, { recursive: true });
66
- fs.writeFileSync(safeJoin(droidsDir, `${sub.name}.md`), transformSubagentForDroid(sub.path));
67
- synced.push(sub.name);
68
- }
69
- else if (agent === 'copilot') {
70
- const agentsDir = path.join(versionHome, '.copilot', 'agents');
71
- fs.mkdirSync(agentsDir, { recursive: true });
72
- fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.agent.md`), transformSubagentForCopilot(sub.path));
73
- synced.push(sub.name);
74
- }
75
- else if (agent === 'openclaw') {
76
- const target = safeJoin(path.join(versionHome, '.openclaw'), sub.name);
77
- const r = syncSubagentToOpenclaw(sub.path, target);
78
- if (r.success)
79
- synced.push(sub.name);
80
- }
81
- else if (agent === 'kiro') {
82
- const agentsDir = path.join(versionHome, '.kiro', 'agents');
83
- fs.mkdirSync(agentsDir, { recursive: true });
84
- fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.json`), transformSubagentForKiro(sub.path));
85
- synced.push(sub.name);
86
- }
87
- else if (agent === 'cursor') {
88
- const agentsDir = path.join(versionHome, '.cursor', 'agents');
89
- fs.mkdirSync(agentsDir, { recursive: true });
90
- fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.md`), transformSubagentForCursor(sub.path));
91
- synced.push(sub.name);
92
- }
93
- else if (agent === 'forge') {
94
- const agentsDir = path.join(versionHome, '.forge', 'agents');
95
- fs.mkdirSync(agentsDir, { recursive: true });
96
- fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.md`), transformSubagentForForge(sub.path));
97
- synced.push(sub.name);
98
- }
99
- else if (agent === 'goose') {
100
- const agentsDir = path.join(versionHome, '.config', 'goose', 'agents');
101
- fs.mkdirSync(agentsDir, { recursive: true });
102
- fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.yaml`), transformSubagentForGoose(sub.path));
103
- synced.push(sub.name);
104
- }
22
+ target.write(dir, sub);
23
+ synced.push(sub.name);
105
24
  }
106
25
  catch { /* per-item sync failure: skip */ }
107
26
  }
108
- // Kimi parent agent file listing all synced subagents for --agent-file.
109
- if (agent === 'kimi' && synced.length > 0) {
110
- const agentsDir = path.join(versionHome, '.kimi-code', 'agents');
111
- const entries = synced.map((name) => {
112
- const sub = map.get(name);
113
- const fm = parseSubagentFrontmatter(path.join(sub.path, 'AGENT.md'));
114
- return {
115
- name,
116
- description: fm?.description ?? name,
117
- relativePath: `./${name}.yaml`,
118
- };
119
- });
120
- fs.writeFileSync(safeJoin(agentsDir, KIMI_SUBAGENTS_PARENT_FILE), buildKimiSubagentsParentYaml(entries));
27
+ // Optional cross-item pass (e.g. Kimi's `_agents-cli.yaml` parent index).
28
+ if (target.finalize && synced.length > 0) {
29
+ target.finalize(dir, synced.map((name) => map.get(name)));
121
30
  }
122
31
  return { synced };
123
32
  },
@@ -43,6 +43,7 @@ export declare const loadImport: ModuleLoader;
43
43
  export declare const loadPackages: ModuleLoader;
44
44
  export declare const loadDaemon: ModuleLoader;
45
45
  export declare const loadRoutines: ModuleLoader;
46
+ export declare const loadMonitors: ModuleLoader;
46
47
  export declare const loadRun: ModuleLoader;
47
48
  export declare const loadDefaults: ModuleLoader;
48
49
  export declare const loadModels: ModuleLoader;
@@ -50,6 +51,7 @@ export declare const loadPrune: ModuleLoader;
50
51
  export declare const loadTrash: ModuleLoader;
51
52
  export declare const loadRestore: ModuleLoader;
52
53
  export declare const loadDoctor: ModuleLoader;
54
+ export declare const loadApply: ModuleLoader;
53
55
  export declare const loadCheck: ModuleLoader;
54
56
  export declare const loadStatus: ModuleLoader;
55
57
  export declare const loadProfiles: ModuleLoader;
@@ -74,6 +76,7 @@ export declare const loadWatchdog: ModuleLoader;
74
76
  export declare const loadBrowser: ModuleLoader;
75
77
  export declare const loadComputer: ModuleLoader;
76
78
  export declare const loadHosts: ModuleLoader;
79
+ export declare const loadLease: ModuleLoader;
77
80
  export declare const loadLogs: ModuleLoader;
78
81
  export declare const loadEvents: ModuleLoader;
79
82
  export declare const loadSsh: ModuleLoader;
@@ -86,6 +89,7 @@ export declare const loadTeams: ModuleLoader;
86
89
  export declare const loadCloud: ModuleLoader;
87
90
  export declare const loadMessage: ModuleLoader;
88
91
  export declare const loadFeed: ModuleLoader;
92
+ export declare const loadMailboxes: ModuleLoader;
89
93
  export declare const loadServe: ModuleLoader;
90
94
  export declare const loadAudit: ModuleLoader;
91
95
  export declare const loadWebhook: ModuleLoader;
@@ -21,6 +21,7 @@ export const loadImport = async () => (await import('../../commands/import.js'))
21
21
  export const loadPackages = async () => (await import('../../commands/packages.js')).registerPackagesCommands;
22
22
  export const loadDaemon = async () => (await import('../../commands/daemon.js')).registerDaemonCommands;
23
23
  export const loadRoutines = async () => (await import('../../commands/routines.js')).registerRoutinesCommands;
24
+ export const loadMonitors = async () => (await import('../../commands/monitors.js')).registerMonitorsCommands;
24
25
  export const loadRun = async () => (await import('../../commands/exec.js')).registerRunCommand;
25
26
  export const loadDefaults = async () => (await import('../../commands/defaults.js')).registerDefaultsCommands;
26
27
  export const loadModels = async () => (await import('../../commands/models.js')).registerModelsCommand;
@@ -28,6 +29,7 @@ export const loadPrune = async () => (await import('../../commands/prune.js')).r
28
29
  export const loadTrash = async () => (await import('../../commands/trash.js')).registerTrashCommands;
29
30
  export const loadRestore = async () => (await import('../../commands/trash.js')).registerRestoreCommand;
30
31
  export const loadDoctor = async () => (await import('../../commands/doctor.js')).registerDoctorCommand;
32
+ export const loadApply = async () => (await import('../../commands/apply.js')).registerApplyCommand;
31
33
  export const loadCheck = async () => (await import('../../commands/check.js')).registerCheckCommand;
32
34
  export const loadStatus = async () => (await import('../../commands/status.js')).registerStatusCommand;
33
35
  export const loadProfiles = async () => (await import('../../commands/profiles.js')).registerProfilesCommands;
@@ -52,6 +54,7 @@ export const loadWatchdog = async () => (await import('../../commands/watchdog.j
52
54
  export const loadBrowser = async () => (await import('../../commands/browser.js')).registerBrowserCommand;
53
55
  export const loadComputer = async () => (await import('../../commands/computer.js')).registerComputerCommand;
54
56
  export const loadHosts = async () => (await import('../../commands/hosts.js')).registerHostsCommand;
57
+ export const loadLease = async () => (await import('../../commands/lease.js')).registerLeaseCommand;
55
58
  export const loadLogs = async () => (await import('../../commands/logs.js')).registerLogsCommand;
56
59
  export const loadEvents = async () => (await import('../../commands/events.js')).registerEventsCommand;
57
60
  export const loadSsh = async () => (await import('../../commands/ssh.js')).registerSshCommands;
@@ -64,6 +67,7 @@ export const loadTeams = async () => (await import('../../commands/teams.js')).r
64
67
  export const loadCloud = async () => (await import('../../commands/cloud.js')).registerCloudCommands;
65
68
  export const loadMessage = async () => (await import('../../commands/message.js')).registerMessageCommand;
66
69
  export const loadFeed = async () => (await import('../../commands/feed.js')).registerFeedCommand;
70
+ export const loadMailboxes = async () => (await import('../../commands/mailboxes.js')).registerMailboxesCommand;
67
71
  export const loadServe = async () => (await import('../../commands/serve.js')).registerServeCommand;
68
72
  export const loadAudit = async () => (await import('../../commands/audit.js')).registerAuditCommands;
69
73
  export const loadWebhook = async () => (await import('../../commands/webhook.js')).registerWebhookCommand;
@@ -119,12 +123,14 @@ export const COMMAND_LOADERS = {
119
123
  install: [loadPackages],
120
124
  daemon: [loadDaemon],
121
125
  routines: [loadRoutines],
126
+ monitors: [loadMonitors],
122
127
  run: [loadRun],
123
128
  defaults: [loadDefaults],
124
129
  models: [loadModels],
125
130
  trash: [loadTrash],
126
131
  restore: [loadRestore],
127
132
  doctor: [loadDoctor],
133
+ apply: [loadApply],
128
134
  check: [loadCheck],
129
135
  status: [loadStatus],
130
136
  profiles: [loadProfiles],
@@ -149,6 +155,7 @@ export const COMMAND_LOADERS = {
149
155
  browser: [loadBrowser],
150
156
  computer: [loadComputer],
151
157
  hosts: [loadHosts],
158
+ lease: [loadLease],
152
159
  logs: [loadLogs],
153
160
  events: [loadEvents],
154
161
  ssh: [loadSsh],
@@ -169,6 +176,8 @@ export const COMMAND_LOADERS = {
169
176
  cloud: [loadCloud],
170
177
  message: [loadMessage],
171
178
  feed: [loadFeed],
179
+ mailboxes: [loadMailboxes],
180
+ mailbox: [loadMailboxes],
172
181
  serve: [loadServe],
173
182
  audit: [loadAudit],
174
183
  webhook: [loadWebhook],
@@ -128,6 +128,11 @@ export declare function getSystemRoutinesDir(): string;
128
128
  export declare function getProjectRoutinesDir(cwd?: string): string | null;
129
129
  /** Path to routine execution logs (~/.agents/.history/runs/). */
130
130
  export declare function getRunsDir(): string;
131
+ /** Path to monitor YAML definitions (~/.agents/monitors/). */
132
+ export declare function getMonitorsDir(): string;
133
+ /** Path to the durable per-monitor state-diff store + fire history
134
+ * (~/.agents/.history/monitors/). */
135
+ export declare function getMonitorsHistoryDir(): string;
131
136
  /** Root for per-agent mailboxes (~/.agents/.history/mailbox/). */
132
137
  export declare function getMailboxRootDir(): string;
133
138
  /** Root for open-block feed records (~/.agents/.history/feed/). */