@phnx-labs/agents-cli 1.20.35 → 1.20.37

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 (236) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/LICENSE +185 -21
  3. package/README.md +8 -4
  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.js +81 -0
  11. package/dist/commands/daemon.js +4 -1
  12. package/dist/commands/doctor.js +1 -89
  13. package/dist/commands/events.js +3 -3
  14. package/dist/commands/exec.d.ts +19 -0
  15. package/dist/commands/exec.js +277 -11
  16. package/dist/commands/hosts.js +10 -6
  17. package/dist/commands/inspect.js +8 -9
  18. package/dist/commands/lock.d.ts +12 -0
  19. package/dist/commands/lock.js +70 -0
  20. package/dist/commands/message.d.ts +15 -0
  21. package/dist/commands/message.js +56 -0
  22. package/dist/commands/routines.js +101 -5
  23. package/dist/commands/secrets-migrate.js +106 -57
  24. package/dist/commands/secrets.d.ts +31 -18
  25. package/dist/commands/secrets.js +156 -75
  26. package/dist/commands/serve.d.ts +10 -0
  27. package/dist/commands/serve.js +37 -0
  28. package/dist/commands/sessions-inject.d.ts +14 -0
  29. package/dist/commands/sessions-inject.js +111 -0
  30. package/dist/commands/sessions-picker.d.ts +2 -0
  31. package/dist/commands/sessions-picker.js +24 -3
  32. package/dist/commands/sessions-resume.js +20 -8
  33. package/dist/commands/sessions-sync.d.ts +3 -0
  34. package/dist/commands/sessions-sync.js +44 -4
  35. package/dist/commands/sessions.d.ts +78 -1
  36. package/dist/commands/sessions.js +506 -66
  37. package/dist/commands/setup.js +4 -2
  38. package/dist/commands/sync.d.ts +3 -1
  39. package/dist/commands/sync.js +156 -4
  40. package/dist/commands/teams.js +217 -0
  41. package/dist/commands/versions.js +2 -4
  42. package/dist/commands/watchdog.d.ts +18 -0
  43. package/dist/commands/watchdog.js +238 -0
  44. package/dist/index.js +25 -2
  45. package/dist/lib/audit/log.d.ts +92 -0
  46. package/dist/lib/audit/log.js +177 -0
  47. package/dist/lib/auto-pull.js +2 -1
  48. package/dist/lib/browser/chrome.d.ts +10 -0
  49. package/dist/lib/browser/chrome.js +18 -7
  50. package/dist/lib/browser/drivers/ssh.js +2 -1
  51. package/dist/lib/browser/har.d.ts +84 -0
  52. package/dist/lib/browser/har.js +77 -0
  53. package/dist/lib/browser/ipc.js +24 -3
  54. package/dist/lib/browser/profiles.d.ts +1 -1
  55. package/dist/lib/browser/profiles.js +8 -10
  56. package/dist/lib/browser/refs.d.ts +65 -0
  57. package/dist/lib/browser/refs.js +73 -1
  58. package/dist/lib/browser/runtime-state.js +1 -0
  59. package/dist/lib/browser/service.d.ts +38 -2
  60. package/dist/lib/browser/service.js +112 -8
  61. package/dist/lib/browser/types.d.ts +14 -1
  62. package/dist/lib/budget/live-cloud.d.ts +42 -0
  63. package/dist/lib/budget/live-cloud.js +79 -0
  64. package/dist/lib/budget/live-team.d.ts +31 -0
  65. package/dist/lib/budget/live-team.js +115 -0
  66. package/dist/lib/cloud/codex.js +4 -0
  67. package/dist/lib/cloud/rush.d.ts +12 -1
  68. package/dist/lib/cloud/rush.js +13 -3
  69. package/dist/lib/cloud/types.d.ts +9 -0
  70. package/dist/lib/computer/dispatch.d.ts +8 -0
  71. package/dist/lib/computer/dispatch.js +125 -0
  72. package/dist/lib/computer/loop.d.ts +62 -0
  73. package/dist/lib/computer/loop.js +98 -0
  74. package/dist/lib/computer/model.d.ts +44 -0
  75. package/dist/lib/computer/model.js +157 -0
  76. package/dist/lib/concurrency.d.ts +19 -0
  77. package/dist/lib/concurrency.js +33 -0
  78. package/dist/lib/daemon.d.ts +57 -0
  79. package/dist/lib/daemon.js +196 -18
  80. package/dist/lib/devices/registry.d.ts +7 -0
  81. package/dist/lib/devices/registry.js +24 -0
  82. package/dist/lib/devices/resolve-target.d.ts +24 -0
  83. package/dist/lib/devices/resolve-target.js +80 -0
  84. package/dist/lib/devices/tailscale.js +1 -1
  85. package/dist/lib/drift.d.ts +52 -0
  86. package/dist/lib/drift.js +112 -0
  87. package/dist/lib/events.d.ts +1 -1
  88. package/dist/lib/events.js +31 -13
  89. package/dist/lib/exec.d.ts +17 -0
  90. package/dist/lib/exec.js +79 -13
  91. package/dist/lib/git.d.ts +27 -0
  92. package/dist/lib/git.js +56 -1
  93. package/dist/lib/hooks/cache.d.ts +6 -0
  94. package/dist/lib/hooks/cache.js +54 -12
  95. package/dist/lib/hooks.d.ts +27 -0
  96. package/dist/lib/hooks.js +127 -8
  97. package/dist/lib/hosts/dispatch.d.ts +15 -0
  98. package/dist/lib/hosts/dispatch.js +39 -6
  99. package/dist/lib/hosts/logs.js +30 -1
  100. package/dist/lib/hosts/option.js +1 -1
  101. package/dist/lib/hosts/passthrough.js +3 -1
  102. package/dist/lib/hosts/ready.d.ts +29 -6
  103. package/dist/lib/hosts/ready.js +66 -15
  104. package/dist/lib/hosts/registry.d.ts +19 -2
  105. package/dist/lib/hosts/registry.js +58 -2
  106. package/dist/lib/hosts/remote-cmd.d.ts +62 -1
  107. package/dist/lib/hosts/remote-cmd.js +70 -1
  108. package/dist/lib/hosts/remote-os.d.ts +17 -0
  109. package/dist/lib/hosts/remote-os.js +30 -0
  110. package/dist/lib/hosts/session-index.d.ts +34 -0
  111. package/dist/lib/hosts/session-index.js +56 -0
  112. package/dist/lib/hosts/tasks.d.ts +14 -0
  113. package/dist/lib/hosts/tasks.js +15 -0
  114. package/dist/lib/lock.d.ts +93 -0
  115. package/dist/lib/lock.js +207 -0
  116. package/dist/lib/loop.js +16 -1
  117. package/dist/lib/machine-id.d.ts +21 -0
  118. package/dist/lib/machine-id.js +26 -0
  119. package/dist/lib/mailbox-target.d.ts +36 -0
  120. package/dist/lib/mailbox-target.js +45 -0
  121. package/dist/lib/mailbox.d.ts +47 -0
  122. package/dist/lib/mailbox.js +194 -0
  123. package/dist/lib/mcp.d.ts +5 -0
  124. package/dist/lib/mcp.js +24 -8
  125. package/dist/lib/migrate.d.ts +19 -0
  126. package/dist/lib/migrate.js +134 -26
  127. package/dist/lib/overdue.js +3 -0
  128. package/dist/lib/picker.d.ts +2 -0
  129. package/dist/lib/picker.js +4 -1
  130. package/dist/lib/platform/exec.d.ts +46 -0
  131. package/dist/lib/platform/exec.js +74 -0
  132. package/dist/lib/platform/process.d.ts +31 -0
  133. package/dist/lib/platform/process.js +34 -1
  134. package/dist/lib/platform/winpath.js +2 -0
  135. package/dist/lib/plugins.js +16 -6
  136. package/dist/lib/profiles.d.ts +25 -0
  137. package/dist/lib/profiles.js +22 -6
  138. package/dist/lib/pty-client.js +2 -1
  139. package/dist/lib/rotate.d.ts +61 -0
  140. package/dist/lib/rotate.js +52 -0
  141. package/dist/lib/routines.d.ts +40 -2
  142. package/dist/lib/routines.js +66 -8
  143. package/dist/lib/runner.d.ts +11 -2
  144. package/dist/lib/runner.js +49 -7
  145. package/dist/lib/scheduler.js +6 -1
  146. package/dist/lib/secrets/bundles.d.ts +60 -4
  147. package/dist/lib/secrets/bundles.js +131 -12
  148. package/dist/lib/secrets/filestore.d.ts +3 -0
  149. package/dist/lib/secrets/filestore.js +42 -16
  150. package/dist/lib/secrets/index.d.ts +43 -2
  151. package/dist/lib/secrets/index.js +102 -3
  152. package/dist/lib/secrets/mcp.d.ts +93 -0
  153. package/dist/lib/secrets/mcp.js +205 -0
  154. package/dist/lib/secrets/remote.js +12 -5
  155. package/dist/lib/secrets/sync.js +83 -4
  156. package/dist/lib/secrets/windows.js +14 -3
  157. package/dist/lib/serve/data.d.ts +81 -0
  158. package/dist/lib/serve/data.js +91 -0
  159. package/dist/lib/serve/page.d.ts +7 -0
  160. package/dist/lib/serve/page.js +140 -0
  161. package/dist/lib/serve/server.d.ts +46 -0
  162. package/dist/lib/serve/server.js +115 -0
  163. package/dist/lib/session/active.d.ts +73 -0
  164. package/dist/lib/session/active.js +199 -22
  165. package/dist/lib/session/db.d.ts +2 -1
  166. package/dist/lib/session/db.js +41 -5
  167. package/dist/lib/session/discover.d.ts +39 -0
  168. package/dist/lib/session/discover.js +127 -29
  169. package/dist/lib/session/ghostty-tabs.d.ts +33 -0
  170. package/dist/lib/session/ghostty-tabs.js +126 -0
  171. package/dist/lib/session/inject.d.ts +18 -0
  172. package/dist/lib/session/inject.js +21 -0
  173. package/dist/lib/session/parse.js +23 -20
  174. package/dist/lib/session/pid-registry.d.ts +1 -0
  175. package/dist/lib/session/pid-registry.js +24 -0
  176. package/dist/lib/session/provenance.d.ts +14 -2
  177. package/dist/lib/session/provenance.js +39 -8
  178. package/dist/lib/session/relative-time.js +6 -2
  179. package/dist/lib/session/remote-active.js +19 -17
  180. package/dist/lib/session/remote-list.d.ts +51 -0
  181. package/dist/lib/session/remote-list.js +205 -0
  182. package/dist/lib/session/remote.d.ts +7 -1
  183. package/dist/lib/session/remote.js +20 -4
  184. package/dist/lib/session/sync/config.d.ts +14 -15
  185. package/dist/lib/session/sync/config.js +60 -20
  186. package/dist/lib/session/types.d.ts +23 -0
  187. package/dist/lib/shims.d.ts +36 -6
  188. package/dist/lib/shims.js +91 -29
  189. package/dist/lib/ssh-exec.js +2 -0
  190. package/dist/lib/ssh-tunnel.js +2 -1
  191. package/dist/lib/startup/command-registry.d.ts +6 -0
  192. package/dist/lib/startup/command-registry.js +13 -1
  193. package/dist/lib/state.d.ts +13 -0
  194. package/dist/lib/state.js +103 -9
  195. package/dist/lib/sync-umbrella.d.ts +14 -7
  196. package/dist/lib/sync-umbrella.js +21 -13
  197. package/dist/lib/teams/forEach.d.ts +110 -0
  198. package/dist/lib/teams/forEach.js +186 -0
  199. package/dist/lib/teams/index.d.ts +1 -0
  200. package/dist/lib/teams/index.js +1 -0
  201. package/dist/lib/teams/pr-watch.d.ts +226 -0
  202. package/dist/lib/teams/pr-watch.js +371 -0
  203. package/dist/lib/teams/supervisor.d.ts +14 -1
  204. package/dist/lib/teams/supervisor.js +19 -0
  205. package/dist/lib/teams/worktree.d.ts +9 -0
  206. package/dist/lib/teams/worktree.js +32 -0
  207. package/dist/lib/terminal/backends/index.d.ts +2 -1
  208. package/dist/lib/terminal/backends/index.js +3 -1
  209. package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
  210. package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
  211. package/dist/lib/terminal/index.d.ts +4 -1
  212. package/dist/lib/terminal/index.js +4 -1
  213. package/dist/lib/terminal/inject.d.ts +204 -0
  214. package/dist/lib/terminal/inject.js +247 -0
  215. package/dist/lib/terminal/resolve.d.ts +64 -0
  216. package/dist/lib/terminal/resolve.js +90 -0
  217. package/dist/lib/terminal/types.d.ts +1 -1
  218. package/dist/lib/tmux/session.d.ts +10 -0
  219. package/dist/lib/tmux/session.js +31 -0
  220. package/dist/lib/triggers/webhook.d.ts +85 -0
  221. package/dist/lib/triggers/webhook.js +141 -0
  222. package/dist/lib/versions.d.ts +23 -0
  223. package/dist/lib/versions.js +119 -13
  224. package/dist/lib/watchdog/index.d.ts +3 -0
  225. package/dist/lib/watchdog/index.js +5 -0
  226. package/dist/lib/watchdog/read.d.ts +35 -0
  227. package/dist/lib/watchdog/read.js +149 -0
  228. package/dist/lib/watchdog/runner.d.ts +127 -0
  229. package/dist/lib/watchdog/runner.js +322 -0
  230. package/dist/lib/watchdog/watchdog.d.ts +40 -0
  231. package/dist/lib/watchdog/watchdog.js +166 -0
  232. package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
  233. package/dist/lib/watchdog/watchdogTail.js +154 -0
  234. package/dist/lib/workflows.d.ts +166 -0
  235. package/dist/lib/workflows.js +193 -0
  236. package/package.json +5 -4
