agent-rooms 0.1.8 → 0.2.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/dist/bridge.js +76 -0
- package/dist/bridge.js.map +1 -0
- package/dist/dashboard/readiness.js +27 -1
- package/dist/dashboard/readiness.js.map +1 -1
- package/dist/dashboard/server.js +85 -7
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/ui.js +110 -65
- package/dist/dashboard/ui.js.map +1 -1
- package/dist/hosts.js +110 -4
- package/dist/hosts.js.map +1 -1
- package/dist/listener.js +132 -3
- package/dist/listener.js.map +1 -1
- package/dist/socket.js +6 -3
- package/dist/socket.js.map +1 -1
- package/dist/spawn.js +111 -4
- package/dist/spawn.js.map +1 -1
- package/package.json +3 -3
- package/skill/agent-rooms/AGENTS.md +109 -40
- package/skill/agent-rooms/SKILL.md +166 -90
- package/skill/agent-rooms/references/etiquette.md +85 -49
- package/skill/agent-rooms/references/tools.md +627 -96
- package/skill/agent-rooms/references/troubleshooting.md +92 -50
package/dist/dashboard/ui.js
CHANGED
|
@@ -7,82 +7,108 @@ export function dashboardHtml() {
|
|
|
7
7
|
<title>Agent Rooms Control</title>
|
|
8
8
|
<style>
|
|
9
9
|
:root {
|
|
10
|
-
color-scheme:
|
|
11
|
-
--bg: #
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
--
|
|
10
|
+
color-scheme: dark;
|
|
11
|
+
--bg: #05070F;
|
|
12
|
+
--bg2: #0B1020;
|
|
13
|
+
--panel: rgba(255,255,255,.022);
|
|
14
|
+
--ink: #EAEFF8;
|
|
15
|
+
--muted: #8A93A6;
|
|
16
|
+
--faint: #5E6678;
|
|
17
|
+
--line: rgba(255,255,255,.07);
|
|
18
|
+
--line2: rgba(255,255,255,.12);
|
|
19
|
+
--cyan: #38BDF8;
|
|
20
|
+
--cyan2: #7DD3FC;
|
|
21
|
+
--purple: #A78BFA;
|
|
22
|
+
--green: #22C55E;
|
|
23
|
+
--amber: #FBBF24;
|
|
24
|
+
--red: #F87171;
|
|
25
|
+
--gold: #FACC15;
|
|
26
|
+
--blue: #38BDF8;
|
|
27
|
+
--shadow: 0 24px 60px -28px rgba(0,0,0,.85);
|
|
22
28
|
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
23
29
|
}
|
|
24
30
|
* { box-sizing: border-box; }
|
|
25
|
-
body {
|
|
31
|
+
body {
|
|
32
|
+
margin: 0; color: var(--ink);
|
|
33
|
+
background:
|
|
34
|
+
radial-gradient(1100px 620px at 78% -8%, rgba(56,189,248,.10), transparent 60%),
|
|
35
|
+
radial-gradient(900px 560px at -6% 8%, rgba(167,139,250,.09), transparent 55%),
|
|
36
|
+
var(--bg);
|
|
37
|
+
-webkit-font-smoothing: antialiased;
|
|
38
|
+
}
|
|
39
|
+
.mono, pre, .event, input, select { font-family: ui-monospace, "SF Mono", SFMono-Regular, Consolas, "Liberation Mono", monospace; }
|
|
26
40
|
button, input, select { font: inherit; }
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
.
|
|
35
|
-
.
|
|
36
|
-
|
|
37
|
-
.
|
|
38
|
-
.
|
|
39
|
-
.
|
|
40
|
-
.
|
|
41
|
+
::selection { background: rgba(56,189,248,.28); }
|
|
42
|
+
::-webkit-scrollbar { width: 9px; height: 9px; }
|
|
43
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,.1); border-radius: 99px; }
|
|
44
|
+
.shell { min-height: 100vh; display: grid; grid-template-columns: 248px 1fr; }
|
|
45
|
+
aside { border-right: 1px solid var(--line); padding: 22px 16px; background: linear-gradient(180deg, rgba(11,16,32,.55), rgba(5,7,15,.2)); position: sticky; top: 0; height: 100vh; }
|
|
46
|
+
main { padding: 28px 30px; max-width: 1200px; width: 100%; }
|
|
47
|
+
.brand { display: flex; align-items: center; gap: 9px; font-size: 14.5px; font-weight: 700; letter-spacing: -.01em; margin-bottom: 5px; color: #F8FAFC; }
|
|
48
|
+
.brand::before { content: ""; width: 19px; height: 19px; border-radius: 6px; flex: none; background: linear-gradient(140deg, var(--cyan), var(--purple)); box-shadow: 0 0 16px -3px rgba(56,189,248,.7); }
|
|
49
|
+
.sub { color: var(--faint); font-size: 12px; line-height: 1.5; margin-bottom: 22px; }
|
|
50
|
+
.nav { display: grid; gap: 3px; }
|
|
51
|
+
.nav button { display: flex; align-items: center; gap: 9px; border: 0; background: transparent; text-align: left; padding: 9px 11px; border-radius: 9px; color: var(--muted); cursor: pointer; font-size: 13.5px; transition: background .12s, color .12s; }
|
|
52
|
+
.nav button:hover { background: rgba(255,255,255,.04); color: var(--ink); }
|
|
53
|
+
.nav button.active { background: linear-gradient(90deg, rgba(56,189,248,.14), rgba(56,189,248,.04)); color: #F8FAFC; font-weight: 600; box-shadow: inset 2px 0 0 var(--cyan); }
|
|
54
|
+
.top { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 20px; }
|
|
55
|
+
h1 { font-size: 23px; margin: 0 0 5px; letter-spacing: -.02em; font-weight: 650; }
|
|
56
|
+
.meta { color: var(--faint); font-size: 12px; }
|
|
57
|
+
.banner { background: var(--panel); border: 1px solid var(--line2); backdrop-filter: blur(10px); border-radius: 13px; padding: 15px 17px; display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 18px; position: relative; overflow: hidden; }
|
|
58
|
+
.banner::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--cyan); }
|
|
59
|
+
.banner.ready::before { background: var(--green); }
|
|
60
|
+
.banner strong { display: block; margin-bottom: 3px; font-weight: 600; color: #F8FAFC; }
|
|
41
61
|
.grid { display: grid; gap: 16px; }
|
|
42
|
-
.two { grid-template-columns: minmax(0, 1.
|
|
43
|
-
.panel { background: var(--panel); border: 1px solid var(--line); border-radius:
|
|
44
|
-
.panel h2 { font-size:
|
|
45
|
-
.step { display: grid; grid-template-columns:
|
|
62
|
+
.two { grid-template-columns: minmax(0, 1.25fr) minmax(330px, .75fr); align-items: start; }
|
|
63
|
+
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--shadow); overflow: hidden; }
|
|
64
|
+
.panel h2 { font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--faint); padding: 15px 16px 9px; margin: 0; font-weight: 600; }
|
|
65
|
+
.step { display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; gap: 13px; padding: 14px 16px; border-top: 1px solid var(--line); align-items: center; }
|
|
46
66
|
.step:first-of-type { border-top: 0; }
|
|
47
|
-
.step.done {
|
|
48
|
-
.step.frontier { background:
|
|
49
|
-
.dot { width:
|
|
50
|
-
.ready .dot { background: var(--green); }
|
|
51
|
-
.action_needed .dot { background: var(--amber); }
|
|
52
|
-
.blocked .dot { background: var(--red); }
|
|
53
|
-
.working .dot { background: var(--
|
|
54
|
-
.attention .dot { background: var(--gold); }
|
|
55
|
-
.absent .dot { background:
|
|
56
|
-
@keyframes pulse { 0%,100% { opacity: .
|
|
57
|
-
.label { font-weight:
|
|
58
|
-
.hint { color: var(--muted); font-size:
|
|
59
|
-
.btn { border: 1px solid
|
|
60
|
-
.btn
|
|
61
|
-
.btn
|
|
62
|
-
.btn
|
|
63
|
-
.
|
|
64
|
-
.
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.pill
|
|
70
|
-
.
|
|
71
|
-
.
|
|
67
|
+
.step.done { opacity: .62; padding-top: 11px; padding-bottom: 11px; }
|
|
68
|
+
.step.frontier { background: linear-gradient(90deg, rgba(56,189,248,.07), transparent); box-shadow: inset 2px 0 0 var(--cyan); }
|
|
69
|
+
.dot { width: 11px; height: 11px; border-radius: 50%; margin-top: 3px; background: var(--faint); box-shadow: 0 0 0 4px rgba(255,255,255,.03); }
|
|
70
|
+
.ready .dot { background: var(--green); box-shadow: 0 0 10px -1px var(--green); }
|
|
71
|
+
.action_needed .dot { background: var(--amber); box-shadow: 0 0 10px -1px var(--amber); }
|
|
72
|
+
.blocked .dot { background: var(--red); box-shadow: 0 0 10px -1px var(--red); }
|
|
73
|
+
.working .dot { background: var(--cyan); animation: pulse 1.2s infinite; box-shadow: 0 0 10px -1px var(--cyan); }
|
|
74
|
+
.attention .dot { background: var(--gold); box-shadow: 0 0 10px -1px var(--gold); }
|
|
75
|
+
.absent .dot { background: var(--faint); }
|
|
76
|
+
@keyframes pulse { 0%,100% { opacity: .4; } 50% { opacity: 1; } }
|
|
77
|
+
.label { font-weight: 600; margin-bottom: 3px; color: #EAEFF8; font-size: 13.5px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
78
|
+
.hint { color: var(--muted); font-size: 12.5px; line-height: 1.5; }
|
|
79
|
+
.btn { border: 1px solid var(--cyan); background: var(--cyan); color: #05070F; border-radius: 9px; min-height: 35px; padding: 0 14px; cursor: pointer; white-space: nowrap; font-weight: 600; font-size: 12.5px; transition: filter .12s, transform .04s; box-shadow: 0 0 20px -8px rgba(56,189,248,.9); }
|
|
80
|
+
.btn:hover { filter: brightness(1.08); }
|
|
81
|
+
.btn:active { transform: translateY(1px); }
|
|
82
|
+
.btn.secondary { background: rgba(255,255,255,.04); color: var(--ink); border-color: var(--line2); box-shadow: none; }
|
|
83
|
+
.btn.secondary:hover { background: rgba(255,255,255,.08); }
|
|
84
|
+
.btn.gold { background: var(--gold); border-color: var(--gold); color: #1A1404; box-shadow: 0 0 20px -8px rgba(250,204,21,.9); }
|
|
85
|
+
.btn:disabled { opacity: .4; cursor: not-allowed; box-shadow: none; filter: none; }
|
|
86
|
+
.kv { display: grid; gap: 9px; padding: 13px 16px; }
|
|
87
|
+
.row { display: flex; align-items: center; justify-content: space-between; gap: 12px; border-top: 1px solid var(--line); padding: 12px 16px; font-size: 13px; }
|
|
88
|
+
.row:first-of-type { border-top: 0; }
|
|
89
|
+
.pill { border: 1px solid var(--line2); border-radius: 999px; padding: 3px 9px; font-size: 10.5px; letter-spacing: .04em; color: var(--muted); white-space: nowrap; font-weight: 600; }
|
|
90
|
+
.pill.ready { color: var(--green); border-color: rgba(34,197,94,.3); background: rgba(34,197,94,.1); }
|
|
91
|
+
.pill.warn { color: var(--amber); border-color: rgba(251,191,36,.3); background: rgba(251,191,36,.1); }
|
|
92
|
+
.pill.bad { color: var(--red); border-color: rgba(248,113,113,.3); background: rgba(248,113,113,.1); }
|
|
93
|
+
.activity { max-height: 520px; overflow: auto; padding: 4px 0; }
|
|
94
|
+
.event { padding: 9px 16px; border-top: 1px solid var(--line); font-size: 11.5px; color: #B6C0D4; line-height: 1.5; }
|
|
72
95
|
.event:first-child { border-top: 0; }
|
|
73
96
|
.form { display: grid; gap: 12px; padding: 16px; }
|
|
74
|
-
label { display: grid; gap:
|
|
75
|
-
input, select { width: 100%; border: 1px solid var(--
|
|
76
|
-
|
|
97
|
+
label { display: grid; gap: 6px; font-size: 12px; color: var(--muted); }
|
|
98
|
+
input, select { width: 100%; border: 1px solid var(--line2); border-radius: 9px; padding: 10px 12px; background: rgba(5,7,15,.6); color: var(--ink); font-size: 12.5px; }
|
|
99
|
+
input:focus, select:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 0 3px rgba(56,189,248,.14); }
|
|
100
|
+
input::placeholder { color: var(--faint); }
|
|
101
|
+
option { background: #0B1020; }
|
|
102
|
+
pre { margin: 0; padding: 14px 16px; overflow: auto; border-top: 1px solid var(--line); font-size: 11.5px; background: rgba(5,7,15,.5); color: #9FB0C8; line-height: 1.55; }
|
|
77
103
|
.hidden { display: none !important; }
|
|
78
|
-
.toast { position: fixed; right:
|
|
104
|
+
.toast { position: fixed; right: 20px; bottom: 20px; width: min(560px, calc(100vw - 40px)); background: rgba(11,16,32,.96); backdrop-filter: blur(14px); color: #EAEFF8; border: 1px solid var(--line2); border-radius: 12px; padding: 13px 15px; box-shadow: var(--shadow); font-size: 12.5px; white-space: pre-wrap; line-height: 1.5; }
|
|
79
105
|
@media (max-width: 850px) {
|
|
80
106
|
.shell { grid-template-columns: 1fr; }
|
|
81
|
-
aside { border-right: 0; border-bottom: 1px solid var(--line); }
|
|
107
|
+
aside { border-right: 0; border-bottom: 1px solid var(--line); position: static; height: auto; }
|
|
82
108
|
main { padding: 18px; }
|
|
83
109
|
.two { grid-template-columns: 1fr; }
|
|
84
110
|
.top, .banner, .row { flex-direction: column; align-items: stretch; }
|
|
85
|
-
.step { grid-template-columns:
|
|
111
|
+
.step { grid-template-columns: 18px minmax(0, 1fr); }
|
|
86
112
|
.step .btn { grid-column: 2; justify-self: start; }
|
|
87
113
|
}
|
|
88
114
|
</style>
|
|
@@ -145,6 +171,24 @@ export function dashboardHtml() {
|
|
|
145
171
|
}
|
|
146
172
|
}
|
|
147
173
|
|
|
174
|
+
// No-code pairing: once pairing has started (the approval page is open in the
|
|
175
|
+
// browser), poll quietly until the device is approved — the human just clicks
|
|
176
|
+
// Approve over there, never copies a code, never clicks "Check approval" here.
|
|
177
|
+
let pairPoller = null;
|
|
178
|
+
async function pollPairSilently() {
|
|
179
|
+
try {
|
|
180
|
+
const result = await api("/api/actions", { method: "POST", body: JSON.stringify({ action: "pair.poll" }) });
|
|
181
|
+
snapshot = result.snapshot;
|
|
182
|
+
if (!snapshot.pendingPair) showToast("Device paired. Connecting your listener…");
|
|
183
|
+
render();
|
|
184
|
+
} catch (err) { /* keep polling; transient */ }
|
|
185
|
+
}
|
|
186
|
+
function ensurePairPolling() {
|
|
187
|
+
const pending = snapshot && snapshot.pendingPair;
|
|
188
|
+
if (pending && !pairPoller) pairPoller = setInterval(pollPairSilently, 3000);
|
|
189
|
+
else if (!pending && pairPoller) { clearInterval(pairPoller); pairPoller = null; }
|
|
190
|
+
}
|
|
191
|
+
|
|
148
192
|
function statusText(status) {
|
|
149
193
|
return {
|
|
150
194
|
ready: "READY",
|
|
@@ -168,6 +212,7 @@ export function dashboardHtml() {
|
|
|
168
212
|
}
|
|
169
213
|
app.innerHTML = '<div class="shell"><aside>' + nav() + '</aside><main>' + page(r) + '</main></div>' + '<div id="toast" class="toast hidden"></div>';
|
|
170
214
|
bind();
|
|
215
|
+
ensurePairPolling();
|
|
171
216
|
}
|
|
172
217
|
|
|
173
218
|
function nav() {
|
|
@@ -192,7 +237,7 @@ export function dashboardHtml() {
|
|
|
192
237
|
function homePage(r) {
|
|
193
238
|
const next = r.nextAction;
|
|
194
239
|
const pair = snapshot.pendingPair
|
|
195
|
-
? '<div class="banner"><div><strong>
|
|
240
|
+
? '<div class="banner"><div><strong>Waiting for approval — code ' + esc(snapshot.pendingPair.user_code) + '</strong><div class="meta">We opened the approval page in your browser. Click <b>Approve & pair</b> there (you\'re already signed in) — this pairs automatically, no code to copy.</div></div><button class="btn secondary" data-action="' + encodeURIComponent(JSON.stringify({ id: "pair.poll", label: "Check now" })) + '">Check now</button></div>'
|
|
196
241
|
: "";
|
|
197
242
|
const banner = next
|
|
198
243
|
? '<div class="banner"><div><strong>Next: ' + esc(next.label) + '</strong><div class="meta">' + esc(next.hint) + '</div></div>' + actionButton(next.action) + '</div>'
|
|
@@ -209,7 +254,7 @@ export function dashboardHtml() {
|
|
|
209
254
|
return '<section class="panel"><h2>Local Truth</h2>' +
|
|
210
255
|
'<div class="row"><span>Device</span><span class="pill ' + (r.device.paired ? "ready" : "warn") + '">' + (r.device.paired ? "PAIRED" : "NOT PAIRED") + '</span></div>' +
|
|
211
256
|
'<div class="row"><span>Listener<br><span class="meta">' + (r.listener.running ? 'Serving ' + esc(r.listener.servingCount) + ' instance(s)' : 'Stopped') + '</span></span><span>' + listenerControls(r) + '</span></div>' +
|
|
212
|
-
'<div class="row"><span>Dashboard</span><span class="pill ' + (r.dashboard.updateAvailable ? "warn" : "ready") + '">' + (r.dashboard.updateAvailable ? "UPDATE AVAILABLE" : "UP TO DATE") + '</span></div>' +
|
|
257
|
+
'<div class="row"><span>Dashboard</span><span>' + (r.dashboard.updateAvailable ? actionButton({ id: "dashboard.update", label: "Update" }, "gold") + ' ' : '') + '<span class="pill ' + (r.dashboard.updateAvailable ? "warn" : "ready") + '">' + (r.dashboard.updateAvailable ? "UPDATE AVAILABLE" : "UP TO DATE") + '</span></span></div>' +
|
|
213
258
|
Object.values(r.hosts).map((h) => '<div class="row"><span>' + esc(h.label) + '<br><span class="meta">' + esc(h.version || h.detail) + '</span></span><span class="pill ' + (!h.detected ? "bad" : h.versionOk ? "ready" : "warn") + '">' + (!h.detected ? "MISSING" : h.versionOk ? "DETECTED" : "UPDATE") + '</span></div>').join("") +
|
|
214
259
|
'<h2>Diagnose</h2>' + r.diagnostics.map((d) => '<div class="row"><span><strong>' + esc(d.symptom) + '</strong><br><span class="meta">' + esc(d.cause) + '</span></span>' + actionButton(d.action, "secondary") + '</div>').join("") +
|
|
215
260
|
'</section>';
|
|
@@ -232,7 +277,7 @@ export function dashboardHtml() {
|
|
|
232
277
|
'<label>Agent plate<input name="agent" placeholder="BRNL-AGT-XXXXXXXX" required /></label>' +
|
|
233
278
|
'<label>Room id(s)<input name="rooms" placeholder="room_1, room_2" required /></label>' +
|
|
234
279
|
'<label>Workspace path<input name="workspace" placeholder="D:\\\\your-project" required /></label>' +
|
|
235
|
-
'<label>Host<select name="host"><option value="claude_code">Claude Code</option><option value="codex">Codex</option></select></label>' +
|
|
280
|
+
'<label>Host<select name="host"><option value="claude_code">Claude Code</option><option value="codex">Codex</option><option value="gemini">Gemini CLI</option><option value="cursor">Cursor CLI</option><option value="openclaw">OpenClaw</option></select></label>' +
|
|
236
281
|
'<button class="btn" type="submit">Save binding</button></form></section></div>';
|
|
237
282
|
}
|
|
238
283
|
|
package/dist/dashboard/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/dashboard/ui.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,GAAG,CAAA
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/dashboard/ui.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiWX,CAAC;AACT,CAAC"}
|
package/dist/hosts.js
CHANGED
|
@@ -101,11 +101,15 @@ export const codexAdapter = {
|
|
|
101
101
|
name: "codex",
|
|
102
102
|
bin: "codex",
|
|
103
103
|
allowedTools: ROOM_TOOLS,
|
|
104
|
-
//
|
|
105
|
-
|
|
104
|
+
// Codex adopted the Agent Skills standard; it loads skills from ~/.codex/skills
|
|
105
|
+
// (the $skill-installer convention). Installing the bundled folder here makes a
|
|
106
|
+
// woken Codex agent room-fluent and shortens its wake prompt.
|
|
107
|
+
skillDir: join(homedir(), ".codex", "skills"),
|
|
106
108
|
buildWake(spec) {
|
|
107
109
|
// `codex exec -` reads the prompt from stdin. Keep the multi-line wake
|
|
108
|
-
// prompt out of argv so Windows shell resolution cannot split it.
|
|
110
|
+
// prompt out of argv so Windows shell resolution cannot split it. The sandbox
|
|
111
|
+
// is Codex's own (workspace-write); we don't override the agent's isolation
|
|
112
|
+
// (spec 14: that's the vendor's job, not the broker's).
|
|
109
113
|
const base = ["exec", "--json", "--skip-git-repo-check", "--sandbox", "workspace-write"];
|
|
110
114
|
if (spec.sessionId) {
|
|
111
115
|
base.push("resume", spec.sessionId);
|
|
@@ -127,9 +131,111 @@ export const codexAdapter = {
|
|
|
127
131
|
return firstJsonField(stdout, ["session_id", "thread_id"]);
|
|
128
132
|
}
|
|
129
133
|
};
|
|
134
|
+
// ── Additional wakeable hosts (native spec §4.3–4.5) ────────────────────────
|
|
135
|
+
// These follow the documented per-host commands from Spec 1. Exact flag names
|
|
136
|
+
// drift across CLI versions (the spec flags this explicitly); where a behavior
|
|
137
|
+
// is version-dependent we take the safe floor — prompt via stdin (never argv,
|
|
138
|
+
// the mangling bug), fresh-spawn when headless resume isn't reliably emitted —
|
|
139
|
+
// and annotate what to re-verify against the user's installed build.
|
|
140
|
+
export const geminiAdapter = {
|
|
141
|
+
name: "gemini",
|
|
142
|
+
bin: "gemini",
|
|
143
|
+
allowedTools: ROOM_TOOLS,
|
|
144
|
+
// Gemini CLI adopted Agent Skills; loads from ~/.gemini/skills.
|
|
145
|
+
skillDir: join(homedir(), ".gemini", "skills"),
|
|
146
|
+
buildWake(spec) {
|
|
147
|
+
// Piping the prompt on stdin makes the run non-TTY → headless, and keeps the
|
|
148
|
+
// multi-line prompt out of argv. `--yolo` is Gemini's auto-approve (verify the
|
|
149
|
+
// flag name on the installed version — older builds used --approval-mode).
|
|
150
|
+
// Headless resume-by-id was historically not emitted in JSON output, so we
|
|
151
|
+
// take the universal floor: fresh spawn + injected thread context.
|
|
152
|
+
const args = ["--output-format", "json", "--yolo"];
|
|
153
|
+
return { command: this.bin, args, cwd: spec.workspace, stdin: spec.prompt, env: spec.env };
|
|
154
|
+
},
|
|
155
|
+
buildMcpAdd(apiBase) {
|
|
156
|
+
// Bearer parity with the wake path; some builds want the header in
|
|
157
|
+
// ~/.gemini/settings.json instead of a flag — verify on install.
|
|
158
|
+
return {
|
|
159
|
+
command: this.bin,
|
|
160
|
+
args: ["mcp", "add", "--transport", "http", "agent-rooms", `${apiBase}/mcp`, "--header", "Authorization: Bearer ${AGENT_ROOMS_TOKEN}"],
|
|
161
|
+
cwd: process.cwd()
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
buildMcpRemove() {
|
|
165
|
+
return { command: this.bin, args: ["mcp", "remove", "agent-rooms"], cwd: process.cwd() };
|
|
166
|
+
},
|
|
167
|
+
parseSessionId(stdout) {
|
|
168
|
+
return firstJsonField(stdout, ["session_id", "sessionId"]);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
export const cursorAdapter = {
|
|
172
|
+
name: "cursor",
|
|
173
|
+
bin: "cursor-agent",
|
|
174
|
+
allowedTools: ROOM_TOOLS,
|
|
175
|
+
skillDir: join(homedir(), ".cursor", "skills"),
|
|
176
|
+
buildWake(spec) {
|
|
177
|
+
// `-p` is print/headless. `--force` is REQUIRED: without it, `-p` hangs
|
|
178
|
+
// indefinitely when workspace trust is required (spec §4.4 gotcha). Prompt on
|
|
179
|
+
// stdin to dodge argv mangling.
|
|
180
|
+
const args = ["-p", "--output-format", "json", "--force"];
|
|
181
|
+
if (spec.sessionId) {
|
|
182
|
+
args.push("--resume", spec.sessionId);
|
|
183
|
+
}
|
|
184
|
+
return { command: this.bin, args, cwd: spec.workspace, stdin: spec.prompt, env: spec.env };
|
|
185
|
+
},
|
|
186
|
+
buildMcpAdd(apiBase) {
|
|
187
|
+
return {
|
|
188
|
+
command: this.bin,
|
|
189
|
+
args: ["mcp", "add", "agent-rooms", "--transport", "http", `${apiBase}/mcp`],
|
|
190
|
+
cwd: process.cwd()
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
buildMcpRemove() {
|
|
194
|
+
return { command: this.bin, args: ["mcp", "remove", "agent-rooms"], cwd: process.cwd() };
|
|
195
|
+
},
|
|
196
|
+
parseSessionId(stdout) {
|
|
197
|
+
return firstJsonField(stdout, ["session_id", "sessionId", "resume_session"]);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
export const openclawAdapter = {
|
|
201
|
+
name: "openclaw",
|
|
202
|
+
bin: "openclaw",
|
|
203
|
+
allowedTools: ROOM_TOOLS,
|
|
204
|
+
// OpenClaw skills come from ClawHub; on-disk dir ~/.openclaw/skills.
|
|
205
|
+
skillDir: join(homedir(), ".openclaw", "skills"),
|
|
206
|
+
buildWake(spec) {
|
|
207
|
+
// NOTE (spec §10.5, OPEN DECISION): OpenClaw is a gateway/meta-host. The clean
|
|
208
|
+
// integration dispatches a session via the running gateway (ACP/acpx with
|
|
209
|
+
// resumeSessionId), NOT a one-shot exec. This engine-level `run` form is the
|
|
210
|
+
// best-effort floor so a bound OpenClaw agent still wakes; revisit once the
|
|
211
|
+
// gateway-vs-engine decision lands. Gateway process must be alive.
|
|
212
|
+
const args = ["run", "--json"];
|
|
213
|
+
if (spec.sessionId) {
|
|
214
|
+
args.push("--resume", spec.sessionId);
|
|
215
|
+
}
|
|
216
|
+
args.push("-");
|
|
217
|
+
return { command: this.bin, args, cwd: spec.workspace, stdin: spec.prompt, env: spec.env };
|
|
218
|
+
},
|
|
219
|
+
buildMcpAdd(apiBase) {
|
|
220
|
+
return {
|
|
221
|
+
command: this.bin,
|
|
222
|
+
args: ["mcp", "add", "agent-rooms", "--transport", "streamable-http", "--url", `${apiBase}/mcp`],
|
|
223
|
+
cwd: process.cwd()
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
buildMcpRemove() {
|
|
227
|
+
return { command: this.bin, args: ["mcp", "remove", "agent-rooms"], cwd: process.cwd() };
|
|
228
|
+
},
|
|
229
|
+
parseSessionId(stdout) {
|
|
230
|
+
return firstJsonField(stdout, ["session_id", "sessionId", "resumeSessionId"]);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
130
233
|
const ADAPTERS = {
|
|
131
234
|
claude_code: claudeAdapter,
|
|
132
|
-
codex: codexAdapter
|
|
235
|
+
codex: codexAdapter,
|
|
236
|
+
gemini: geminiAdapter,
|
|
237
|
+
cursor: cursorAdapter,
|
|
238
|
+
openclaw: openclawAdapter
|
|
133
239
|
};
|
|
134
240
|
export function getAdapter(host) {
|
|
135
241
|
return ADAPTERS[host] ?? null;
|
package/dist/hosts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hosts.js","sourceRoot":"","sources":["../src/hosts.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"hosts.js","sourceRoot":"","sources":["../src/hosts.ts"],"names":[],"mappings":"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"}
|
package/dist/listener.js
CHANGED
|
@@ -5,10 +5,23 @@ import { platform } from "node:os";
|
|
|
5
5
|
import { loadConfig, saveConfig, sessionKey } from "./config.js";
|
|
6
6
|
import { listenerPost } from "./api.js";
|
|
7
7
|
import { getAdapter, isSkillInstalled } from "./hosts.js";
|
|
8
|
+
import { ensureClaudeMdBridge, writeRoomMd } from "./bridge.js";
|
|
8
9
|
import { runWake } from "./spawn.js";
|
|
9
10
|
import { DaemonSocket } from "./socket.js";
|
|
10
11
|
import { log } from "./log.js";
|
|
11
12
|
const HEARTBEAT_MS = 60_000;
|
|
13
|
+
// §5.5 — spawn-rate / concurrency caps. A wake storm (a malicious collaborator
|
|
14
|
+
// hammering @mentions, or a chatter loop) must not fork-bomb the machine or blow
|
|
15
|
+
// up model cost (#41). Caps sit far above any real workload; tune via env.
|
|
16
|
+
const MAX_CONCURRENT_SPAWNS = clampInt(process.env.AGENT_ROOMS_MAX_CONCURRENT_SPAWNS, 3, 1, 32);
|
|
17
|
+
const MAX_SPAWNS_PER_MIN = clampInt(process.env.AGENT_ROOMS_MAX_SPAWNS_PER_MIN, 20, 1, 600);
|
|
18
|
+
const RATE_WINDOW_MS = 60_000;
|
|
19
|
+
function clampInt(raw, fallback, min, max) {
|
|
20
|
+
const n = raw != null ? Number.parseInt(raw, 10) : NaN;
|
|
21
|
+
if (!Number.isFinite(n))
|
|
22
|
+
return fallback;
|
|
23
|
+
return Math.min(max, Math.max(min, n));
|
|
24
|
+
}
|
|
12
25
|
export class ListenerRuntime {
|
|
13
26
|
options;
|
|
14
27
|
config = null;
|
|
@@ -19,6 +32,15 @@ export class ListenerRuntime {
|
|
|
19
32
|
instanceIds = [];
|
|
20
33
|
instanceByAgentWorkspace = new Map();
|
|
21
34
|
bindingByInstanceId = new Map();
|
|
35
|
+
// §5.5 spawn caps: in-flight count + rolling window of recent spawn starts.
|
|
36
|
+
inFlightSpawns = 0;
|
|
37
|
+
recentSpawns = [];
|
|
38
|
+
// Spec3 §15.7 self-update hot-handoff: while draining we refuse NEW wakes but let
|
|
39
|
+
// in-flight runs finish — so a dashboard self-update never hard-kills a working agent.
|
|
40
|
+
draining = false;
|
|
41
|
+
// Running spawns keyed by agent target, so an owner stop can terminate the
|
|
42
|
+
// real process mid-run (consent model: "stop your agent, instantly").
|
|
43
|
+
running = new Map();
|
|
22
44
|
status = {
|
|
23
45
|
running: false,
|
|
24
46
|
cloudConnected: false,
|
|
@@ -91,6 +113,7 @@ export class ListenerRuntime {
|
|
|
91
113
|
platform: platform(),
|
|
92
114
|
agents,
|
|
93
115
|
onWake: (wake) => this.onWake(wake),
|
|
116
|
+
onStop: (target) => this.stopTarget(target),
|
|
94
117
|
onActivity: (activity) => this.onSocketActivity(activity)
|
|
95
118
|
});
|
|
96
119
|
this.socket.start();
|
|
@@ -138,7 +161,32 @@ export class ListenerRuntime {
|
|
|
138
161
|
}
|
|
139
162
|
return await this.start();
|
|
140
163
|
}
|
|
164
|
+
/** Spec3 §15.7: stop taking new wakes and wait for in-flight runs to finish so a
|
|
165
|
+
* self-update hands off cleanly instead of hard-killing a working agent. The
|
|
166
|
+
* refused wakes stay queued server-side and deliver after the new version
|
|
167
|
+
* reconnects. Returns whether it drained before the timeout. */
|
|
168
|
+
async drain(timeoutMs = 90_000) {
|
|
169
|
+
this.draining = true;
|
|
170
|
+
this.emit("info", "Draining: refusing new wakes, waiting for in-flight runs to finish.", "drain_start");
|
|
171
|
+
const deadline = Date.now() + timeoutMs;
|
|
172
|
+
while (this.inFlightSpawns > 0 && Date.now() < deadline) {
|
|
173
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
174
|
+
}
|
|
175
|
+
const remaining = this.inFlightSpawns;
|
|
176
|
+
this.emit(remaining === 0 ? "info" : "warn", remaining === 0 ? "Drained — no runs in flight." : `Drain timed out with ${remaining} run(s) still active.`, "drain_done");
|
|
177
|
+
return { drained: remaining === 0, remaining };
|
|
178
|
+
}
|
|
179
|
+
/** Abort a drain (update cancelled) and resume normal wake handling. */
|
|
180
|
+
resumeFromDrain() {
|
|
181
|
+
this.draining = false;
|
|
182
|
+
}
|
|
141
183
|
async onWake(wake) {
|
|
184
|
+
if (this.draining) {
|
|
185
|
+
// Draining for an update — don't start new work; the mention stays queued
|
|
186
|
+
// server-side and is delivered after the new version reconnects.
|
|
187
|
+
this.emit("info", `Draining for update; deferring wake for ${wake.target}.`, "wake_draining");
|
|
188
|
+
return { status: "error" };
|
|
189
|
+
}
|
|
142
190
|
const config = this.config ?? loadConfig();
|
|
143
191
|
if (config.pausedAgents.includes(wake.target)) {
|
|
144
192
|
const message = `${wake.target} is paused locally; ignoring wake.`;
|
|
@@ -160,18 +208,61 @@ export class ListenerRuntime {
|
|
|
160
208
|
this.emit("warn", message, "wake_no_adapter");
|
|
161
209
|
return { status: "error" };
|
|
162
210
|
}
|
|
211
|
+
// §5.5: refuse the spawn if we're over the concurrency or rate cap. This is
|
|
212
|
+
// the local brake on a wake storm — it returns an error rather than forking.
|
|
213
|
+
const throttle = this.spawnThrottleReason();
|
|
214
|
+
if (throttle) {
|
|
215
|
+
log.warn(throttle);
|
|
216
|
+
this.emit("warn", throttle, "wake_throttled");
|
|
217
|
+
return { status: "error" };
|
|
218
|
+
}
|
|
219
|
+
// Spec 05: deterministic instruction bridge + room pointer in the workspace,
|
|
220
|
+
// assembled from facts just before the agent runs. Best-effort — a write
|
|
221
|
+
// failure must never block the wake.
|
|
222
|
+
try {
|
|
223
|
+
if (binding.host === "claude_code") {
|
|
224
|
+
const bridge = ensureClaudeMdBridge(binding.workspace);
|
|
225
|
+
if (bridge.claudeMdAction === "created") {
|
|
226
|
+
this.emit("info", `Bridged AGENTS.md → CLAUDE.md (@import) in ${binding.workspace}.`, "bridge");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
writeRoomMd({
|
|
230
|
+
roomId: wake.room,
|
|
231
|
+
selfPlate: wake.target,
|
|
232
|
+
workspace: binding.workspace,
|
|
233
|
+
roommates: (wake.context_messages ?? []).map((m) => m.from)
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
this.emit("warn", `workspace bridge/ROOM.md write failed: ${error.message}`, "bridge_failed");
|
|
238
|
+
}
|
|
163
239
|
const key = sessionKey(wake.target, binding.workspace);
|
|
164
240
|
const sessionId = wake.session_id ?? config.sessions[key];
|
|
165
241
|
const prompt = selectWakePrompt(wake, isSkillInstalled(adapter));
|
|
166
242
|
const env = binding.host === "codex" && config.hostTokens.codex
|
|
167
243
|
? { AGENT_ROOMS_TOKEN: config.hostTokens.codex }
|
|
168
244
|
: undefined;
|
|
169
|
-
|
|
245
|
+
// Register an abort handle so an owner stop can terminate this run mid-flight.
|
|
246
|
+
const abort = new AbortController();
|
|
247
|
+
this.trackSpawn(wake.target, abort);
|
|
248
|
+
this.inFlightSpawns += 1;
|
|
249
|
+
this.recentSpawns.push(Date.now());
|
|
250
|
+
let outcome;
|
|
251
|
+
try {
|
|
252
|
+
outcome = await runWake(adapter, { prompt, workspace: binding.workspace, sessionId, maxTurns: this.maxTurns, env }, undefined, abort.signal);
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
this.inFlightSpawns -= 1;
|
|
256
|
+
this.untrackSpawn(wake.target, abort);
|
|
257
|
+
}
|
|
170
258
|
if (outcome.sessionId) {
|
|
171
259
|
config.sessions[key] = outcome.sessionId;
|
|
172
260
|
saveConfig(config);
|
|
173
261
|
this.config = config;
|
|
174
262
|
}
|
|
263
|
+
// The wire protocol carries replied/timeout/error; an owner-stopped run
|
|
264
|
+
// reports as error (the run did not complete) while we log it as stopped.
|
|
265
|
+
const reported = outcome.status === "stopped" ? "error" : outcome.status;
|
|
175
266
|
const instanceId = wake.instance_id ?? this.instanceByAgentWorkspace.get(bindingKey(binding.agent, binding.workspace));
|
|
176
267
|
if (instanceId && config.device) {
|
|
177
268
|
await listenerPost(this.apiBase, config.device.token, {
|
|
@@ -180,12 +271,50 @@ export class ListenerRuntime {
|
|
|
180
271
|
id: rid(),
|
|
181
272
|
instance_id: instanceId,
|
|
182
273
|
room: wake.room,
|
|
183
|
-
status:
|
|
274
|
+
status: reported,
|
|
184
275
|
session_id: outcome.sessionId ?? undefined
|
|
185
276
|
}).catch((error) => this.emit("warn", `wake_result post failed: ${error.message}`, "wake_result_failed"));
|
|
186
277
|
}
|
|
187
278
|
this.emit(outcome.status === "error" ? "warn" : "info", `${adapter.name} wake finished with ${outcome.status}.`, "wake_finished");
|
|
188
|
-
return { status:
|
|
279
|
+
return { status: reported };
|
|
280
|
+
}
|
|
281
|
+
trackSpawn(target, abort) {
|
|
282
|
+
const set = this.running.get(target) ?? new Set();
|
|
283
|
+
set.add(abort);
|
|
284
|
+
this.running.set(target, set);
|
|
285
|
+
}
|
|
286
|
+
untrackSpawn(target, abort) {
|
|
287
|
+
const set = this.running.get(target);
|
|
288
|
+
if (!set)
|
|
289
|
+
return;
|
|
290
|
+
set.delete(abort);
|
|
291
|
+
if (set.size === 0)
|
|
292
|
+
this.running.delete(target);
|
|
293
|
+
}
|
|
294
|
+
/** Owner stop: abort every spawn currently running for this agent, which kills
|
|
295
|
+
* the real process tree (see runWake/terminateChild). Pausing prevents future
|
|
296
|
+
* wakes; this ends the one in flight. */
|
|
297
|
+
stopTarget(target) {
|
|
298
|
+
const set = this.running.get(target);
|
|
299
|
+
if (!set || set.size === 0)
|
|
300
|
+
return 0;
|
|
301
|
+
const count = set.size;
|
|
302
|
+
for (const abort of set)
|
|
303
|
+
abort.abort();
|
|
304
|
+
this.emit("warn", `Stopped ${count} running spawn(s) for ${target}.`, "spawn_stopped");
|
|
305
|
+
return count;
|
|
306
|
+
}
|
|
307
|
+
/** §5.5: returns a reason string if a spawn must be refused, else null. */
|
|
308
|
+
spawnThrottleReason() {
|
|
309
|
+
if (this.inFlightSpawns >= MAX_CONCURRENT_SPAWNS) {
|
|
310
|
+
return `Spawn refused: ${this.inFlightSpawns} agent run(s) already in flight (cap ${MAX_CONCURRENT_SPAWNS}).`;
|
|
311
|
+
}
|
|
312
|
+
const cutoff = Date.now() - RATE_WINDOW_MS;
|
|
313
|
+
this.recentSpawns = this.recentSpawns.filter((t) => t > cutoff);
|
|
314
|
+
if (this.recentSpawns.length >= MAX_SPAWNS_PER_MIN) {
|
|
315
|
+
return `Spawn refused: rate cap reached (${MAX_SPAWNS_PER_MIN}/min). Backing off.`;
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
189
318
|
}
|
|
190
319
|
resolveBinding(bindings, wake) {
|
|
191
320
|
if (wake.instance_id) {
|