@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.
- package/CHANGELOG.md +91 -0
- package/LICENSE +185 -21
- package/README.md +8 -4
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer.js +81 -0
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.js +1 -89
- package/dist/commands/events.js +3 -3
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +277 -11
- package/dist/commands/hosts.js +10 -6
- package/dist/commands/inspect.js +8 -9
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +156 -75
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.js +20 -8
- package/dist/commands/sessions-sync.d.ts +3 -0
- package/dist/commands/sessions-sync.js +44 -4
- package/dist/commands/sessions.d.ts +78 -1
- package/dist/commands/sessions.js +506 -66
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +25 -2
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.js +2 -1
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.js +24 -3
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +112 -8
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +57 -0
- package/dist/lib/daemon.js +196 -18
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/resolve-target.d.ts +24 -0
- package/dist/lib/devices/resolve-target.js +80 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/events.js +31 -13
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +79 -13
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +127 -8
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +39 -6
- package/dist/lib/hosts/logs.js +30 -1
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/ready.d.ts +29 -6
- package/dist/lib/hosts/ready.js +66 -15
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +14 -0
- package/dist/lib/hosts/tasks.js +15 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/picker.d.ts +2 -0
- package/dist/lib/picker.js +4 -1
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugins.js +16 -6
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +73 -0
- package/dist/lib/session/active.js +199 -22
- package/dist/lib/session/db.d.ts +2 -1
- package/dist/lib/session/db.js +41 -5
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +127 -29
- package/dist/lib/session/ghostty-tabs.d.ts +33 -0
- package/dist/lib/session/ghostty-tabs.js +126 -0
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.js +23 -20
- package/dist/lib/session/pid-registry.d.ts +1 -0
- package/dist/lib/session/pid-registry.js +24 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/relative-time.js +6 -2
- package/dist/lib/session/remote-active.js +19 -17
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +205 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +20 -4
- package/dist/lib/session/sync/config.d.ts +14 -15
- package/dist/lib/session/sync/config.js +60 -20
- package/dist/lib/session/types.d.ts +23 -0
- package/dist/lib/shims.d.ts +36 -6
- package/dist/lib/shims.js +91 -29
- package/dist/lib/ssh-exec.js +2 -0
- package/dist/lib/ssh-tunnel.js +2 -1
- package/dist/lib/startup/command-registry.d.ts +6 -0
- package/dist/lib/startup/command-registry.js +13 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +21 -13
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/index.d.ts +2 -1
- package/dist/lib/terminal/backends/index.js +3 -1
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/index.d.ts +4 -1
- package/dist/lib/terminal/index.js +4 -1
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/tmux/session.d.ts +10 -0
- package/dist/lib/tmux/session.js +31 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/versions.d.ts +23 -0
- package/dist/lib/versions.js +119 -13
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- 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 & 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) => ({'&':'&','<':'<','>':'>','"':'"'}[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 {};
|