ai-agentmanager 0.0.0-stage → 1.0.1

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 (60) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +768 -2
  5. package/SECURITY.md +44 -0
  6. package/bin/agentmgr-hub.js +16 -0
  7. package/bin/agentmgr.js +4 -0
  8. package/docs/architecture.md +67 -0
  9. package/docs/distributed.md +147 -0
  10. package/docs/engines.md +177 -0
  11. package/docs/images/agents.jpg +0 -0
  12. package/docs/images/conflict-radar.jpg +0 -0
  13. package/docs/plugins.md +81 -0
  14. package/package.json +58 -4
  15. package/public/app.js +3138 -0
  16. package/public/hub/hub.css +230 -0
  17. package/public/hub/hub.js +766 -0
  18. package/public/hub/index.html +146 -0
  19. package/public/hub/term.js +147 -0
  20. package/public/index.html +354 -0
  21. package/public/share.html +34 -0
  22. package/public/share.js +41 -0
  23. package/public/style.css +550 -0
  24. package/public/touchscroll.js +73 -0
  25. package/public/transport.js +78 -0
  26. package/scripts/build-release.sh +58 -0
  27. package/scripts/fix-pty.js +15 -0
  28. package/src/about.js +70 -0
  29. package/src/backlog.js +413 -0
  30. package/src/brief.js +512 -0
  31. package/src/cli.js +1471 -0
  32. package/src/client.js +49 -0
  33. package/src/config.js +109 -0
  34. package/src/conflicts.js +105 -0
  35. package/src/engines/antigravity.js +296 -0
  36. package/src/engines/claude.js +98 -0
  37. package/src/engines/codex.js +331 -0
  38. package/src/engines/index.js +37 -0
  39. package/src/fleet.js +176 -0
  40. package/src/git.js +340 -0
  41. package/src/hub.js +857 -0
  42. package/src/hubactions.js +107 -0
  43. package/src/hubbacklog.js +373 -0
  44. package/src/hublink.js +437 -0
  45. package/src/inbox.js +94 -0
  46. package/src/judge.js +278 -0
  47. package/src/livebrowser.js +637 -0
  48. package/src/manager.js +2554 -0
  49. package/src/notify.js +284 -0
  50. package/src/plugins.js +119 -0
  51. package/src/preview.js +334 -0
  52. package/src/races.js +192 -0
  53. package/src/remote.js +284 -0
  54. package/src/server.js +762 -0
  55. package/src/service.js +150 -0
  56. package/src/sessions.js +160 -0
  57. package/src/store.js +104 -0
  58. package/src/stuck.js +160 -0
  59. package/src/tracker.js +311 -0
  60. 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>AI 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>AI 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 AI 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
+ });