@@ -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 {
@@ -58,6 +66,24 @@ export interface ActiveSession {
58
66
  * two windows have the same cwd open. Only populated for `terminal` context.
59
67
  */
60
68
  windowId?: string;
69
+ /**
70
+ * Controlling TTY of the agent process (e.g. 'ttys003'), from the `ps -A`
71
+ * read. macOS/Linux terminal sessions only; '??'/none normalized to undefined.
72
+ * A disambiguation bridge (and the basis for future terminal addressing).
73
+ */
74
+ tty?: string;
75
+ /**
76
+ * Ghostty tab index (1-based) the session is shown in, when it can be matched
77
+ * to a Ghostty surface by working directory (+ title). Transient, populated by
78
+ * the renderer just before printing — NOT part of the pure discovery path.
79
+ */
80
+ ghosttyTab?: number;
81
+ /**
82
+ * Resolved tmux attach target (`session:window.pane`) for a tmux-hosted local
83
+ * session, from the pane id via `mapPanesToTargets`. Transient, renderer-set
84
+ * (after the --json/--waiting gates) — NOT emitted on the discovery path.
85
+ */
86
+ tmuxTarget?: string;
61
87
  }
62
88
  export interface ActiveQueryOptions {
63
89
  /** Skip the `ps` scan for ad-hoc headless agents. */
@@ -69,6 +95,52 @@ export declare function listTeamsActive(): Promise<ActiveSession[]>;
69
95
  export declare function listTerminalsActive(): Promise<ActiveSession[]>;
70
96
  /** Cloud tasks still in a non-terminal state. `tasks.db` may not exist; that's fine. */
71
97
  export declare function listCloudActive(): ActiveSession[];
98
+ interface ProcRow {
99
+ pid: number;
100
+ ppid: number;
101
+ tty?: string;
102
+ comm: string;
103
+ kind?: string;
104
+ }
105
+ /** Parse `ConvertTo-Csv` output of Win32_Process rows. Exported for tests. */
106
+ export declare function parseWin32ProcessCsv(out: string): ProcRow[];
107
+ /**
108
+ * Resolve every candidate PID's cwd, bounded and staggered so the probes no
109
+ * longer fan out as one simultaneous system-wide `lsof` burst (a behavioral-EDR
110
+ * recon trigger). Order matches the input `pids`. The `probe` seam is injectable
111
+ * for testing the bound; production always uses the real `lsof`-backed probe.
112
+ */
113
+ export declare function resolveCwds(pids: number[], probe?: (pid: number) => Promise<string | undefined>): Promise<(string | undefined)[]>;
114
+ export interface AgentCandidate {
115
+ pid: number;
116
+ kind: string;
117
+ }
118
+ /**
119
+ * Find the launch registry entry recorded by a WRAPPER of this process. The
120
+ * shim delegate records the pid it spawned, but on Windows the `.cmd` shell
121
+ * path makes that a cmd.exe intermediary whose child is the real agent binary
122
+ * — so the agent pid itself has no entry and the wrapper one ancestor up does.
123
+ * The nearest entry wins, and only if its agent matches the candidate's kind:
124
+ * a claude session shelling out to codex must not hand codex its identity.
125
+ */
126
+ export declare function readAncestorSessionEntry(pid: number, ppidMap: Map<number, number>, kind: string, readEntry?: (pid: number) => PidSessionEntry | undefined): PidSessionEntry | undefined;
127
+ /**
128
+ * Collapse agent processes spawned by another live agent process of the same
129
+ * kind onto their nearest kept ancestor. Claude runs subagents, forks, and
130
+ * even its bundled ripgrep as child `claude` processes — on POSIX those
131
+ * children resolve to the parent's cwd and collapse in dedupeBySession, but
132
+ * where no cwd can be recovered (Windows has no lsof) every fork would print
133
+ * as its own headless row. Two exceptions keep their own row: a candidate with
134
+ * its own registry entry — on its pid OR on a wrapper ancestor strictly below
135
+ * the pid it would fold into (the shim's entry lands on the cmd.exe
136
+ * intermediary on Windows) — and a child of a *different* agent kind (claude
137
+ * shelling out to codex is a real second session, not a fork).
138
+ * Returns the kept roots plus, per root pid, how many descendants folded in.
139
+ */
140
+ export declare function foldSubordinateAgents(candidates: AgentCandidate[], ppidMap: Map<number, number>, readEntry: (pid: number) => PidSessionEntry | undefined): {
141
+ kept: AgentCandidate[];
142
+ foldedByRoot: Map<number, number>;
143
+ };
72
144
  /**
73
145
  * Agent processes not attributed to a team or the runtime registry.
74
146
  * Classified by walking the ppid chain: any recognised UI ancestor (IDE
@@ -82,3 +154,4 @@ export declare function listUnattributedActive(attributed: Set<number>): Promise
82
154
  * already-attributed PIDs removed.
83
155
  */
84
156
  export declare function getActiveSessions(opts?: ActiveQueryOptions): Promise<ActiveSession[]>;
157
+ export {};