ai-agentmanager 0.0.0-stage → 1.0.0
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +767 -2
- package/SECURITY.md +44 -0
- package/bin/agentmgr-hub.js +16 -0
- package/bin/agentmgr.js +4 -0
- package/docs/architecture.md +67 -0
- package/docs/distributed.md +147 -0
- package/docs/engines.md +177 -0
- package/docs/images/agents.jpg +0 -0
- package/docs/images/conflict-radar.jpg +0 -0
- package/docs/plugins.md +81 -0
- package/package.json +56 -4
- package/public/app.js +3083 -0
- package/public/hub/hub.css +230 -0
- package/public/hub/hub.js +766 -0
- package/public/hub/index.html +146 -0
- package/public/hub/term.js +147 -0
- package/public/index.html +342 -0
- package/public/share.html +34 -0
- package/public/share.js +41 -0
- package/public/style.css +541 -0
- package/public/touchscroll.js +73 -0
- package/public/transport.js +78 -0
- package/scripts/build-release.sh +58 -0
- package/scripts/fix-pty.js +15 -0
- package/src/backlog.js +413 -0
- package/src/brief.js +512 -0
- package/src/cli.js +1447 -0
- package/src/client.js +49 -0
- package/src/config.js +109 -0
- package/src/conflicts.js +105 -0
- package/src/engines/antigravity.js +296 -0
- package/src/engines/claude.js +98 -0
- package/src/engines/codex.js +331 -0
- package/src/engines/index.js +37 -0
- package/src/fleet.js +176 -0
- package/src/git.js +340 -0
- package/src/hub.js +857 -0
- package/src/hubactions.js +107 -0
- package/src/hubbacklog.js +373 -0
- package/src/hublink.js +437 -0
- package/src/inbox.js +94 -0
- package/src/judge.js +278 -0
- package/src/livebrowser.js +637 -0
- package/src/manager.js +2554 -0
- package/src/notify.js +284 -0
- package/src/plugins.js +119 -0
- package/src/preview.js +334 -0
- package/src/races.js +192 -0
- package/src/remote.js +284 -0
- package/src/server.js +759 -0
- package/src/service.js +150 -0
- package/src/sessions.js +160 -0
- package/src/store.js +104 -0
- package/src/stuck.js +160 -0
- package/src/tracker.js +311 -0
- package/src/trust.js +44 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="referrer" content="no-referrer" />
|
|
7
|
+
<title>Agent Manager Hub</title>
|
|
8
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22%3E%3Cpath d=%22M8 1l7 7-7 7-7-7z%22 fill=%22%233b5bdb%22/%3E%3C/svg%3E" />
|
|
9
|
+
<link rel="stylesheet" href="/vendor/xterm/css/xterm.css" />
|
|
10
|
+
<link rel="stylesheet" href="/hub.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<header class="topbar">
|
|
14
|
+
<div class="brand">
|
|
15
|
+
<span class="logo" aria-hidden="true">◆</span>
|
|
16
|
+
<h1>Agent Manager <span class="muted">Hub</span></h1>
|
|
17
|
+
<span id="hub-name" class="muted"></span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="top-actions" id="top-actions" hidden>
|
|
20
|
+
<span id="conn" class="conn">connecting…</span>
|
|
21
|
+
<button id="new-btn" class="primary" hidden>New agent</button>
|
|
22
|
+
<button id="invite-btn">Add machine</button>
|
|
23
|
+
<button id="logout-btn" class="ghost">Sign out</button>
|
|
24
|
+
</div>
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<main id="login" class="login" hidden>
|
|
28
|
+
<form id="login-form" class="card">
|
|
29
|
+
<h2>Sign in to the hub</h2>
|
|
30
|
+
<a id="google-btn" class="google-btn" href="/api/auth/google" hidden>Sign in with Google</a>
|
|
31
|
+
<p id="login-error" class="error" hidden></p>
|
|
32
|
+
<details id="token-box" open>
|
|
33
|
+
<summary id="token-summary">Use the access token</summary>
|
|
34
|
+
<p class="muted">Paste the hub's access token. Where the hub runs, <code>agentmgr hub token</code> prints it.</p>
|
|
35
|
+
<input id="token" type="password" autocomplete="current-password" placeholder="Access token" required />
|
|
36
|
+
<button class="primary" type="submit">Sign in</button>
|
|
37
|
+
</details>
|
|
38
|
+
</form>
|
|
39
|
+
</main>
|
|
40
|
+
|
|
41
|
+
<main id="app" class="app" hidden>
|
|
42
|
+
<section class="machines" id="machines" aria-label="Machines"></section>
|
|
43
|
+
|
|
44
|
+
<nav class="tabs" role="tablist">
|
|
45
|
+
<button role="tab" data-tab="inbox" class="active">Needs you <span class="count" id="inbox-count">0</span></button>
|
|
46
|
+
<button role="tab" data-tab="agents">Agents <span class="count" id="agents-count">0</span></button>
|
|
47
|
+
<button role="tab" data-tab="backlog">Backlog <span class="count" id="backlog-count">0</span></button>
|
|
48
|
+
<div class="spacer"></div>
|
|
49
|
+
<span id="filter-chip" class="chip" hidden></span>
|
|
50
|
+
<input id="search" type="search" placeholder="Filter by name, intent, project…" aria-label="Filter" />
|
|
51
|
+
</nav>
|
|
52
|
+
|
|
53
|
+
<section id="tab-inbox" class="tab"></section>
|
|
54
|
+
<section id="tab-agents" class="tab" hidden></section>
|
|
55
|
+
<section id="tab-backlog" class="tab" hidden></section>
|
|
56
|
+
<p class="footnote muted" id="footnote">
|
|
57
|
+
Buttons appear for what each machine allows the hub (the chips on its card); it decides that in the <code>hub</code> section of its <code>config.json</code>.<br />
|
|
58
|
+
<b>Open</b> goes to the machine's own UI. The first time from a device, sign in there with that machine's remote-access token (<code>agentmgr remote status</code> on it), not the hub's.
|
|
59
|
+
</p>
|
|
60
|
+
</main>
|
|
61
|
+
|
|
62
|
+
<dialog id="invite-dialog">
|
|
63
|
+
<form method="dialog" class="dialog">
|
|
64
|
+
<h2>Add a machine</h2>
|
|
65
|
+
<p class="muted">On the Mac to add (with Agent Manager installed), run:</p>
|
|
66
|
+
<div class="cmd"><code id="invite-cmd">…</code><button type="button" id="copy-btn" class="ghost">Copy</button></div>
|
|
67
|
+
<p class="muted small" id="invite-expiry"></p>
|
|
68
|
+
<p class="error small" id="invite-warning" hidden></p>
|
|
69
|
+
<p class="muted small">
|
|
70
|
+
A new machine shares <b>view</b> only: its agents and what they're waiting on, without code, transcripts or terminals.
|
|
71
|
+
Each machine decides this itself, in the <code>hub</code> section of its <code>~/.agentmanager/config.json</code>.
|
|
72
|
+
</p>
|
|
73
|
+
<div class="dialog-actions"><button class="primary" value="close">Done</button></div>
|
|
74
|
+
</form>
|
|
75
|
+
</dialog>
|
|
76
|
+
|
|
77
|
+
<dialog id="new-dialog">
|
|
78
|
+
<form class="dialog" id="new-form">
|
|
79
|
+
<h2>New agent</h2>
|
|
80
|
+
<label>Machine <select id="new-machine" required></select></label>
|
|
81
|
+
<label>Project <select id="new-project" required></select></label>
|
|
82
|
+
<label>Intent <textarea id="new-intent" rows="4" required placeholder="What should the agent do?"></textarea></label>
|
|
83
|
+
<div class="row-fields">
|
|
84
|
+
<label>Priority
|
|
85
|
+
<select id="new-priority"><option value="0">P0</option><option value="1">P1</option><option value="2" selected>P2</option><option value="3">P3</option></select>
|
|
86
|
+
</label>
|
|
87
|
+
<label id="new-engine-field" hidden>Engine
|
|
88
|
+
<select id="new-engine"></select>
|
|
89
|
+
</label>
|
|
90
|
+
<label>Permissions
|
|
91
|
+
<select id="new-mode"><option value="">Machine default</option><option value="default">Ask</option><option value="acceptEdits">Accept edits</option><option value="plan">Plan first</option></select>
|
|
92
|
+
</label>
|
|
93
|
+
</div>
|
|
94
|
+
<p class="muted small">Only projects registered on that machine and shared with the hub are listed. The hub can't start agents that skip permission prompts.</p>
|
|
95
|
+
<p class="error" id="new-error" hidden></p>
|
|
96
|
+
<div class="dialog-actions"><button type="button" class="ghost" id="new-cancel">Cancel</button><button class="primary">Start</button></div>
|
|
97
|
+
</form>
|
|
98
|
+
</dialog>
|
|
99
|
+
|
|
100
|
+
<dialog id="queue-dialog">
|
|
101
|
+
<form class="dialog" id="queue-form">
|
|
102
|
+
<h2>Add to the backlog</h2>
|
|
103
|
+
<label>Project <select id="queue-project" required></select></label>
|
|
104
|
+
<label>Work <textarea id="queue-titles" rows="5" required placeholder="One item per line: each becomes one agent"></textarea></label>
|
|
105
|
+
<label>Needs labels <input id="queue-labels" placeholder="Optional, comma separated: gpu, machine:mac-mini" /></label>
|
|
106
|
+
<div class="row-fields">
|
|
107
|
+
<label>Priority
|
|
108
|
+
<select id="queue-priority"><option value="0">P0</option><option value="1">P1</option><option value="2" selected>P2</option><option value="3">P3</option></select>
|
|
109
|
+
</label>
|
|
110
|
+
<label id="queue-engine-field" hidden>Engine
|
|
111
|
+
<select id="queue-engine"></select>
|
|
112
|
+
</label>
|
|
113
|
+
<label>Permissions
|
|
114
|
+
<select id="queue-mode"><option value="">Machine default</option><option value="default">Ask</option><option value="acceptEdits">Accept edits</option><option value="plan">Plan first</option></select>
|
|
115
|
+
</label>
|
|
116
|
+
</div>
|
|
117
|
+
<p class="muted small">Each item starts on a machine that shares the project, has the labels and has room, preferring one that already has the repo cloned.</p>
|
|
118
|
+
<p class="error" id="queue-error" hidden></p>
|
|
119
|
+
<div class="dialog-actions"><button type="button" class="ghost" id="queue-cancel">Cancel</button><button class="primary">Queue</button></div>
|
|
120
|
+
</form>
|
|
121
|
+
</dialog>
|
|
122
|
+
|
|
123
|
+
<div id="term-overlay" class="term-overlay" hidden>
|
|
124
|
+
<div class="term-head">
|
|
125
|
+
<b id="term-title"></b>
|
|
126
|
+
<span id="term-status" class="term-status"></span>
|
|
127
|
+
<button id="term-close" class="ghost" title="Close (⌘.)">Close</button>
|
|
128
|
+
</div>
|
|
129
|
+
<div id="term" class="term"></div>
|
|
130
|
+
<div class="term-keys" id="term-keys" aria-label="Terminal keys">
|
|
131
|
+
<button data-key="esc">Esc</button><button data-key="tab">Tab</button><button data-key="stab" title="Shift+Tab: cycle permission mode">⇧Tab</button>
|
|
132
|
+
<button data-key="up">↑</button><button data-key="down">↓</button><button data-key="left">←</button><button data-key="right">→</button>
|
|
133
|
+
<button data-key="enter">⏎</button><button data-key="ctrlc">^C</button>
|
|
134
|
+
<button data-key="1">1</button><button data-key="2">2</button><button data-key="3">3</button><button data-key="4">4</button>
|
|
135
|
+
</div>
|
|
136
|
+
<form class="term-compose" id="term-compose" autocomplete="off">
|
|
137
|
+
<textarea id="term-text" rows="1" placeholder="Reply to the agent…" enterkeyhint="send"></textarea>
|
|
138
|
+
<button class="primary">Send</button>
|
|
139
|
+
</form>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div id="toast" class="toast" hidden></div>
|
|
143
|
+
|
|
144
|
+
<script src="/hub.js" type="module"></script>
|
|
145
|
+
</body>
|
|
146
|
+
</html>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// A machine's agent terminal, relayed by the hub: browser <-> hub <-> machine.
|
|
2
|
+
import { Terminal } from '/vendor/xterm/lib/xterm.mjs';
|
|
3
|
+
import { FitAddon } from '/vendor/xterm-addon-fit/lib/addon-fit.mjs';
|
|
4
|
+
import { touchScroll } from '/touchscroll.js';
|
|
5
|
+
|
|
6
|
+
const $ = (sel) => document.querySelector(sel);
|
|
7
|
+
const KEYS = {
|
|
8
|
+
esc: '\x1b', tab: '\t', stab: '\x1b[Z', up: '\x1b[A', down: '\x1b[B', right: '\x1b[C', left: '\x1b[D',
|
|
9
|
+
enter: '\r', ctrlc: '\x03', 1: '1', 2: '2', 3: '3', 4: '4',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let xterm = null;
|
|
13
|
+
let fit = null;
|
|
14
|
+
let ws = null;
|
|
15
|
+
let target = null; // { runnerId, runnerName, agentId, agentName, kind }
|
|
16
|
+
let retry = null;
|
|
17
|
+
|
|
18
|
+
function ensure() {
|
|
19
|
+
if (xterm) return;
|
|
20
|
+
xterm = new Terminal({
|
|
21
|
+
cursorBlink: true,
|
|
22
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
23
|
+
fontSize: matchMedia('(max-width: 640px)').matches ? 11 : 13,
|
|
24
|
+
scrollback: 10000,
|
|
25
|
+
theme: { background: '#0f1117' },
|
|
26
|
+
allowProposedApi: true,
|
|
27
|
+
});
|
|
28
|
+
fit = new FitAddon();
|
|
29
|
+
xterm.loadAddon(fit);
|
|
30
|
+
xterm.open($('#term'));
|
|
31
|
+
xterm.onData((d) => send({ t: 'in', d }));
|
|
32
|
+
xterm.onResize(({ cols, rows }) => send({ t: 'resize', cols, rows }));
|
|
33
|
+
new ResizeObserver(() => refit()).observe($('#term'));
|
|
34
|
+
touchScroll($('#term'), xterm);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const send = (m) => ws?.readyState === WebSocket.OPEN && ws.send(JSON.stringify(m));
|
|
38
|
+
function refit() {
|
|
39
|
+
if (!target) return;
|
|
40
|
+
try {
|
|
41
|
+
fit.fit();
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
function status(text, bad = false) {
|
|
45
|
+
$('#term-status').textContent = text;
|
|
46
|
+
$('#term-status').classList.toggle('bad', bad);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function openTerminal(t) {
|
|
50
|
+
closeTerminal();
|
|
51
|
+
target = t;
|
|
52
|
+
$('#term-title').textContent = `${t.runnerName}/${t.agentName}${t.kind === 'claude' ? '' : ` · ${t.kind}`}`;
|
|
53
|
+
$('#term-overlay').hidden = false;
|
|
54
|
+
document.body.classList.add('term-open');
|
|
55
|
+
ensure();
|
|
56
|
+
xterm.reset();
|
|
57
|
+
refit();
|
|
58
|
+
connect();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function connect() {
|
|
62
|
+
clearTimeout(retry);
|
|
63
|
+
const t = target;
|
|
64
|
+
status('connecting…');
|
|
65
|
+
const q = new URLSearchParams({ runner: t.runnerId, agent: t.agentId, kind: t.kind, cols: xterm.cols, rows: xterm.rows });
|
|
66
|
+
const sock = new WebSocket(`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws/term?${q}`);
|
|
67
|
+
ws = sock;
|
|
68
|
+
let fatal = false;
|
|
69
|
+
sock.onmessage = (e) => {
|
|
70
|
+
const m = JSON.parse(e.data);
|
|
71
|
+
if (m.t === 'term-hello') {
|
|
72
|
+
xterm.reset();
|
|
73
|
+
xterm.write(m.backlog ?? '');
|
|
74
|
+
status(m.running ? 'live' : 'not running: Start it from the hub, or type to see its last screen', !m.running);
|
|
75
|
+
send({ t: 'resize', cols: xterm.cols, rows: xterm.rows });
|
|
76
|
+
if (!matchMedia('(pointer: coarse)').matches) xterm.focus();
|
|
77
|
+
} else if (m.t === 'term-out') {
|
|
78
|
+
xterm.write(m.d);
|
|
79
|
+
} else if (m.t === 'term-msg') {
|
|
80
|
+
if (m.msg?.t === 'exit') {
|
|
81
|
+
if (target?.kind === 'claude') xterm.write(`\r\n\x1b[2m[claude exited with code ${m.msg.code}; session kept]\x1b[0m\r\n`);
|
|
82
|
+
status(target?.kind === 'claude' ? 'not running: Start it from the hub' : 'shell exited', true);
|
|
83
|
+
}
|
|
84
|
+
} else if (m.t === 'term-error') {
|
|
85
|
+
fatal = true;
|
|
86
|
+
status(m.error, true);
|
|
87
|
+
} else if (m.t === 'term-end') {
|
|
88
|
+
fatal = true;
|
|
89
|
+
status('terminal closed', true);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
sock.onclose = (e) => {
|
|
93
|
+
if (ws !== sock || target !== t) return;
|
|
94
|
+
ws = null;
|
|
95
|
+
if (fatal || e.code === 1008) return;
|
|
96
|
+
// Network blip, or this browser fell behind: reconnect for a fresh screen.
|
|
97
|
+
status('reconnecting…', true);
|
|
98
|
+
retry = setTimeout(connect, e.code === 1013 ? 200 : 2000);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Keep the status line in step with the agent as the fleet view updates. */
|
|
103
|
+
export function syncTerminal(fleet) {
|
|
104
|
+
if (!target || !ws || target.kind !== 'claude') return;
|
|
105
|
+
const a = fleet.agents.find((x) => x.runnerId === target.runnerId && x.id === target.agentId);
|
|
106
|
+
if (!a) return;
|
|
107
|
+
if (!a.online) status('machine offline', true);
|
|
108
|
+
else if (a.status === 'running') status(a.attention ? 'live · needs input' : 'live');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const terminalTarget = () => target;
|
|
112
|
+
|
|
113
|
+
export function closeTerminal() {
|
|
114
|
+
clearTimeout(retry);
|
|
115
|
+
target = null;
|
|
116
|
+
const sock = ws;
|
|
117
|
+
ws = null;
|
|
118
|
+
sock?.close();
|
|
119
|
+
$('#term-overlay').hidden = true;
|
|
120
|
+
document.body.classList.remove('term-open');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
$('#term-close').addEventListener('click', closeTerminal);
|
|
124
|
+
document.addEventListener('keydown', (e) => {
|
|
125
|
+
// Esc belongs to Claude while typing in the terminal; close with the button or Cmd+.
|
|
126
|
+
if (target && e.key === '.' && (e.metaKey || e.ctrlKey)) closeTerminal();
|
|
127
|
+
});
|
|
128
|
+
$('#term-keys').addEventListener('click', (e) => {
|
|
129
|
+
const key = e.target.closest('[data-key]')?.dataset.key;
|
|
130
|
+
if (key) send({ t: 'in', d: KEYS[key] });
|
|
131
|
+
});
|
|
132
|
+
$('#term-keys').addEventListener('mousedown', (e) => e.preventDefault());
|
|
133
|
+
$('#term-compose').addEventListener('submit', (e) => {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
const box = $('#term-text');
|
|
136
|
+
if (!box.value.trim()) return;
|
|
137
|
+
send({ t: 'in', d: box.value });
|
|
138
|
+
// Enter separately, so Claude reads the text as typed input and then submits it.
|
|
139
|
+
setTimeout(() => send({ t: 'in', d: '\r' }), 150);
|
|
140
|
+
box.value = '';
|
|
141
|
+
});
|
|
142
|
+
$('#term-text').addEventListener('keydown', (e) => {
|
|
143
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
$('#term-compose').requestSubmit();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
|
+
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
7
|
+
<title>Agent Manager</title>
|
|
8
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22%3E%3Cpath d=%22M8 1l7 7-7 7-7-7z%22 fill=%22%233b5bdb%22/%3E%3C/svg%3E" />
|
|
9
|
+
<link rel="stylesheet" href="/vendor/xterm/css/xterm.css" />
|
|
10
|
+
<link rel="stylesheet" href="/style.css" />
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<header class="topbar">
|
|
14
|
+
<div class="brand">
|
|
15
|
+
<span class="logo" aria-hidden="true">◆</span>
|
|
16
|
+
<h1>Agent Manager</h1>
|
|
17
|
+
<span id="conn" class="conn" title="Daemon connection">connecting…</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="top-actions">
|
|
20
|
+
<input id="filter" type="search" placeholder="Filter agents" aria-label="Filter agents" />
|
|
21
|
+
<button id="alerts-btn" class="local-only" title="Slack alerts when an agent needs you">Alerts</button>
|
|
22
|
+
<button id="remote-btn" class="local-only" title="Open Agent Manager from your phone">Remote access</button>
|
|
23
|
+
<button id="ask-btn" title="A general Claude session with no repo or folder">Ask Claude</button>
|
|
24
|
+
<button id="new-btn" class="primary">+ New agents</button>
|
|
25
|
+
</div>
|
|
26
|
+
</header>
|
|
27
|
+
|
|
28
|
+
<main class="layout">
|
|
29
|
+
<aside class="sidebar">
|
|
30
|
+
<div class="side-tabs" role="tablist">
|
|
31
|
+
<button class="side-tab active" data-tab="agents" role="tab">Agents</button>
|
|
32
|
+
<button class="side-tab" data-tab="inbox" role="tab" title="Everything waiting on you: prompts, questions, stuck agents, overlaps">Inbox <span id="inbox-count" class="badge"></span></button>
|
|
33
|
+
<button class="side-tab" data-tab="backlog" role="tab">Backlog <span id="backlog-count" class="badge"></span></button>
|
|
34
|
+
</div>
|
|
35
|
+
<div id="agents-view">
|
|
36
|
+
<div class="counts" id="counts"></div>
|
|
37
|
+
<ul id="agent-list" class="agent-list" aria-label="Agents"></ul>
|
|
38
|
+
</div>
|
|
39
|
+
<div id="backlog-view" hidden></div>
|
|
40
|
+
<div id="inbox-view" hidden></div>
|
|
41
|
+
<div id="empty" class="empty" hidden>
|
|
42
|
+
<p>No agents yet.</p>
|
|
43
|
+
<p>Add one or more intents and a repo or folder to get started.</p>
|
|
44
|
+
</div>
|
|
45
|
+
</aside>
|
|
46
|
+
|
|
47
|
+
<section class="detail" id="detail">
|
|
48
|
+
<div class="placeholder" id="placeholder">Select an agent to see its session, folder, repo, PRs and bugs.</div>
|
|
49
|
+
<div id="detail-body" hidden></div>
|
|
50
|
+
<div class="term-wrap" id="term-wrap" hidden>
|
|
51
|
+
<div class="term-bar">
|
|
52
|
+
<div class="term-tabs" role="tablist">
|
|
53
|
+
<button class="term-tab" data-kind="claude" role="tab">Claude</button>
|
|
54
|
+
<span id="shell-tabs" class="term-tabs"></span>
|
|
55
|
+
<button class="term-tab term-add" id="shell-new" title="Open another shell in the agent's folder">+ Shell</button>
|
|
56
|
+
<span id="browser-tabs" class="term-tabs"></span>
|
|
57
|
+
<button class="term-tab term-add" id="browser-new" title="View a page as this machine sees it (a dev server on localhost, or any address), from any device">+ Browser</button>
|
|
58
|
+
</div>
|
|
59
|
+
<span id="term-title"></span>
|
|
60
|
+
<span class="spacer"></span>
|
|
61
|
+
<button id="term-attach" class="ghost small" title="Attach files or images (you can also drop or paste them into the terminal)">📎 Attach</button>
|
|
62
|
+
<input type="file" id="term-file" multiple hidden>
|
|
63
|
+
<button id="term-fit" class="ghost small" title="Refit the terminal to the panel">Fit</button>
|
|
64
|
+
<button id="term-close" class="ghost small" title="Hide the terminal (the agent keeps running)">Hide terminal</button>
|
|
65
|
+
</div>
|
|
66
|
+
<div id="signin-bar" class="signin-bar" hidden></div>
|
|
67
|
+
<div id="terminal" class="terminal"></div>
|
|
68
|
+
<div id="browser" class="browser">
|
|
69
|
+
<form id="browser-bar" class="browser-bar" autocomplete="off">
|
|
70
|
+
<button type="button" id="browser-back" class="ghost small live-only" title="Back">◀</button>
|
|
71
|
+
<button type="button" id="browser-fwd" class="ghost small live-only" title="Forward">▶</button>
|
|
72
|
+
<button type="button" id="browser-reload" class="ghost small" title="Reload the page">⟳</button>
|
|
73
|
+
<input id="browser-url" placeholder="localhost:5173, a port, or any address" spellcheck="false" autocapitalize="off" autocorrect="off" enterkeyhint="go">
|
|
74
|
+
<button class="small">Go</button>
|
|
75
|
+
<button type="button" id="browser-sound" class="ghost small live-only" title="Hear the page: sound from videos and audio playing on that machine">🔇</button>
|
|
76
|
+
<button type="button" id="browser-keys" class="ghost small live-only touch-only" title="Show the keyboard to type into the page">⌨</button>
|
|
77
|
+
<button type="button" id="browser-mode" class="ghost small" hidden></button>
|
|
78
|
+
<a id="browser-pop" class="ghost small button" target="_blank" rel="noopener" title="Open in a new browser tab on this device" hidden>↗</a>
|
|
79
|
+
</form>
|
|
80
|
+
<div id="browser-hint" class="browser-hint" hidden>Blank? Many sites refuse to show inside another page. Open it with ↗ instead.</div>
|
|
81
|
+
<div id="browser-cert" class="browser-start" hidden>
|
|
82
|
+
<p><b>One more step on this device.</b> Each browser tab goes through its own port, and this device hasn't accepted Agent Manager's certificate for this one yet.</p>
|
|
83
|
+
<p>Open it, accept the warning the same way you did for Agent Manager (Safari: Show Details → visit this website), then come back here. You only do this once per site; it keeps its port.</p>
|
|
84
|
+
<div><a id="browser-cert-open" class="button primary small" target="_blank" rel="noopener">Open and accept</a> <button type="button" id="browser-cert-retry" class="small">Done, load it</button></div>
|
|
85
|
+
</div>
|
|
86
|
+
<div id="browser-start" class="browser-start" hidden>
|
|
87
|
+
<p>Open a page as this machine sees it: the agent's dev server, a site on its network or VPN, or any address. It works from your phone or laptop too: Agent Manager relays it over its own secure connection, for signed-in devices only.</p>
|
|
88
|
+
<div id="browser-suggest"></div>
|
|
89
|
+
</div>
|
|
90
|
+
<div id="browser-frames" class="browser-frames"></div>
|
|
91
|
+
<div id="browser-live" class="browser-live" hidden>
|
|
92
|
+
<img id="browser-live-img" alt="" draggable="false">
|
|
93
|
+
<textarea id="browser-live-keys" class="browser-live-keys" autocapitalize="off" autocomplete="off" autocorrect="off" spellcheck="false" aria-label="Type into the page"></textarea>
|
|
94
|
+
<div id="browser-live-msg" class="browser-live-msg" hidden></div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
<div id="term-drop" class="term-drop" hidden>Drop files to attach</div>
|
|
98
|
+
<div class="keybar" id="keybar" aria-label="Terminal keys">
|
|
99
|
+
<button data-key="esc">Esc</button>
|
|
100
|
+
<button data-key="tab">Tab</button>
|
|
101
|
+
<button data-key="stab" title="Shift+Tab: cycle permission mode">⇧Tab</button>
|
|
102
|
+
<button data-key="up">↑</button>
|
|
103
|
+
<button data-key="down">↓</button>
|
|
104
|
+
<button data-key="left">←</button>
|
|
105
|
+
<button data-key="right">→</button>
|
|
106
|
+
<button data-key="enter">⏎</button>
|
|
107
|
+
<button data-key="ctrlc">^C</button>
|
|
108
|
+
<button data-key="1">1</button>
|
|
109
|
+
<button data-key="2">2</button>
|
|
110
|
+
<button data-key="3">3</button>
|
|
111
|
+
<button data-key="4">4</button>
|
|
112
|
+
</div>
|
|
113
|
+
<form class="compose" id="compose" autocomplete="off">
|
|
114
|
+
<textarea id="compose-text" rows="1" placeholder="Reply to the agent…" enterkeyhint="send"></textarea>
|
|
115
|
+
<button class="primary">Send</button>
|
|
116
|
+
</form>
|
|
117
|
+
<div id="term-overlay" class="term-overlay" hidden>
|
|
118
|
+
<p id="term-overlay-msg">This agent's session is closed.</p>
|
|
119
|
+
<button id="term-resume" class="primary">Resume session</button>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</section>
|
|
123
|
+
</main>
|
|
124
|
+
|
|
125
|
+
<dialog id="new-dialog">
|
|
126
|
+
<form id="new-form" method="dialog">
|
|
127
|
+
<h2>New agents</h2>
|
|
128
|
+
<label>
|
|
129
|
+
Intents <span class="hint">one per line; each gets its own agent</span>
|
|
130
|
+
<textarea name="intents" rows="6" required placeholder="Add a dark mode toggle that remembers the user's choice Make search results show up as you type Find out why the dashboard takes 5 seconds to load and speed it up Add a one-click CSV export to the reports page"></textarea>
|
|
131
|
+
</label>
|
|
132
|
+
<div class="row">
|
|
133
|
+
<label>
|
|
134
|
+
Git repository URL
|
|
135
|
+
<input name="repo" placeholder="https://github.com/org/repo.git" />
|
|
136
|
+
</label>
|
|
137
|
+
<label>
|
|
138
|
+
or working folder
|
|
139
|
+
<input name="dir" placeholder="~/projects/my-repo" />
|
|
140
|
+
</label>
|
|
141
|
+
</div>
|
|
142
|
+
<div class="row">
|
|
143
|
+
<label>
|
|
144
|
+
Permission mode
|
|
145
|
+
<select name="permissionMode">
|
|
146
|
+
<option value="">Default</option>
|
|
147
|
+
<option value="acceptEdits">Accept edits</option>
|
|
148
|
+
<option value="plan">Plan</option>
|
|
149
|
+
<option value="auto">Auto</option>
|
|
150
|
+
<option value="bypassPermissions">Bypass permissions</option>
|
|
151
|
+
</select>
|
|
152
|
+
</label>
|
|
153
|
+
<label>
|
|
154
|
+
Model <span class="hint">optional</span>
|
|
155
|
+
<input name="model" placeholder="e.g. opus" />
|
|
156
|
+
</label>
|
|
157
|
+
<label id="engine-field" hidden>
|
|
158
|
+
Engine
|
|
159
|
+
<select name="engine"><option value="">Claude Code</option></select>
|
|
160
|
+
</label>
|
|
161
|
+
</div>
|
|
162
|
+
<label>
|
|
163
|
+
Project <span class="hint">optional; a shortcut like <code>cc</code> fills in its repo, or name a new project</span>
|
|
164
|
+
<input name="project" placeholder="shortcut or name (default: from the repo)" list="project-options" autocomplete="off" />
|
|
165
|
+
<datalist id="project-options"></datalist>
|
|
166
|
+
</label>
|
|
167
|
+
<label>
|
|
168
|
+
Base branch <span class="hint">optional; each agent branches from this. Default: the repo's default branch, freshly fetched</span>
|
|
169
|
+
<input name="base" placeholder="main" />
|
|
170
|
+
</label>
|
|
171
|
+
<label class="check">
|
|
172
|
+
<input type="checkbox" name="worktree" checked />
|
|
173
|
+
Give each agent its own git worktree and branch <span class="hint">(for a git folder; always on when cloning a repo URL)</span>
|
|
174
|
+
</label>
|
|
175
|
+
<label class="check">
|
|
176
|
+
<input type="checkbox" name="plan" checked />
|
|
177
|
+
Plan with Claude while the repo clones <span class="hint">(repo URLs only; the session moves into the repo when it's ready)</span>
|
|
178
|
+
</label>
|
|
179
|
+
<label class="check">
|
|
180
|
+
<input type="checkbox" name="planReview" />
|
|
181
|
+
Review the implementation plan first <span class="hint">(the agent writes a plan and waits for your approval before changing anything)</span>
|
|
182
|
+
</label>
|
|
183
|
+
<div class="row when">
|
|
184
|
+
<label class="check"><input type="radio" name="when" value="now" checked /> Start now</label>
|
|
185
|
+
<label class="check"><input type="radio" name="when" value="backlog" /> Queue in backlog</label>
|
|
186
|
+
<label class="check" title="Several agents try the same intent; compare and keep the best"><input type="radio" name="when" value="race" /> Race variants</label>
|
|
187
|
+
<label class="check">Priority
|
|
188
|
+
<select name="priority" aria-label="Priority">
|
|
189
|
+
<option value="0">P0 urgent</option>
|
|
190
|
+
<option value="1">P1 high</option>
|
|
191
|
+
<option value="2" selected>P2 normal</option>
|
|
192
|
+
<option value="3">P3 low</option>
|
|
193
|
+
</select>
|
|
194
|
+
</label>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="race-only" hidden>
|
|
197
|
+
<label>
|
|
198
|
+
Variants <span class="hint">one per line: <code>model</code>, <code>model | approach</code>, or <code>| approach</code>. At least two.</span>
|
|
199
|
+
<textarea name="variants" rows="3" placeholder="opus sonnet | Keep the change as small as possible"></textarea>
|
|
200
|
+
</label>
|
|
201
|
+
<label>
|
|
202
|
+
Test command <span class="hint">optional; run in each attempt's worktree to compare</span>
|
|
203
|
+
<input name="testCommand" placeholder="npm test" />
|
|
204
|
+
</label>
|
|
205
|
+
</div>
|
|
206
|
+
<label class="check now-only">
|
|
207
|
+
<input type="checkbox" name="start" checked />
|
|
208
|
+
Launch immediately
|
|
209
|
+
</label>
|
|
210
|
+
<p id="new-error" class="error" hidden></p>
|
|
211
|
+
<div class="dialog-actions">
|
|
212
|
+
<button type="button" id="new-cancel" class="ghost">Cancel</button>
|
|
213
|
+
<button type="submit" class="primary">Create agents</button>
|
|
214
|
+
</div>
|
|
215
|
+
</form>
|
|
216
|
+
</dialog>
|
|
217
|
+
|
|
218
|
+
<dialog id="ask-dialog">
|
|
219
|
+
<form id="ask-form" method="dialog">
|
|
220
|
+
<h2>Ask Claude</h2>
|
|
221
|
+
<p class="hint">A general session with no repo or folder. It is tracked and resumable like any other agent.</p>
|
|
222
|
+
<label>
|
|
223
|
+
Question <span class="hint">optional; you can also just type in the terminal</span>
|
|
224
|
+
<textarea name="question" rows="4" placeholder="How do I tune GC for a JVM with a 32 GB heap?"></textarea>
|
|
225
|
+
</label>
|
|
226
|
+
<label>
|
|
227
|
+
Name <span class="hint">optional</span>
|
|
228
|
+
<input name="name" placeholder="jvm-gc-questions" />
|
|
229
|
+
</label>
|
|
230
|
+
<p id="ask-error" class="error" hidden></p>
|
|
231
|
+
<div class="dialog-actions">
|
|
232
|
+
<button type="button" id="ask-cancel" class="ghost">Cancel</button>
|
|
233
|
+
<button type="submit" class="primary">Start session</button>
|
|
234
|
+
</div>
|
|
235
|
+
</form>
|
|
236
|
+
</dialog>
|
|
237
|
+
|
|
238
|
+
<dialog id="login-dialog">
|
|
239
|
+
<form id="login-form" method="dialog">
|
|
240
|
+
<h2>Sign in</h2>
|
|
241
|
+
<p class="hint">Scan the QR code under <b>Remote access</b> on your computer, or paste the access token (or the whole sign-in link) here.</p>
|
|
242
|
+
<input name="token" placeholder="Access token" autocomplete="off" autocapitalize="off" spellcheck="false" required />
|
|
243
|
+
<p id="login-error" class="error" hidden></p>
|
|
244
|
+
<div class="dialog-actions"><button class="primary">Sign in</button></div>
|
|
245
|
+
</form>
|
|
246
|
+
</dialog>
|
|
247
|
+
|
|
248
|
+
<dialog id="doc-dialog" class="doc-dialog">
|
|
249
|
+
<div class="doc-head">
|
|
250
|
+
<h2 id="doc-title"></h2>
|
|
251
|
+
<span class="spacer"></span>
|
|
252
|
+
<button type="button" id="doc-bg" class="small" hidden title="Close this and keep writing; you'll be told when it's ready">Continue in background</button>
|
|
253
|
+
<button type="button" id="doc-regen" class="small" title="Write it again from the agent's latest work">Regenerate</button>
|
|
254
|
+
<button type="button" id="doc-copy" class="small" title="Copy as Markdown">Copy Markdown</button>
|
|
255
|
+
<button type="button" id="doc-download" class="small">Download .md</button>
|
|
256
|
+
<button type="button" id="doc-print" class="small primary" title="Open a print-ready page (Save as PDF)">Print / PDF</button>
|
|
257
|
+
<button type="button" id="doc-close" class="small ghost">Close</button>
|
|
258
|
+
</div>
|
|
259
|
+
<p id="doc-meta" class="hint"></p>
|
|
260
|
+
<article id="doc-body" class="md doc-body"></article>
|
|
261
|
+
<div id="doc-foot" class="doc-foot" hidden></div>
|
|
262
|
+
</dialog>
|
|
263
|
+
|
|
264
|
+
<dialog id="import-dialog" class="doc-dialog">
|
|
265
|
+
<div class="doc-head">
|
|
266
|
+
<h2>Continue a Claude session</h2>
|
|
267
|
+
<span class="spacer"></span>
|
|
268
|
+
<select id="import-days" class="small" title="How far back">
|
|
269
|
+
<option value="7">Last 7 days</option>
|
|
270
|
+
<option value="30" selected>Last 30 days</option>
|
|
271
|
+
<option value="90">Last 90 days</option>
|
|
272
|
+
</select>
|
|
273
|
+
<button type="button" id="import-close" class="small ghost">Close</button>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="import-body">
|
|
276
|
+
<p class="hint">Sessions you started with <code>claude</code> in a terminal on this machine. Each one you pick becomes an agent in the same folder that continues the same conversation, with the brief, inbox, PRs and usage tracked here.</p>
|
|
277
|
+
<input id="import-filter" placeholder="Filter by folder, title or what you asked" autocomplete="off">
|
|
278
|
+
<div id="import-list" class="import-list"></div>
|
|
279
|
+
</div>
|
|
280
|
+
<div class="import-foot">
|
|
281
|
+
<label class="check"><input type="checkbox" id="import-start" checked> Start now</label>
|
|
282
|
+
<span class="spacer"></span>
|
|
283
|
+
<button type="button" id="import-go" class="primary" disabled>Continue</button>
|
|
284
|
+
</div>
|
|
285
|
+
</dialog>
|
|
286
|
+
|
|
287
|
+
<dialog id="race-dialog" class="doc-dialog">
|
|
288
|
+
<div class="doc-head">
|
|
289
|
+
<h2 id="race-title">Race</h2>
|
|
290
|
+
<span class="spacer"></span>
|
|
291
|
+
<button type="button" id="race-delete" class="small ghost" title="Forget this race (its agents stay)">Forget race</button>
|
|
292
|
+
<button type="button" id="race-close" class="small ghost">Close</button>
|
|
293
|
+
</div>
|
|
294
|
+
<div id="race-body" class="race-body"></div>
|
|
295
|
+
</dialog>
|
|
296
|
+
|
|
297
|
+
<dialog id="alerts-dialog">
|
|
298
|
+
<form id="alerts-form" method="dialog" class="alerts-body">
|
|
299
|
+
<h2>Slack alerts</h2>
|
|
300
|
+
<p class="hint">Get a Slack message when an agent needs you. Use a <b>Workflow Builder</b> webhook (<code>…/triggers/…</code>, with one Text variable named <code>text</code>), an app's <b>incoming webhook</b> (<code>…/services/…</code>), or a <b>bot token</b> and channel. Stored only on this computer.</p>
|
|
301
|
+
<label>Webhook URL <input name="webhookUrl" type="password" autocomplete="off" placeholder="https://hooks.slack.com/services/…" /></label>
|
|
302
|
+
<div class="row">
|
|
303
|
+
<label>or bot token <input name="botToken" type="password" autocomplete="off" placeholder="xoxb-…" /></label>
|
|
304
|
+
<label>channel <input name="channel" placeholder="#agents" /></label>
|
|
305
|
+
</div>
|
|
306
|
+
<p class="hint" id="alerts-current"></p>
|
|
307
|
+
<fieldset>
|
|
308
|
+
<legend>Alert when an agent…</legend>
|
|
309
|
+
<label class="check"><input type="checkbox" name="ev_needsInput" /> needs input (a prompt or question is unanswered) after <input name="needsInputDelaySec" type="number" min="0" class="num" />s</label>
|
|
310
|
+
<label class="check"><input type="checkbox" name="ev_review" /> finishes a backlog item (ready for review)</label>
|
|
311
|
+
<label class="check"><input type="checkbox" name="ev_error" /> fails (setup or launch error)</label>
|
|
312
|
+
<label class="check"><input type="checkbox" name="ev_blocked" /> reports it is blocked</label>
|
|
313
|
+
<label class="check"><input type="checkbox" name="ev_stuck" /> seems stuck (same failure, undoing its edits, no progress)</label>
|
|
314
|
+
<label class="check"><input type="checkbox" name="ev_conflict" /> changes the same files as another agent</label>
|
|
315
|
+
<label class="check"><input type="checkbox" name="ev_question" /> asks a question in the inbox</label>
|
|
316
|
+
<label class="check"><input type="checkbox" name="ev_waiting" /> finished its turn and is idle after <input name="waitingDelaySec" type="number" min="0" class="num" />s</label>
|
|
317
|
+
<label class="check"><input type="checkbox" name="skipWhenWatching" /> not while I have its terminal open</label>
|
|
318
|
+
<label class="check"><input type="checkbox" name="includeIntent" /> include the intent text in the message</label>
|
|
319
|
+
<label class="check"><input type="checkbox" name="enabled" /> <b>Alerts on</b></label>
|
|
320
|
+
</fieldset>
|
|
321
|
+
<div id="alerts-recent" class="hint"></div>
|
|
322
|
+
<p id="alerts-error" class="error" hidden></p>
|
|
323
|
+
<div class="dialog-actions">
|
|
324
|
+
<button type="button" id="alerts-test">Send test</button>
|
|
325
|
+
<span class="spacer"></span>
|
|
326
|
+
<button type="button" id="alerts-close" class="ghost">Close</button>
|
|
327
|
+
<button class="primary">Save</button>
|
|
328
|
+
</div>
|
|
329
|
+
</form>
|
|
330
|
+
</dialog>
|
|
331
|
+
|
|
332
|
+
<dialog id="remote-dialog">
|
|
333
|
+
<div class="remote-body">
|
|
334
|
+
<h2>Remote access</h2>
|
|
335
|
+
<div id="remote-content">Loading…</div>
|
|
336
|
+
<div class="dialog-actions"><button type="button" id="remote-close" class="ghost">Close</button></div>
|
|
337
|
+
</div>
|
|
338
|
+
</dialog>
|
|
339
|
+
|
|
340
|
+
<script type="module" src="/app.js"></script>
|
|
341
|
+
</body>
|
|
342
|
+
</html>
|