@phnx-labs/agents-cli 1.20.34 → 1.20.36

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 (291) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/LICENSE +185 -21
  3. package/README.md +13 -5
  4. package/dist/commands/audit.d.ts +14 -0
  5. package/dist/commands/audit.js +68 -0
  6. package/dist/commands/browser.js +82 -8
  7. package/dist/commands/check.d.ts +15 -0
  8. package/dist/commands/check.js +84 -0
  9. package/dist/commands/cloud.js +143 -3
  10. package/dist/commands/computer-actions.d.ts +15 -0
  11. package/dist/commands/computer-actions.js +120 -22
  12. package/dist/commands/computer.js +81 -0
  13. package/dist/commands/cost.js +17 -12
  14. package/dist/commands/daemon.js +4 -1
  15. package/dist/commands/doctor.d.ts +1 -0
  16. package/dist/commands/doctor.js +54 -97
  17. package/dist/commands/events.d.ts +16 -0
  18. package/dist/commands/events.js +173 -0
  19. package/dist/commands/exec.d.ts +19 -0
  20. package/dist/commands/exec.js +283 -12
  21. package/dist/commands/hosts.js +37 -29
  22. package/dist/commands/inspect.d.ts +2 -0
  23. package/dist/commands/inspect.js +62 -16
  24. package/dist/commands/lock.d.ts +12 -0
  25. package/dist/commands/lock.js +70 -0
  26. package/dist/commands/logs.d.ts +17 -0
  27. package/dist/commands/logs.js +139 -0
  28. package/dist/commands/message.d.ts +15 -0
  29. package/dist/commands/message.js +56 -0
  30. package/dist/commands/models.d.ts +3 -0
  31. package/dist/commands/models.js +27 -8
  32. package/dist/commands/permissions.js +9 -2
  33. package/dist/commands/repo.d.ts +34 -0
  34. package/dist/commands/repo.js +243 -65
  35. package/dist/commands/resource-view.d.ts +20 -0
  36. package/dist/commands/resource-view.js +90 -28
  37. package/dist/commands/routines.js +101 -5
  38. package/dist/commands/secrets-migrate.js +106 -57
  39. package/dist/commands/secrets.d.ts +31 -18
  40. package/dist/commands/secrets.js +196 -91
  41. package/dist/commands/serve.d.ts +10 -0
  42. package/dist/commands/serve.js +37 -0
  43. package/dist/commands/sessions-inject.d.ts +14 -0
  44. package/dist/commands/sessions-inject.js +111 -0
  45. package/dist/commands/sessions-picker.d.ts +2 -0
  46. package/dist/commands/sessions-picker.js +24 -3
  47. package/dist/commands/sessions-resume.d.ts +2 -0
  48. package/dist/commands/sessions-resume.js +221 -0
  49. package/dist/commands/sessions-tail.d.ts +10 -0
  50. package/dist/commands/sessions-tail.js +11 -0
  51. package/dist/commands/sessions.d.ts +143 -1
  52. package/dist/commands/sessions.js +628 -80
  53. package/dist/commands/setup.js +4 -2
  54. package/dist/commands/sync.d.ts +3 -1
  55. package/dist/commands/sync.js +156 -4
  56. package/dist/commands/teams.js +217 -0
  57. package/dist/commands/versions.js +2 -4
  58. package/dist/commands/view.d.ts +3 -0
  59. package/dist/commands/view.js +19 -8
  60. package/dist/commands/wallet.d.ts +6 -0
  61. package/dist/commands/wallet.js +22 -5
  62. package/dist/commands/watchdog.d.ts +18 -0
  63. package/dist/commands/watchdog.js +238 -0
  64. package/dist/index.js +82 -32
  65. package/dist/lib/agents.js +30 -9
  66. package/dist/lib/audit/log.d.ts +92 -0
  67. package/dist/lib/audit/log.js +177 -0
  68. package/dist/lib/auto-pull.js +2 -1
  69. package/dist/lib/browser/cdp.js +5 -1
  70. package/dist/lib/browser/chrome.d.ts +10 -0
  71. package/dist/lib/browser/chrome.js +18 -7
  72. package/dist/lib/browser/drivers/ssh.d.ts +8 -0
  73. package/dist/lib/browser/drivers/ssh.js +77 -16
  74. package/dist/lib/browser/har.d.ts +84 -0
  75. package/dist/lib/browser/har.js +77 -0
  76. package/dist/lib/browser/ipc.d.ts +3 -0
  77. package/dist/lib/browser/ipc.js +37 -12
  78. package/dist/lib/browser/profiles.d.ts +1 -1
  79. package/dist/lib/browser/profiles.js +8 -10
  80. package/dist/lib/browser/refs.d.ts +65 -0
  81. package/dist/lib/browser/refs.js +73 -1
  82. package/dist/lib/browser/runtime-state.js +1 -0
  83. package/dist/lib/browser/service.d.ts +38 -2
  84. package/dist/lib/browser/service.js +183 -23
  85. package/dist/lib/browser/types.d.ts +14 -1
  86. package/dist/lib/budget/live-cloud.d.ts +42 -0
  87. package/dist/lib/budget/live-cloud.js +79 -0
  88. package/dist/lib/budget/live-team.d.ts +31 -0
  89. package/dist/lib/budget/live-team.js +115 -0
  90. package/dist/lib/cloud/codex.js +4 -0
  91. package/dist/lib/cloud/rush.d.ts +12 -1
  92. package/dist/lib/cloud/rush.js +13 -3
  93. package/dist/lib/cloud/types.d.ts +9 -0
  94. package/dist/lib/computer/dispatch.d.ts +8 -0
  95. package/dist/lib/computer/dispatch.js +125 -0
  96. package/dist/lib/computer/loop.d.ts +62 -0
  97. package/dist/lib/computer/loop.js +98 -0
  98. package/dist/lib/computer/model.d.ts +44 -0
  99. package/dist/lib/computer/model.js +157 -0
  100. package/dist/lib/concurrency.d.ts +19 -0
  101. package/dist/lib/concurrency.js +33 -0
  102. package/dist/lib/daemon.d.ts +92 -0
  103. package/dist/lib/daemon.js +222 -18
  104. package/dist/lib/devices/connect.d.ts +3 -2
  105. package/dist/lib/devices/connect.js +5 -3
  106. package/dist/lib/devices/registry.d.ts +7 -0
  107. package/dist/lib/devices/registry.js +24 -0
  108. package/dist/lib/devices/tailscale.js +1 -1
  109. package/dist/lib/drift.d.ts +52 -0
  110. package/dist/lib/drift.js +112 -0
  111. package/dist/lib/events.d.ts +10 -3
  112. package/dist/lib/events.js +101 -24
  113. package/dist/lib/exec.d.ts +17 -0
  114. package/dist/lib/exec.js +100 -12
  115. package/dist/lib/git.d.ts +27 -0
  116. package/dist/lib/git.js +56 -1
  117. package/dist/lib/hooks/cache.d.ts +6 -0
  118. package/dist/lib/hooks/cache.js +54 -12
  119. package/dist/lib/hooks.d.ts +27 -0
  120. package/dist/lib/hooks.js +135 -10
  121. package/dist/lib/hosts/dispatch.d.ts +15 -0
  122. package/dist/lib/hosts/dispatch.js +45 -13
  123. package/dist/lib/hosts/logs.d.ts +16 -0
  124. package/dist/lib/hosts/logs.js +74 -0
  125. package/dist/lib/hosts/option.js +1 -1
  126. package/dist/lib/hosts/passthrough.js +3 -1
  127. package/dist/lib/hosts/progress.d.ts +66 -0
  128. package/dist/lib/hosts/progress.js +125 -17
  129. package/dist/lib/hosts/ready.d.ts +51 -7
  130. package/dist/lib/hosts/ready.js +98 -25
  131. package/dist/lib/hosts/reconcile.d.ts +53 -0
  132. package/dist/lib/hosts/reconcile.js +81 -0
  133. package/dist/lib/hosts/registry.d.ts +19 -2
  134. package/dist/lib/hosts/registry.js +58 -2
  135. package/dist/lib/hosts/remote-cmd.d.ts +62 -1
  136. package/dist/lib/hosts/remote-cmd.js +70 -1
  137. package/dist/lib/hosts/remote-os.d.ts +17 -0
  138. package/dist/lib/hosts/remote-os.js +30 -0
  139. package/dist/lib/hosts/session-index.d.ts +34 -0
  140. package/dist/lib/hosts/session-index.js +56 -0
  141. package/dist/lib/hosts/tasks.d.ts +22 -0
  142. package/dist/lib/hosts/tasks.js +29 -0
  143. package/dist/lib/lock.d.ts +93 -0
  144. package/dist/lib/lock.js +207 -0
  145. package/dist/lib/loop.js +16 -1
  146. package/dist/lib/machine-id.d.ts +21 -0
  147. package/dist/lib/machine-id.js +26 -0
  148. package/dist/lib/mailbox-target.d.ts +36 -0
  149. package/dist/lib/mailbox-target.js +45 -0
  150. package/dist/lib/mailbox.d.ts +47 -0
  151. package/dist/lib/mailbox.js +194 -0
  152. package/dist/lib/mcp.d.ts +5 -0
  153. package/dist/lib/mcp.js +24 -8
  154. package/dist/lib/migrate.d.ts +19 -0
  155. package/dist/lib/migrate.js +134 -26
  156. package/dist/lib/overdue.js +3 -0
  157. package/dist/lib/permissions.d.ts +4 -0
  158. package/dist/lib/permissions.js +35 -0
  159. package/dist/lib/picker.d.ts +26 -0
  160. package/dist/lib/picker.js +127 -0
  161. package/dist/lib/platform/exec.d.ts +46 -0
  162. package/dist/lib/platform/exec.js +74 -0
  163. package/dist/lib/platform/process.d.ts +31 -0
  164. package/dist/lib/platform/process.js +34 -1
  165. package/dist/lib/platform/winpath.js +2 -0
  166. package/dist/lib/plugin-marketplace.d.ts +30 -0
  167. package/dist/lib/plugin-marketplace.js +215 -2
  168. package/dist/lib/plugins.d.ts +5 -0
  169. package/dist/lib/plugins.js +61 -10
  170. package/dist/lib/profiles.d.ts +25 -0
  171. package/dist/lib/profiles.js +22 -6
  172. package/dist/lib/pty-client.js +2 -1
  173. package/dist/lib/rotate.d.ts +61 -0
  174. package/dist/lib/rotate.js +52 -0
  175. package/dist/lib/routines.d.ts +40 -2
  176. package/dist/lib/routines.js +66 -8
  177. package/dist/lib/runner.d.ts +11 -2
  178. package/dist/lib/runner.js +49 -7
  179. package/dist/lib/scheduler.js +6 -1
  180. package/dist/lib/secrets/bundles.d.ts +60 -4
  181. package/dist/lib/secrets/bundles.js +131 -12
  182. package/dist/lib/secrets/filestore.d.ts +3 -0
  183. package/dist/lib/secrets/filestore.js +42 -16
  184. package/dist/lib/secrets/index.d.ts +43 -2
  185. package/dist/lib/secrets/index.js +102 -3
  186. package/dist/lib/secrets/mcp.d.ts +93 -0
  187. package/dist/lib/secrets/mcp.js +205 -0
  188. package/dist/lib/secrets/remote.js +12 -5
  189. package/dist/lib/secrets/sync.js +83 -4
  190. package/dist/lib/secrets/windows.js +14 -3
  191. package/dist/lib/serve/data.d.ts +81 -0
  192. package/dist/lib/serve/data.js +91 -0
  193. package/dist/lib/serve/page.d.ts +7 -0
  194. package/dist/lib/serve/page.js +140 -0
  195. package/dist/lib/serve/server.d.ts +46 -0
  196. package/dist/lib/serve/server.js +115 -0
  197. package/dist/lib/session/active.d.ts +61 -0
  198. package/dist/lib/session/active.js +196 -17
  199. package/dist/lib/session/discover.d.ts +37 -0
  200. package/dist/lib/session/discover.js +204 -27
  201. package/dist/lib/session/inject.d.ts +18 -0
  202. package/dist/lib/session/inject.js +21 -0
  203. package/dist/lib/session/parse.d.ts +6 -0
  204. package/dist/lib/session/parse.js +286 -2
  205. package/dist/lib/session/pid-registry.d.ts +22 -0
  206. package/dist/lib/session/pid-registry.js +104 -0
  207. package/dist/lib/session/provenance.d.ts +14 -2
  208. package/dist/lib/session/provenance.js +39 -8
  209. package/dist/lib/session/remote-active.d.ts +26 -0
  210. package/dist/lib/session/remote-active.js +153 -0
  211. package/dist/lib/session/remote-list.d.ts +51 -0
  212. package/dist/lib/session/remote-list.js +213 -0
  213. package/dist/lib/session/remote.d.ts +7 -1
  214. package/dist/lib/session/remote.js +18 -8
  215. package/dist/lib/session/render.js +1 -1
  216. package/dist/lib/session/sync/config.d.ts +1 -7
  217. package/dist/lib/session/sync/config.js +4 -11
  218. package/dist/lib/session/types.d.ts +18 -1
  219. package/dist/lib/session/types.js +1 -1
  220. package/dist/lib/shims.d.ts +44 -6
  221. package/dist/lib/shims.js +143 -57
  222. package/dist/lib/ssh-exec.d.ts +26 -3
  223. package/dist/lib/ssh-exec.js +47 -3
  224. package/dist/lib/ssh-tunnel.d.ts +24 -5
  225. package/dist/lib/ssh-tunnel.js +62 -63
  226. package/dist/lib/staleness/writers/hooks.js +1 -1
  227. package/dist/lib/startup/command-registry.d.ts +8 -0
  228. package/dist/lib/startup/command-registry.js +17 -1
  229. package/dist/lib/state.d.ts +13 -0
  230. package/dist/lib/state.js +103 -9
  231. package/dist/lib/sync-umbrella.d.ts +14 -7
  232. package/dist/lib/sync-umbrella.js +17 -9
  233. package/dist/lib/teams/forEach.d.ts +110 -0
  234. package/dist/lib/teams/forEach.js +186 -0
  235. package/dist/lib/teams/index.d.ts +1 -0
  236. package/dist/lib/teams/index.js +1 -0
  237. package/dist/lib/teams/pr-watch.d.ts +226 -0
  238. package/dist/lib/teams/pr-watch.js +371 -0
  239. package/dist/lib/teams/registry.js +25 -9
  240. package/dist/lib/teams/supervisor.d.ts +14 -1
  241. package/dist/lib/teams/supervisor.js +19 -0
  242. package/dist/lib/teams/worktree.d.ts +9 -0
  243. package/dist/lib/teams/worktree.js +32 -0
  244. package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
  245. package/dist/lib/terminal/backends/ghostty.js +69 -0
  246. package/dist/lib/terminal/backends/index.d.ts +19 -0
  247. package/dist/lib/terminal/backends/index.js +31 -0
  248. package/dist/lib/terminal/backends/iterm.d.ts +6 -0
  249. package/dist/lib/terminal/backends/iterm.js +62 -0
  250. package/dist/lib/terminal/backends/tmux.d.ts +14 -0
  251. package/dist/lib/terminal/backends/tmux.js +23 -0
  252. package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
  253. package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
  254. package/dist/lib/terminal/engine.d.ts +39 -0
  255. package/dist/lib/terminal/engine.js +54 -0
  256. package/dist/lib/terminal/index.d.ts +17 -0
  257. package/dist/lib/terminal/index.js +8 -0
  258. package/dist/lib/terminal/inject.d.ts +204 -0
  259. package/dist/lib/terminal/inject.js +247 -0
  260. package/dist/lib/terminal/policy.d.ts +11 -0
  261. package/dist/lib/terminal/policy.js +11 -0
  262. package/dist/lib/terminal/quote.d.ts +11 -0
  263. package/dist/lib/terminal/quote.js +13 -0
  264. package/dist/lib/terminal/resolve.d.ts +64 -0
  265. package/dist/lib/terminal/resolve.js +90 -0
  266. package/dist/lib/terminal/shell.d.ts +6 -0
  267. package/dist/lib/terminal/shell.js +23 -0
  268. package/dist/lib/terminal/transport.d.ts +15 -0
  269. package/dist/lib/terminal/transport.js +40 -0
  270. package/dist/lib/terminal/types.d.ts +59 -0
  271. package/dist/lib/terminal/types.js +13 -0
  272. package/dist/lib/triggers/webhook.d.ts +85 -0
  273. package/dist/lib/triggers/webhook.js +141 -0
  274. package/dist/lib/types.d.ts +7 -0
  275. package/dist/lib/versions.d.ts +30 -3
  276. package/dist/lib/versions.js +129 -18
  277. package/dist/lib/watchdog/index.d.ts +3 -0
  278. package/dist/lib/watchdog/index.js +5 -0
  279. package/dist/lib/watchdog/read.d.ts +35 -0
  280. package/dist/lib/watchdog/read.js +149 -0
  281. package/dist/lib/watchdog/runner.d.ts +127 -0
  282. package/dist/lib/watchdog/runner.js +322 -0
  283. package/dist/lib/watchdog/watchdog.d.ts +40 -0
  284. package/dist/lib/watchdog/watchdog.js +166 -0
  285. package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
  286. package/dist/lib/watchdog/watchdogTail.js +154 -0
  287. package/dist/lib/whats-new.d.ts +9 -0
  288. package/dist/lib/whats-new.js +35 -0
  289. package/dist/lib/workflows.d.ts +166 -0
  290. package/dist/lib/workflows.js +193 -0
  291. package/package.json +5 -4
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Read-only state assembly for `agents serve`.
3
+ *
4
+ * Reuses the SAME structured data the CLI already emits — no recomputation:
5
+ * - teams: {@link handleTasks} + {@link handleStatus} + {@link toTaskStatusSummary}
6
+ * (the shape behind `agents teams status --json`), plus a per-worktree
7
+ * `git diff` via {@link gitDiff}.
8
+ * - routines: {@link listJobs} (the shape behind `agents routines list --json`).
9
+ * - cloud: {@link listTasks} from the local SQLite store (behind `agents cloud list --json`).
10
+ *
11
+ * Every panel is assembled independently: if one subsystem throws (e.g. the
12
+ * SQLite cloud store isn't provisioned on this box), that panel reports an
13
+ * `error` string and the others still render. This is an aggregator dashboard,
14
+ * not a fallback that hides a bug in a single code path.
15
+ */
16
+ import { AgentManager } from '../teams/agents.js';
17
+ import type { AgentStatusSummary } from '../teams/api.js';
18
+ import type { JobConfig } from '../routines.js';
19
+ import type { CloudTask } from '../cloud/types.js';
20
+ /** One teammate's worktree and its current uncommitted diff. */
21
+ export interface WorktreeDiff {
22
+ agent_id: string;
23
+ name: string | null;
24
+ agent_type: string;
25
+ status: string;
26
+ worktree_name: string | null;
27
+ worktree_path: string | null;
28
+ pr_url: string | null;
29
+ /** Uncommitted `git diff HEAD` for the worktree, capped. '' when clean/absent. */
30
+ diff: string;
31
+ }
32
+ /** One team: its per-teammate status summary plus per-worktree diffs. */
33
+ export interface TeamPanel {
34
+ task_name: string;
35
+ agent_count: number;
36
+ running: number;
37
+ completed: number;
38
+ failed: number;
39
+ agents: AgentStatusSummary[];
40
+ worktrees: WorktreeDiff[];
41
+ }
42
+ /** A panel that either carries data or an error string (never both). */
43
+ export type PanelResult<T> = {
44
+ ok: true;
45
+ data: T;
46
+ } | {
47
+ ok: false;
48
+ error: string;
49
+ };
50
+ /** The full read-only snapshot streamed to the browser. */
51
+ export interface ServeState {
52
+ generated_at: string;
53
+ teams: PanelResult<TeamPanel[]>;
54
+ routines: PanelResult<JobConfig[]>;
55
+ cloud: PanelResult<CloudTask[]>;
56
+ }
57
+ /** Shape of the teammate fields needed to build a worktree diff. */
58
+ export interface WorktreeAgentLike {
59
+ agentId: string;
60
+ name: string | null;
61
+ agentType: string;
62
+ status: string;
63
+ worktreeName: string | null;
64
+ worktreePath: string | null;
65
+ prUrl: string | null;
66
+ }
67
+ /**
68
+ * Build the per-worktree diffs for a set of teammates. Only teammates with a
69
+ * `worktreePath` produce a diff; each gets a real `git diff HEAD` via
70
+ * {@link gitDiff}. Pure over its inputs (no manager, no globals) so it can be
71
+ * unit-tested against a real temp git worktree.
72
+ */
73
+ export declare function buildWorktreeDiffs(agents: WorktreeAgentLike[]): Promise<WorktreeDiff[]>;
74
+ /**
75
+ * Assemble the full read-only snapshot. Each panel degrades independently to an
76
+ * `error` string so one unavailable subsystem never blanks the whole dashboard.
77
+ *
78
+ * @param cwd - Project root used for project-scoped routine discovery.
79
+ * @param manager - Injectable for tests; defaults to a fresh {@link AgentManager}.
80
+ */
81
+ export declare function assembleState(cwd?: string, manager?: AgentManager): Promise<ServeState>;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Read-only state assembly for `agents serve`.
3
+ *
4
+ * Reuses the SAME structured data the CLI already emits — no recomputation:
5
+ * - teams: {@link handleTasks} + {@link handleStatus} + {@link toTaskStatusSummary}
6
+ * (the shape behind `agents teams status --json`), plus a per-worktree
7
+ * `git diff` via {@link gitDiff}.
8
+ * - routines: {@link listJobs} (the shape behind `agents routines list --json`).
9
+ * - cloud: {@link listTasks} from the local SQLite store (behind `agents cloud list --json`).
10
+ *
11
+ * Every panel is assembled independently: if one subsystem throws (e.g. the
12
+ * SQLite cloud store isn't provisioned on this box), that panel reports an
13
+ * `error` string and the others still render. This is an aggregator dashboard,
14
+ * not a fallback that hides a bug in a single code path.
15
+ */
16
+ import { AgentManager } from '../teams/agents.js';
17
+ import { handleTasks, handleStatus, toTaskStatusSummary } from '../teams/api.js';
18
+ import { gitDiff } from '../teams/worktree.js';
19
+ import { listJobs } from '../routines.js';
20
+ import { listTasks as listCloudTasks } from '../cloud/store.js';
21
+ /**
22
+ * Build the per-worktree diffs for a set of teammates. Only teammates with a
23
+ * `worktreePath` produce a diff; each gets a real `git diff HEAD` via
24
+ * {@link gitDiff}. Pure over its inputs (no manager, no globals) so it can be
25
+ * unit-tested against a real temp git worktree.
26
+ */
27
+ export async function buildWorktreeDiffs(agents) {
28
+ const withTree = agents.filter((a) => !!a.worktreePath);
29
+ return Promise.all(withTree.map(async (a) => ({
30
+ agent_id: a.agentId,
31
+ name: a.name,
32
+ agent_type: a.agentType,
33
+ status: a.status,
34
+ worktree_name: a.worktreeName,
35
+ worktree_path: a.worktreePath,
36
+ pr_url: a.prUrl,
37
+ diff: await gitDiff(a.worktreePath),
38
+ })));
39
+ }
40
+ /** Assemble the teams panel: overview counts, status summaries, worktree diffs. */
41
+ async function assembleTeams(manager) {
42
+ const { tasks } = await handleTasks(manager, 1000);
43
+ const panels = [];
44
+ for (const t of tasks) {
45
+ const status = toTaskStatusSummary(await handleStatus(manager, t.task_name, 'all'));
46
+ const teamAgents = await manager.listByTask(t.task_name);
47
+ const worktrees = await buildWorktreeDiffs(teamAgents.map((a) => ({
48
+ agentId: a.agentId,
49
+ name: a.name,
50
+ agentType: a.agentType,
51
+ status: a.status,
52
+ worktreeName: a.worktreeName,
53
+ worktreePath: a.worktreePath,
54
+ prUrl: a.prUrl,
55
+ })));
56
+ panels.push({
57
+ task_name: t.task_name,
58
+ agent_count: t.agent_count,
59
+ running: t.running,
60
+ completed: t.completed,
61
+ failed: t.failed,
62
+ agents: status.agents,
63
+ worktrees,
64
+ });
65
+ }
66
+ return panels;
67
+ }
68
+ /**
69
+ * Assemble the full read-only snapshot. Each panel degrades independently to an
70
+ * `error` string so one unavailable subsystem never blanks the whole dashboard.
71
+ *
72
+ * @param cwd - Project root used for project-scoped routine discovery.
73
+ * @param manager - Injectable for tests; defaults to a fresh {@link AgentManager}.
74
+ */
75
+ export async function assembleState(cwd = process.cwd(), manager = new AgentManager()) {
76
+ const [teams, routines, cloud] = await Promise.all([
77
+ assembleTeams(manager).then((data) => ({ ok: true, data }), (err) => ({ ok: false, error: String(err?.message ?? err) })),
78
+ Promise.resolve()
79
+ .then(() => listJobs(cwd))
80
+ .then((data) => ({ ok: true, data }), (err) => ({ ok: false, error: String(err?.message ?? err) })),
81
+ Promise.resolve()
82
+ .then(() => listCloudTasks({ limit: 50 }))
83
+ .then((data) => ({ ok: true, data }), (err) => ({ ok: false, error: String(err?.message ?? err) })),
84
+ ]);
85
+ return {
86
+ generated_at: new Date().toISOString(),
87
+ teams,
88
+ routines,
89
+ cloud,
90
+ };
91
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Self-contained HTML page for `agents serve`. No framework, no external assets
3
+ * — a single inline <style> + <script> that subscribes to /events (SSE) and
4
+ * re-renders the panels on each snapshot. Terminal-coded per the agents-cli
5
+ * brand: #0a0a0a bg, #a3e635 lime accent, JetBrains Mono.
6
+ */
7
+ export declare function renderPage(): string;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Self-contained HTML page for `agents serve`. No framework, no external assets
3
+ * — a single inline <style> + <script> that subscribes to /events (SSE) and
4
+ * re-renders the panels on each snapshot. Terminal-coded per the agents-cli
5
+ * brand: #0a0a0a bg, #a3e635 lime accent, JetBrains Mono.
6
+ */
7
+ export function renderPage() {
8
+ return `<!DOCTYPE html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8" />
12
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
13
+ <title>agents serve</title>
14
+ <style>
15
+ :root {
16
+ --bg: #0a0a0a; --panel: #121212; --border: #262626; --fg: #e5e5e5;
17
+ --dim: #737373; --accent: #a3e635; --add: #4ade80; --del: #f87171; --hunk: #60a5fa;
18
+ }
19
+ * { box-sizing: border-box; }
20
+ body {
21
+ margin: 0; background: var(--bg); color: var(--fg);
22
+ font-family: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
23
+ font-size: 13px; line-height: 1.5;
24
+ }
25
+ header {
26
+ position: sticky; top: 0; z-index: 10; background: var(--bg);
27
+ border-bottom: 1px solid var(--border); padding: 12px 20px;
28
+ display: flex; align-items: center; gap: 12px;
29
+ }
30
+ header .mark { color: var(--accent); font-weight: 700; letter-spacing: .5px; }
31
+ header .status { color: var(--dim); }
32
+ header .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--del); margin-right: 6px; vertical-align: middle; }
33
+ header .dot.live { background: var(--accent); }
34
+ main { padding: 20px; max-width: 1100px; margin: 0 auto; display: grid; gap: 24px; }
35
+ h2 { font-size: 14px; color: var(--accent); border-bottom: 1px solid var(--border); padding-bottom: 6px; margin: 0 0 12px; text-transform: uppercase; letter-spacing: 1px; }
36
+ .card { background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 14px; margin-bottom: 12px; }
37
+ .row { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
38
+ .muted { color: var(--dim); }
39
+ .badge { font-size: 11px; padding: 1px 7px; border-radius: 10px; border: 1px solid var(--border); }
40
+ .badge.running { color: var(--accent); border-color: var(--accent); }
41
+ .badge.completed { color: var(--add); border-color: var(--add); }
42
+ .badge.failed { color: var(--del); border-color: var(--del); }
43
+ a { color: var(--hunk); text-decoration: none; }
44
+ a:hover { text-decoration: underline; }
45
+ details { margin-top: 8px; }
46
+ summary { cursor: pointer; color: var(--dim); }
47
+ pre.diff { overflow-x: auto; background: #0d0d0d; border: 1px solid var(--border); border-radius: 4px; padding: 10px; margin: 8px 0 0; white-space: pre; }
48
+ pre.diff .a { color: var(--add); } pre.diff .d { color: var(--del); } pre.diff .h { color: var(--hunk); } pre.diff .m { color: var(--dim); }
49
+ .err { color: var(--del); }
50
+ .empty { color: var(--dim); font-style: italic; }
51
+ table { width: 100%; border-collapse: collapse; }
52
+ td, th { text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
53
+ th { color: var(--dim); font-weight: 500; }
54
+ </style>
55
+ </head>
56
+ <body>
57
+ <header>
58
+ <span class="mark">agents serve</span>
59
+ <span class="status"><span class="dot" id="dot"></span><span id="conn">connecting…</span></span>
60
+ <span class="status" id="ts"></span>
61
+ </header>
62
+ <main>
63
+ <section><h2>Teams &amp; Diffs</h2><div id="teams"></div></section>
64
+ <section><h2>Routines</h2><div id="routines"></div></section>
65
+ <section><h2>Cloud</h2><div id="cloud"></div></section>
66
+ </main>
67
+ <script>
68
+ const $ = (id) => document.getElementById(id);
69
+ const esc = (s) => String(s == null ? '' : s).replace(/[&<>"]/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
70
+ // href allowlist: only http(s) URLs are linkable. esc() blocks attribute
71
+ // breakout, but a javascript:/data: URL would still execute on click — so a
72
+ // non-http(s) pr_url renders as an inert '#' rather than a live link.
73
+ const safeUrl = (u) => { const s = String(u == null ? '' : u); return /^https?:\\/\\//i.test(s) ? s : '#'; };
74
+ function colorDiff(text) {
75
+ return esc(text).split('\\n').map((line) => {
76
+ if (line.startsWith('+')) return '<span class="a">' + line + '</span>';
77
+ if (line.startsWith('-')) return '<span class="d">' + line + '</span>';
78
+ if (line.startsWith('@@')) return '<span class="h">' + line + '</span>';
79
+ if (line.startsWith('diff ') || line.startsWith('index ')) return '<span class="m">' + line + '</span>';
80
+ return line;
81
+ }).join('\\n');
82
+ }
83
+ function panelError(el, error) { el.innerHTML = '<div class="card err">error: ' + esc(error) + '</div>'; }
84
+
85
+ function renderTeams(res) {
86
+ const el = $('teams');
87
+ if (!res.ok) return panelError(el, res.error);
88
+ if (!res.data.length) { el.innerHTML = '<div class="empty">no teams</div>'; return; }
89
+ el.innerHTML = res.data.map((t) => {
90
+ const agents = t.agents.map((a) =>
91
+ '<div class="row"><span>' + esc(a.name || a.agent_id.slice(0,8)) +
92
+ ' <span class="muted">' + esc(a.agent_type) + '</span>' +
93
+ (a.pr_url ? ' <a href="' + esc(safeUrl(a.pr_url)) + '" target="_blank">PR</a>' : '') +
94
+ '</span><span class="badge ' + esc(a.status) + '">' + esc(a.status) + '</span></div>'
95
+ ).join('');
96
+ const worktrees = t.worktrees.map((w) => {
97
+ const body = w.diff
98
+ ? '<details><summary>diff · ' + esc(w.worktree_name || w.worktree_path) + '</summary><pre class="diff">' + colorDiff(w.diff) + '</pre></details>'
99
+ : '<div class="muted">' + esc(w.worktree_name || w.worktree_path) + ' — no uncommitted changes</div>';
100
+ return body;
101
+ }).join('');
102
+ return '<div class="card"><div class="row"><strong>' + esc(t.task_name) + '</strong>' +
103
+ '<span class="muted">' + t.running + ' running · ' + t.completed + ' done · ' + t.failed + ' failed</span></div>' +
104
+ agents + worktrees + '</div>';
105
+ }).join('');
106
+ }
107
+ function renderRoutines(res) {
108
+ const el = $('routines');
109
+ if (!res.ok) return panelError(el, res.error);
110
+ if (!res.data.length) { el.innerHTML = '<div class="empty">no routines</div>'; return; }
111
+ el.innerHTML = '<div class="card"><table><tr><th>name</th><th>schedule</th><th>agent</th><th>enabled</th></tr>' +
112
+ res.data.map((j) => '<tr><td>' + esc(j.name) + '</td><td class="muted">' + esc(j.schedule) + '</td><td>' +
113
+ esc(j.workflow ? 'wf:' + j.workflow : j.agent) + '</td><td>' + (j.enabled ? 'yes' : '<span class="muted">no</span>') + '</td></tr>').join('') +
114
+ '</table></div>';
115
+ }
116
+ function renderCloud(res) {
117
+ const el = $('cloud');
118
+ if (!res.ok) return panelError(el, res.error);
119
+ if (!res.data.length) { el.innerHTML = '<div class="empty">no cloud tasks</div>'; return; }
120
+ el.innerHTML = '<div class="card"><table><tr><th>id</th><th>provider</th><th>status</th><th>repo</th><th></th></tr>' +
121
+ res.data.map((c) => '<tr><td>' + esc(c.id.slice(0,10)) + '</td><td>' + esc(c.provider) + '</td><td><span class="badge ' + esc(c.status) + '">' +
122
+ esc(c.status) + '</span></td><td class="muted">' + esc(c.repo || '') + '</td><td>' +
123
+ (c.prUrl ? '<a href="' + esc(safeUrl(c.prUrl)) + '" target="_blank">PR</a>' : '') + '</td></tr>').join('') +
124
+ '</table></div>';
125
+ }
126
+ function render(state) {
127
+ renderTeams(state.teams); renderRoutines(state.routines); renderCloud(state.cloud);
128
+ $('ts').textContent = 'updated ' + new Date(state.generated_at).toLocaleTimeString();
129
+ }
130
+ function setConn(live) {
131
+ $('dot').className = 'dot' + (live ? ' live' : '');
132
+ $('conn').textContent = live ? 'live' : 'reconnecting…';
133
+ }
134
+ const src = new EventSource('/events');
135
+ src.addEventListener('state', (e) => { setConn(true); render(JSON.parse(e.data)); });
136
+ src.onerror = () => setConn(false);
137
+ </script>
138
+ </body>
139
+ </html>`;
140
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Read-only localhost HTTP + Server-Sent-Events server for `agents serve`.
3
+ *
4
+ * There is deliberately NO framework and NO mutation surface:
5
+ * - binds 127.0.0.1 only (loopback) — never reachable off the machine;
6
+ * - answers GET only (every other method → 405);
7
+ * - routes: `GET /` → HTML page, `GET /api/state` → JSON snapshot,
8
+ * `GET /events` → SSE stream that re-pushes the snapshot on an interval.
9
+ *
10
+ * The whole thing is a viewer over data other commands already own
11
+ * ({@link assembleState}); it writes nothing.
12
+ */
13
+ import http from 'http';
14
+ import type { ServeState } from './data.js';
15
+ /** Loopback address — the server binds here and nowhere else. */
16
+ export declare const SERVE_HOST = "127.0.0.1";
17
+ /** Default port for `agents serve`. */
18
+ export declare const DEFAULT_SERVE_PORT = 4477;
19
+ /** Default SSE push cadence. */
20
+ export declare const DEFAULT_INTERVAL_MS = 3000;
21
+ export interface ServeOptions {
22
+ /** Project root for project-scoped routine discovery. Defaults to process.cwd(). */
23
+ cwd?: string;
24
+ /** SSE push cadence in ms. Defaults to {@link DEFAULT_INTERVAL_MS}. */
25
+ intervalMs?: number;
26
+ /**
27
+ * Snapshot data source. Defaults to {@link assembleState}. Read-only DI seam
28
+ * so tests can drive SSE lifecycle timing deterministically (e.g. gate the
29
+ * first snapshot to reproduce a mid-snapshot disconnect); production never
30
+ * sets it.
31
+ */
32
+ snapshot?: () => Promise<ServeState>;
33
+ }
34
+ /**
35
+ * Create (but do not start) the read-only serve server. Caller invokes
36
+ * `.listen(port, SERVE_HOST)`. Returned so tests can bind an ephemeral port.
37
+ */
38
+ export declare function createServeServer(opts?: ServeOptions): http.Server;
39
+ /**
40
+ * Start the serve server on `port`, bound to loopback. Resolves with the
41
+ * actual bound port (useful when `port === 0` for tests).
42
+ */
43
+ export declare function startServeServer(port?: number, opts?: ServeOptions): Promise<{
44
+ server: http.Server;
45
+ port: number;
46
+ }>;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Read-only localhost HTTP + Server-Sent-Events server for `agents serve`.
3
+ *
4
+ * There is deliberately NO framework and NO mutation surface:
5
+ * - binds 127.0.0.1 only (loopback) — never reachable off the machine;
6
+ * - answers GET only (every other method → 405);
7
+ * - routes: `GET /` → HTML page, `GET /api/state` → JSON snapshot,
8
+ * `GET /events` → SSE stream that re-pushes the snapshot on an interval.
9
+ *
10
+ * The whole thing is a viewer over data other commands already own
11
+ * ({@link assembleState}); it writes nothing.
12
+ */
13
+ import http from 'http';
14
+ import { assembleState } from './data.js';
15
+ import { renderPage } from './page.js';
16
+ /** Loopback address — the server binds here and nowhere else. */
17
+ export const SERVE_HOST = '127.0.0.1';
18
+ /** Default port for `agents serve`. */
19
+ export const DEFAULT_SERVE_PORT = 4477;
20
+ /** Default SSE push cadence. */
21
+ export const DEFAULT_INTERVAL_MS = 3000;
22
+ /**
23
+ * Create (but do not start) the read-only serve server. Caller invokes
24
+ * `.listen(port, SERVE_HOST)`. Returned so tests can bind an ephemeral port.
25
+ */
26
+ export function createServeServer(opts = {}) {
27
+ const cwd = opts.cwd ?? process.cwd();
28
+ const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
29
+ const snapshot = opts.snapshot ?? (() => assembleState(cwd));
30
+ const server = http.createServer(async (req, res) => {
31
+ // Read-only: reject anything that could mutate. Only GET is served.
32
+ if (req.method !== 'GET') {
33
+ res.writeHead(405, { 'content-type': 'text/plain', allow: 'GET' });
34
+ res.end('method not allowed');
35
+ return;
36
+ }
37
+ const url = (req.url || '/').split('?')[0];
38
+ if (url === '/' || url === '/index.html') {
39
+ const html = renderPage();
40
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
41
+ res.end(html);
42
+ return;
43
+ }
44
+ if (url === '/api/state') {
45
+ try {
46
+ const state = await snapshot();
47
+ res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' });
48
+ res.end(JSON.stringify(state));
49
+ }
50
+ catch (err) {
51
+ res.writeHead(500, { 'content-type': 'application/json' });
52
+ res.end(JSON.stringify({ error: String(err?.message ?? err) }));
53
+ }
54
+ return;
55
+ }
56
+ if (url === '/events') {
57
+ res.writeHead(200, {
58
+ 'content-type': 'text/event-stream',
59
+ 'cache-control': 'no-cache',
60
+ connection: 'keep-alive',
61
+ });
62
+ let closed = false;
63
+ // Declare the timer first and register the disconnect cleanup BEFORE the
64
+ // first `await push()`. `assembleState()` (team scan + git diffs) can be
65
+ // slow; if the client disconnects during it, the one-shot 'close' must
66
+ // already have a listener — otherwise it fires into the void, the interval
67
+ // starts anyway, and we leak a timer writing to a destroyed socket forever.
68
+ let timer;
69
+ req.on('close', () => {
70
+ closed = true;
71
+ if (timer)
72
+ clearInterval(timer);
73
+ });
74
+ const push = async () => {
75
+ if (closed)
76
+ return;
77
+ try {
78
+ const state = await snapshot();
79
+ if (closed)
80
+ return;
81
+ res.write(`event: state\ndata: ${JSON.stringify(state)}\n\n`);
82
+ }
83
+ catch (err) {
84
+ if (!closed)
85
+ res.write(`event: error\ndata: ${JSON.stringify({ error: String(err) })}\n\n`);
86
+ }
87
+ };
88
+ await push(); // immediate first frame
89
+ // Only arm the interval if the client is still connected. If 'close' fired
90
+ // during the first push, `closed` is already true (and the handler ran
91
+ // while `timer` was undefined), so starting an interval here would leak.
92
+ if (!closed)
93
+ timer = setInterval(push, intervalMs);
94
+ return;
95
+ }
96
+ res.writeHead(404, { 'content-type': 'text/plain' });
97
+ res.end('not found');
98
+ });
99
+ return server;
100
+ }
101
+ /**
102
+ * Start the serve server on `port`, bound to loopback. Resolves with the
103
+ * actual bound port (useful when `port === 0` for tests).
104
+ */
105
+ export function startServeServer(port = DEFAULT_SERVE_PORT, opts = {}) {
106
+ const server = createServeServer(opts);
107
+ return new Promise((resolve, reject) => {
108
+ server.once('error', reject);
109
+ server.listen(port, SERVE_HOST, () => {
110
+ const addr = server.address();
111
+ const boundPort = typeof addr === 'object' && addr ? addr.port : port;
112
+ resolve({ server, port: boundPort });
113
+ });
114
+ });
115
+ }
@@ -1,5 +1,13 @@
1
+ import { type PidSessionEntry } from './pid-registry.js';
1
2
  import { type SessionActivity, type AwaitingReason, type DetectedPr, type DetectedWorktree, type DetectedTicket } from './state.js';
2
3
  import { type SessionProvenance } from './provenance.js';
4
+ /**
5
+ * Per-PID `lsof` probes run bounded and staggered rather than as one parallel
6
+ * fan-out: a simultaneous system-wide `lsof` burst reads to behavioral EDR
7
+ * (CrowdStrike Falcon) as lateral-movement recon. Results are identical — the
8
+ * cwds are just gathered at a bounded spawn rate instead of a single burst.
9
+ */
10
+ export declare const LSOF_CONCURRENCY = 4;
3
11
  export type ActiveContext = 'terminal' | 'teams' | 'cloud' | 'headless';
4
12
  export type ActiveStatus = 'running' | 'idle' | 'queued' | 'input_required';
5
13
  export interface ActiveSession {
@@ -38,6 +46,13 @@ export interface ActiveSession {
38
46
  * Absent for cloud sessions (no local pid) and any pid whose env is unreadable.
39
47
  */
40
48
  provenance?: SessionProvenance;
49
+ /**
50
+ * The machine this session runs on, as a normalized device id (machineId()
51
+ * form). Set when merging cross-machine results so the grouped `--active`
52
+ * view can bucket by computer. Absent for a purely local query (the renderer
53
+ * falls back to provenance.host, then the local machine).
54
+ */
55
+ machine?: string;
41
56
  teamName?: string;
42
57
  agentId?: string;
43
58
  cloudProvider?: string;
@@ -62,6 +77,51 @@ export declare function listTeamsActive(): Promise<ActiveSession[]>;
62
77
  export declare function listTerminalsActive(): Promise<ActiveSession[]>;
63
78
  /** Cloud tasks still in a non-terminal state. `tasks.db` may not exist; that's fine. */
64
79
  export declare function listCloudActive(): ActiveSession[];
80
+ interface ProcRow {
81
+ pid: number;
82
+ ppid: number;
83
+ comm: string;
84
+ kind?: string;
85
+ }
86
+ /** Parse `ConvertTo-Csv` output of Win32_Process rows. Exported for tests. */
87
+ export declare function parseWin32ProcessCsv(out: string): ProcRow[];
88
+ /**
89
+ * Resolve every candidate PID's cwd, bounded and staggered so the probes no
90
+ * longer fan out as one simultaneous system-wide `lsof` burst (a behavioral-EDR
91
+ * recon trigger). Order matches the input `pids`. The `probe` seam is injectable
92
+ * for testing the bound; production always uses the real `lsof`-backed probe.
93
+ */
94
+ export declare function resolveCwds(pids: number[], probe?: (pid: number) => Promise<string | undefined>): Promise<(string | undefined)[]>;
95
+ export interface AgentCandidate {
96
+ pid: number;
97
+ kind: string;
98
+ }
99
+ /**
100
+ * Find the launch registry entry recorded by a WRAPPER of this process. The
101
+ * shim delegate records the pid it spawned, but on Windows the `.cmd` shell
102
+ * path makes that a cmd.exe intermediary whose child is the real agent binary
103
+ * — so the agent pid itself has no entry and the wrapper one ancestor up does.
104
+ * The nearest entry wins, and only if its agent matches the candidate's kind:
105
+ * a claude session shelling out to codex must not hand codex its identity.
106
+ */
107
+ export declare function readAncestorSessionEntry(pid: number, ppidMap: Map<number, number>, kind: string, readEntry?: (pid: number) => PidSessionEntry | undefined): PidSessionEntry | undefined;
108
+ /**
109
+ * Collapse agent processes spawned by another live agent process of the same
110
+ * kind onto their nearest kept ancestor. Claude runs subagents, forks, and
111
+ * even its bundled ripgrep as child `claude` processes — on POSIX those
112
+ * children resolve to the parent's cwd and collapse in dedupeBySession, but
113
+ * where no cwd can be recovered (Windows has no lsof) every fork would print
114
+ * as its own headless row. Two exceptions keep their own row: a candidate with
115
+ * its own registry entry — on its pid OR on a wrapper ancestor strictly below
116
+ * the pid it would fold into (the shim's entry lands on the cmd.exe
117
+ * intermediary on Windows) — and a child of a *different* agent kind (claude
118
+ * shelling out to codex is a real second session, not a fork).
119
+ * Returns the kept roots plus, per root pid, how many descendants folded in.
120
+ */
121
+ export declare function foldSubordinateAgents(candidates: AgentCandidate[], ppidMap: Map<number, number>, readEntry: (pid: number) => PidSessionEntry | undefined): {
122
+ kept: AgentCandidate[];
123
+ foldedByRoot: Map<number, number>;
124
+ };
65
125
  /**
66
126
  * Agent processes not attributed to a team or the runtime registry.
67
127
  * Classified by walking the ppid chain: any recognised UI ancestor (IDE
@@ -75,3 +135,4 @@ export declare function listUnattributedActive(attributed: Set<number>): Promise
75
135
  * already-attributed PIDs removed.
76
136
  */
77
137
  export declare function getActiveSessions(opts?: ActiveQueryOptions): Promise<ActiveSession[]>;
138
+ export {};