groove-dev 0.27.8 → 0.27.11
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/node_modules/@groove-dev/daemon/src/api.js +460 -25
- package/node_modules/@groove-dev/daemon/src/index.js +7 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +72 -4
- package/node_modules/@groove-dev/daemon/src/journalist.js +66 -11
- package/node_modules/@groove-dev/daemon/src/process.js +67 -7
- package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
- package/node_modules/@groove-dev/daemon/src/repo-import.js +541 -0
- package/node_modules/@groove-dev/daemon/src/rotator.js +28 -1
- package/node_modules/@groove-dev/daemon/src/supervisor.js +2 -1
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +504 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +13 -0
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +5 -4
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +4 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +677 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/app.css +14 -0
- package/node_modules/@groove-dev/gui/src/app.jsx +13 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +130 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-mdfiles.jsx +43 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +141 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +7 -1
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
- package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +14 -4
- package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +46 -11
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-card.jsx +64 -0
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +363 -0
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
- package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +22 -0
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +48 -0
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +129 -0
- package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +243 -0
- package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +192 -0
- package/node_modules/@groove-dev/gui/src/components/ui/approval-modal.jsx +63 -0
- package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/lib/edition.js +4 -0
- package/node_modules/@groove-dev/gui/src/lib/electron.js +17 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +139 -6
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +38 -39
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +82 -0
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +66 -0
- package/node_modules/@groove-dev/gui/vite.config.js +3 -0
- package/package.json +7 -2
- package/packages/daemon/src/api.js +460 -25
- package/packages/daemon/src/index.js +7 -0
- package/packages/daemon/src/introducer.js +72 -4
- package/packages/daemon/src/journalist.js +66 -11
- package/packages/daemon/src/process.js +67 -7
- package/packages/daemon/src/registry.js +1 -1
- package/packages/daemon/src/repo-import.js +541 -0
- package/packages/daemon/src/rotator.js +28 -1
- package/packages/daemon/src/supervisor.js +2 -1
- package/packages/daemon/src/tunnel-manager.js +504 -0
- package/packages/daemon/src/validate.js +13 -0
- package/packages/gui/dist/assets/index-BE6lYcd7.css +1 -0
- package/packages/gui/dist/assets/index-zdzOLAZM.js +677 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +2 -2
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +5 -5
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-dialog.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-scroll-area.js +1 -1
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tabs.js +5 -5
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tooltip.js +3 -3
- package/packages/gui/node_modules/.vite/deps/_metadata.json +53 -53
- package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js → chunk-DH7AESXW.js} +2 -2
- package/packages/gui/node_modules/.vite/deps/{chunk-KXLIKZFX.js → chunk-GFE3G4IN.js} +133 -133
- package/packages/gui/node_modules/.vite/deps/chunk-GFE3G4IN.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js → chunk-LKZVMLRH.js} +6 -6
- package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js → chunk-MCVDVNE5.js} +2 -2
- package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js → chunk-SPKVQGZX.js} +6 -6
- package/packages/gui/src/app.css +14 -0
- package/packages/gui/src/app.jsx +13 -0
- package/packages/gui/src/components/agents/agent-config.jsx +130 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
- package/packages/gui/src/components/agents/agent-mdfiles.jsx +43 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +141 -1
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +3 -3
- package/packages/gui/src/components/dashboard/intel-panel.jsx +4 -4
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/layout/activity-bar.jsx +4 -4
- package/packages/gui/src/components/layout/app-shell.jsx +7 -1
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
- package/packages/gui/src/components/layout/command-palette.jsx +14 -4
- package/packages/gui/src/components/layout/status-bar.jsx +46 -11
- package/packages/gui/src/components/marketplace/repo-card.jsx +64 -0
- package/packages/gui/src/components/marketplace/repo-import.jsx +363 -0
- package/packages/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
- package/packages/gui/src/components/pro/pro-gate.jsx +22 -0
- package/packages/gui/src/components/pro/upgrade-card.jsx +48 -0
- package/packages/gui/src/components/settings/quick-connect.jsx +129 -0
- package/packages/gui/src/components/settings/remote-server-card.jsx +243 -0
- package/packages/gui/src/components/settings/server-dialog.jsx +192 -0
- package/packages/gui/src/components/ui/approval-modal.jsx +63 -0
- package/packages/gui/src/components/ui/toast.jsx +1 -1
- package/packages/gui/src/lib/edition.js +4 -0
- package/packages/gui/src/lib/electron.js +17 -0
- package/packages/gui/src/lib/status.js +1 -0
- package/packages/gui/src/stores/groove.js +139 -6
- package/packages/gui/src/views/dashboard.jsx +38 -39
- package/packages/gui/src/views/marketplace.jsx +82 -0
- package/packages/gui/src/views/settings.jsx +66 -0
- package/packages/gui/vite.config.js +3 -0
- package/integrations/FEDERATION_PLAN.md +0 -583
- package/integrations/VOICE_PLAN.md +0 -232
- package/node_modules/@groove-dev/gui/dist/assets/index-CwmR3-HY.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DiCjVtQL.js +0 -652
- package/packages/gui/dist/assets/index-CwmR3-HY.css +0 -1
- package/packages/gui/dist/assets/index-DiCjVtQL.js +0 -652
- package/packages/gui/node_modules/.vite/deps/chunk-KXLIKZFX.js.map +0 -7
- package/test-slack.mjs +0 -28
- /package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js.map → chunk-DH7AESXW.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js.map → chunk-LKZVMLRH.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js.map → chunk-MCVDVNE5.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js.map → chunk-SPKVQGZX.js.map} +0 -0
|
@@ -1,583 +0,0 @@
|
|
|
1
|
-
# Multi-Server Federation Plan — Cross-Instance Agent Communication
|
|
2
|
-
|
|
3
|
-
> Status: Planning complete, not yet started
|
|
4
|
-
> Created: 2026-04-12
|
|
5
|
-
> Owner: Rok
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Current State
|
|
10
|
-
|
|
11
|
-
Already built:
|
|
12
|
-
- federation.js — Ed25519 keypair per daemon, peer pairing via HTTP key exchange, signed contracts with 5-min replay protection, peer storage on disk
|
|
13
|
-
- connect.js — SSH tunnel to remote daemon (groove connect user@host)
|
|
14
|
-
- CLI commands: groove federation pair/unpair/list/status
|
|
15
|
-
- API endpoints: /api/federation/* (status, peers, pair, unpair, contract send/receive)
|
|
16
|
-
|
|
17
|
-
Not built:
|
|
18
|
-
- Agent-to-agent communication across instances
|
|
19
|
-
- WebSocket bridge between paired daemons
|
|
20
|
-
- Ambassador agent role
|
|
21
|
-
- IP whitelisting on bridge port
|
|
22
|
-
- Nonce-based replay protection
|
|
23
|
-
- Ephemeral (job-scoped) connections
|
|
24
|
-
- GUI for any federation features
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Vision
|
|
29
|
-
|
|
30
|
-
Two or more Groove instances coordinate through ambassador agents. A user spawns an ambassador, it opens an ephemeral bridge to a paired peer, and agents on both sides work together as if they were on the same machine. The connection is job-scoped — it opens when work starts and closes when work finishes. Security is layered: IP whitelist first, then Ed25519 signatures, then nonces, all over Tailscale encryption.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## The Ambassador Model
|
|
35
|
-
|
|
36
|
-
### How It Works
|
|
37
|
-
|
|
38
|
-
1. Pairing (already built): `groove federation pair 100.64.0.2` — both daemons exchange Ed25519 keys, become trusted peers. This is a one-time setup. The peer's IP is stored and becomes the whitelist entry.
|
|
39
|
-
|
|
40
|
-
2. Spawning an ambassador: User picks "ambassador" role in the Spawn Wizard and selects a paired peer. The ambassador agent gets injected context: remote daemon's agent registry, remote project map, and a message channel to the remote side.
|
|
41
|
-
|
|
42
|
-
3. Bridge opens: An ephemeral WebSocket connection opens between the two daemons on the bridge port (31416). Both sides verify IP whitelist, then Ed25519 handshake.
|
|
43
|
-
|
|
44
|
-
4. Agents coordinate: Local and remote agents exchange messages, status updates, scope claims, and project map syncs through the bridge. The ambassador relays and translates.
|
|
45
|
-
|
|
46
|
-
5. Job completes: All linked agents finish. Bridge auto-closes. WebSocket torn down, port released.
|
|
47
|
-
|
|
48
|
-
### Use Cases
|
|
49
|
-
|
|
50
|
-
- API server (VPS) + frontend (local): backend and frontend agents coordinate the build together, sharing API contracts, types, and integration points
|
|
51
|
-
- Monorepo split: one Groove instance per service, ambassadors coordinate cross-service changes
|
|
52
|
-
- Staging + production: agent on staging validates changes before ambassador signals production instance to deploy
|
|
53
|
-
- Team collaboration: two developers on different machines, each running Groove, coordinate through federation
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## Security Architecture
|
|
58
|
-
|
|
59
|
-
### Layered Defense
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
Incoming connection to :31416
|
|
63
|
-
|
|
|
64
|
-
[Layer 1: IP whitelist] — not on list? TCP RST, logged. Done.
|
|
65
|
-
|
|
|
66
|
-
[Layer 2: Tailscale encryption] — WireGuard tunnel, end-to-end
|
|
67
|
-
|
|
|
68
|
-
[Layer 3: WebSocket upgrade]
|
|
69
|
-
|
|
|
70
|
-
[Layer 4: Ed25519 hello verification] — bad signature? close.
|
|
71
|
-
|
|
|
72
|
-
[Layer 5: Nonce + timestamp check] — replay? reject.
|
|
73
|
-
|
|
|
74
|
-
[Layer 6: JSON schema validation] — malformed/oversized? reject.
|
|
75
|
-
|
|
|
76
|
-
[Layer 7: Route to agent inbox]
|
|
77
|
-
|
|
|
78
|
-
[Layer 8: Agent applies scope + safety rules]
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Layer 1 — IP Whitelist (First Gate)
|
|
82
|
-
|
|
83
|
-
The bridge port checks source IP against the peer allowlist before any protocol logic, any crypto, any parsing. If you're not on the list, connection is dropped at TCP level.
|
|
84
|
-
|
|
85
|
-
- Allowlist source: federation/peers/*.json (each peer file stores host IP)
|
|
86
|
-
- Each `federation pair` adds one IP, each `federation unpair` removes one
|
|
87
|
-
- Non-whitelisted IPs never reach the WebSocket layer
|
|
88
|
-
- Bridge port appears closed to non-whitelisted IPs (port scan sees nothing)
|
|
89
|
-
- All rejections logged to audit
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
allowedIPs = federation.getPeers().map(p => p.host)
|
|
93
|
-
if sourceIP not in allowedIPs:
|
|
94
|
-
drop connection (TCP RST)
|
|
95
|
-
audit.log('bridge.rejected', { sourceIP, reason: 'not_whitelisted' })
|
|
96
|
-
return
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Eliminates: rogue pairing, port scanning, brute force, flooding from unknown IPs.
|
|
100
|
-
|
|
101
|
-
### Layer 2 — Tailscale (Transport Encryption)
|
|
102
|
-
|
|
103
|
-
Primary transport: Tailscale (WireGuard). Provides:
|
|
104
|
-
- End-to-end encryption between machines
|
|
105
|
-
- Stable IPs (100.64.x.x) that don't change
|
|
106
|
-
- NAT traversal (no port forwarding needed)
|
|
107
|
-
- Machine identity verification
|
|
108
|
-
|
|
109
|
-
Fallback transports:
|
|
110
|
-
- SSH tunnel (groove connect) — already encrypted
|
|
111
|
-
- For non-Tailscale static IPs: TLS required on bridge WebSocket (wss://)
|
|
112
|
-
- bridge.js refuses unencrypted connections to non-Tailscale, non-localhost targets
|
|
113
|
-
|
|
114
|
-
### Layer 3 — Ed25519 Signatures (Message Authentication)
|
|
115
|
-
|
|
116
|
-
Every bridge message is signed with the sender daemon's Ed25519 private key (already built in federation.js). Receiving daemon verifies with the stored public key.
|
|
117
|
-
|
|
118
|
-
Handles: compromised peer forgery (attacker would need the private key).
|
|
119
|
-
|
|
120
|
-
### Layer 4 — Nonce + Timestamp (Replay Protection)
|
|
121
|
-
|
|
122
|
-
Every message includes:
|
|
123
|
-
- nonce: UUID v4, unique per message
|
|
124
|
-
- timestamp: ISO 8601, must be within 5-minute window
|
|
125
|
-
|
|
126
|
-
Receiving daemon:
|
|
127
|
-
- Tracks seen nonces in memory (Set, pruned every 10 minutes)
|
|
128
|
-
- Rejects duplicate nonces
|
|
129
|
-
- Rejects messages outside the 5-minute timestamp window
|
|
130
|
-
- Combined with signature, eliminates replay attacks entirely
|
|
131
|
-
|
|
132
|
-
### Layer 5 — Input Validation
|
|
133
|
-
|
|
134
|
-
- JSON schema validation on every received message
|
|
135
|
-
- Max message size: 1MB
|
|
136
|
-
- Max JSON depth: 10 levels
|
|
137
|
-
- No prototype pollution (__proto__, constructor, prototype keys rejected)
|
|
138
|
-
- Payload is text only — no executable code, no serialized objects
|
|
139
|
-
- Bridge never passes payloads to eval, new Function, or child_process
|
|
140
|
-
|
|
141
|
-
### Layer 6 — Scope and Approval
|
|
142
|
-
|
|
143
|
-
- Remote peers can only send messages, not execute commands
|
|
144
|
-
- Messages go to agent inbox — the agent decides what to do
|
|
145
|
-
- Agent's own scope restrictions and CLAUDE.md safety rules apply
|
|
146
|
-
- Dangerous cross-instance actions (task handoffs, file syncs) require local user approval
|
|
147
|
-
- Message type allowlist: only hello, agent.status, message, coordinate, sync, heartbeat
|
|
148
|
-
|
|
149
|
-
### Additional Measures
|
|
150
|
-
|
|
151
|
-
- Rate limiting: 100 messages/min per peer on bridge
|
|
152
|
-
- Anomaly detection: flag sudden spikes in message volume, auto-pause connection
|
|
153
|
-
- Key rotation: `groove federation rotate-keys` generates new keypair, invalidates old, all peers must re-pair
|
|
154
|
-
- Audit trail: every cross-daemon event logged (send, receive, reject, connect, disconnect)
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## IP Whitelist — Multi-Peer Mesh
|
|
159
|
-
|
|
160
|
-
Scales naturally with each pairing:
|
|
161
|
-
|
|
162
|
-
```
|
|
163
|
-
Server A (laptop): allowlist = [B: 100.64.0.2, C: 100.64.0.3]
|
|
164
|
-
Server B (api-vps): allowlist = [A: 100.64.0.1, C: 100.64.0.3]
|
|
165
|
-
Server C (frontend-vps): allowlist = [A: 100.64.0.1, B: 100.64.0.2]
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Each server only talks to servers it has explicitly paired with. Peer-to-peer, no central hub. Adding a new server: `groove federation pair <new-ip>` on both sides.
|
|
169
|
-
|
|
170
|
-
### Dynamic IP Handling
|
|
171
|
-
|
|
172
|
-
| IP Type | Stability | Recommendation |
|
|
173
|
-
|---------|-----------|----------------|
|
|
174
|
-
| Tailscale (100.64.x.x) | Stable, tied to machine identity | Primary path, recommended |
|
|
175
|
-
| Static VPS | Stable | Works perfectly |
|
|
176
|
-
| Dynamic home IP | Changes | Use Tailscale, or re-pair when IP changes |
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## Ephemeral Connections — Job Mode
|
|
181
|
-
|
|
182
|
-
Connections are tied to a job, not a peer relationship. The pairing (key exchange + IP whitelist) is permanent, but the live WebSocket is temporary.
|
|
183
|
-
|
|
184
|
-
### Connection Lifecycle
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
User spawns ambassador with peer + task
|
|
188
|
-
-> bridge.open(peerId, { jobId, ttl, agents })
|
|
189
|
-
-> IP whitelist check
|
|
190
|
-
-> WebSocket connects, Ed25519 handshake
|
|
191
|
-
-> Agents work, messages flow
|
|
192
|
-
-> Job completes (all linked agents done)
|
|
193
|
-
-> bridge.close(peerId, jobId)
|
|
194
|
-
-> WebSocket torn down
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Auto-Close Triggers
|
|
198
|
-
|
|
199
|
-
Any of these kills the connection:
|
|
200
|
-
|
|
201
|
-
| Trigger | Default | Description |
|
|
202
|
-
|---------|---------|-------------|
|
|
203
|
-
| Job complete | Always | All linked agents complete/crash/killed |
|
|
204
|
-
| Idle timeout | 15 min | No messages in either direction |
|
|
205
|
-
| TTL expiry | 2 hours | Hard cap, even if agents still running |
|
|
206
|
-
| Manual disconnect | User action | GUI button or CLI command |
|
|
207
|
-
| Daemon shutdown | Always | Graceful close with signed goodbye |
|
|
208
|
-
|
|
209
|
-
### Keep-Alive Mode (Opt-In)
|
|
210
|
-
|
|
211
|
-
For persistent coordination (always-on servers), user sets keepAlive: true when spawning. This disables idle timeout and extends TTL to 24h with auto-renew. Default is ephemeral.
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## Protocol Design — WebSocket Bridge
|
|
216
|
-
|
|
217
|
-
### Message Envelope
|
|
218
|
-
|
|
219
|
-
Every message through the bridge:
|
|
220
|
-
|
|
221
|
-
```json
|
|
222
|
-
{
|
|
223
|
-
"type": "message",
|
|
224
|
-
"nonce": "550e8400-e29b-41d4-a716-446655440000",
|
|
225
|
-
"timestamp": "2026-04-12T20:30:00.000Z",
|
|
226
|
-
"from": { "daemon": "abc123", "agent": "frontend-1" },
|
|
227
|
-
"to": { "daemon": "def456", "agent": "backend-3" },
|
|
228
|
-
"payload": { ... },
|
|
229
|
-
"signature": "base64-ed25519-signature"
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Message Types
|
|
234
|
-
|
|
235
|
-
| Type | Direction | Description |
|
|
236
|
-
|------|-----------|-------------|
|
|
237
|
-
| hello | Bidirectional | Initial handshake with agent registry + job metadata |
|
|
238
|
-
| goodbye | Bidirectional | Graceful close with reason |
|
|
239
|
-
| heartbeat | Bidirectional | Keepalive, every 30s, dead peer at 90s |
|
|
240
|
-
| agent.status | Broadcast | Agent spawned/completed/crashed/killed on remote |
|
|
241
|
-
| message | Routed | Direct agent-to-agent message |
|
|
242
|
-
| coordinate | Routed | Scope negotiation, task handoff, shared decision |
|
|
243
|
-
| sync.registry | Request/Response | Full agent registry exchange |
|
|
244
|
-
| sync.projectmap | Request/Response | Journalist project map exchange |
|
|
245
|
-
|
|
246
|
-
### Connection Flow
|
|
247
|
-
|
|
248
|
-
```
|
|
249
|
-
Daemon A Daemon B
|
|
250
|
-
| |
|
|
251
|
-
|--- TCP connect to :31416 ---------->|
|
|
252
|
-
| [IP whitelist check]
|
|
253
|
-
|<-- TCP accept ---------------------|
|
|
254
|
-
| |
|
|
255
|
-
|--- WS upgrade -------------------->|
|
|
256
|
-
|<-- WS accept ----------------------|
|
|
257
|
-
| |
|
|
258
|
-
|--- hello (signed, with registry) ->|
|
|
259
|
-
|<-- hello (signed, with registry) --|
|
|
260
|
-
| |
|
|
261
|
-
|--- message (A:frontend -> B:backend) -->|
|
|
262
|
-
|<-- message (B:backend -> A:frontend) ---|
|
|
263
|
-
| |
|
|
264
|
-
|--- heartbeat --------------------->|
|
|
265
|
-
|<-- heartbeat ----------------------|
|
|
266
|
-
| |
|
|
267
|
-
|--- goodbye (job complete) -------->|
|
|
268
|
-
|<-- goodbye (ack) ------------------|
|
|
269
|
-
|--- TCP close --------------------->|
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## Daemon Architecture
|
|
275
|
-
|
|
276
|
-
### New Files
|
|
277
|
-
|
|
278
|
-
#### bridge.js — Ephemeral WebSocket Bridge Manager
|
|
279
|
-
|
|
280
|
-
```
|
|
281
|
-
class Bridge {
|
|
282
|
-
constructor(daemon)
|
|
283
|
-
- connections: Map<jobId, Connection>
|
|
284
|
-
- seenNonces: Set (pruned every 10 min)
|
|
285
|
-
- bridgeServer: WebSocket.Server on :31416
|
|
286
|
-
|
|
287
|
-
open(peerId, { jobId, linkedAgents, ttl, keepAlive })
|
|
288
|
-
- Validate peer is paired
|
|
289
|
-
- Enforce transport security (Tailscale/localhost/wss)
|
|
290
|
-
- Connect WebSocket to peer's bridge port
|
|
291
|
-
- Send signed hello with job metadata + agent registry
|
|
292
|
-
- Start idle timer + TTL timer
|
|
293
|
-
- Return connection handle
|
|
294
|
-
|
|
295
|
-
close(jobId)
|
|
296
|
-
- Send signed goodbye
|
|
297
|
-
- Close WebSocket
|
|
298
|
-
- Clean up timers
|
|
299
|
-
- Audit log
|
|
300
|
-
|
|
301
|
-
send(jobId, message)
|
|
302
|
-
- Add nonce (UUID v4) + timestamp
|
|
303
|
-
- Sign with Ed25519
|
|
304
|
-
- Validate message size (<1MB)
|
|
305
|
-
- Route through connection
|
|
306
|
-
- Update lastActivity
|
|
307
|
-
|
|
308
|
-
receive(rawMessage)
|
|
309
|
-
- Verify source IP is whitelisted
|
|
310
|
-
- Verify Ed25519 signature
|
|
311
|
-
- Check nonce (reject duplicate)
|
|
312
|
-
- Check timestamp (reject >5min)
|
|
313
|
-
- Validate JSON schema
|
|
314
|
-
- Route to target agent's inbox
|
|
315
|
-
- Emit event for GUI WebSocket
|
|
316
|
-
|
|
317
|
-
_onAgentStatusChange(agentId, status)
|
|
318
|
-
- Broadcast to all connections that include this agent
|
|
319
|
-
- If all linked agents done, trigger close
|
|
320
|
-
|
|
321
|
-
_checkIdle(jobId)
|
|
322
|
-
- If lastActivity > idleTimeout, close
|
|
323
|
-
|
|
324
|
-
_enforceTransportSecurity(peerHost)
|
|
325
|
-
- Tailscale range (100.64.0.0/10): allow ws://
|
|
326
|
-
- localhost/127.0.0.1: allow ws://
|
|
327
|
-
- All others: require wss:// or reject
|
|
328
|
-
|
|
329
|
-
_pruneNonces()
|
|
330
|
-
- Remove nonces older than 10 minutes
|
|
331
|
-
- Runs on 5-minute timer
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### Modified Files
|
|
336
|
-
|
|
337
|
-
#### federation.js
|
|
338
|
-
|
|
339
|
-
- Add peer IP to allowlist management
|
|
340
|
-
- Add bridge lifecycle hooks (onPeerPaired, onPeerUnpaired)
|
|
341
|
-
- Add key rotation support
|
|
342
|
-
- Add nonce tracking helpers
|
|
343
|
-
|
|
344
|
-
#### api.js — New Endpoints
|
|
345
|
-
|
|
346
|
-
| Method | Path | Description |
|
|
347
|
-
|--------|------|-------------|
|
|
348
|
-
| GET | /api/federation/bridge/status | Bridge connection state per peer |
|
|
349
|
-
| POST | /api/federation/bridge/open | Open bridge to peer (for ambassador spawn) |
|
|
350
|
-
| POST | /api/federation/bridge/close | Close bridge connection |
|
|
351
|
-
| POST | /api/federation/bridge/message | Send message to remote agent |
|
|
352
|
-
| GET | /api/federation/bridge/inbox/:agentId | Pending messages for an agent |
|
|
353
|
-
| GET | /api/federation/remote-agents | List agents on all peers |
|
|
354
|
-
| GET | /api/federation/remote-agents/:peerId | List agents on a specific peer |
|
|
355
|
-
|
|
356
|
-
#### index.js
|
|
357
|
-
|
|
358
|
-
- Initialize Bridge after Federation
|
|
359
|
-
- Wire bridge port listener (31416)
|
|
360
|
-
- Pass bridge reference to ProcessManager for ambassador spawning
|
|
361
|
-
|
|
362
|
-
#### introducer.js
|
|
363
|
-
|
|
364
|
-
- Inject remote agent context into ambassador agents
|
|
365
|
-
- Include remote project map in agent briefing
|
|
366
|
-
- Add cross-instance team awareness
|
|
367
|
-
|
|
368
|
-
#### registry.js
|
|
369
|
-
|
|
370
|
-
- Support "remote" agent entries (ghost agents from peers)
|
|
371
|
-
- Remote agents are read-only, updated via bridge sync
|
|
372
|
-
|
|
373
|
-
#### process.js
|
|
374
|
-
|
|
375
|
-
- Ambassador role spawning with federation context
|
|
376
|
-
- Wire agent status changes to bridge broadcasts
|
|
377
|
-
|
|
378
|
-
### Port Allocation
|
|
379
|
-
|
|
380
|
-
| Port | Binding | Purpose |
|
|
381
|
-
|------|---------|---------|
|
|
382
|
-
| 31415 | 127.0.0.1 | Main daemon (GUI, API, WebSocket, local agents) |
|
|
383
|
-
| 31416 | Tailscale interface / 0.0.0.0 with IP whitelist | Bridge (peer-to-peer federation) |
|
|
384
|
-
|
|
385
|
-
---
|
|
386
|
-
|
|
387
|
-
## GUI Design
|
|
388
|
-
|
|
389
|
-
### A. Spawn Wizard — Federation Section
|
|
390
|
-
|
|
391
|
-
Appears when at least one peer is paired:
|
|
392
|
-
|
|
393
|
-
```
|
|
394
|
-
Federation
|
|
395
|
-
[x] Connect to peer: [dropdown: my-vps (100.64.0.2)]
|
|
396
|
-
Remote agents: backend-3, frontend-2, planner-1
|
|
397
|
-
Mode: (x) Ambassador ( ) Direct link
|
|
398
|
-
|
|
399
|
-
Ambassador: Full bridge — relays between local and remote teams
|
|
400
|
-
Direct link: 1:1 channel to a specific remote agent
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
### B. Agent Config Panel — Federation Tab
|
|
404
|
-
|
|
405
|
-
New tab alongside Chat, Config, Monitor, Files:
|
|
406
|
-
|
|
407
|
-
```
|
|
408
|
-
Federation
|
|
409
|
-
Peer: my-vps (100.64.0.2) [Connected]
|
|
410
|
-
Role: Ambassador
|
|
411
|
-
Job: job-a1b2c3
|
|
412
|
-
Bridge uptime: 14m
|
|
413
|
-
Messages: 47 sent / 52 received
|
|
414
|
-
Last activity: 12s ago
|
|
415
|
-
|
|
416
|
-
Remote Team:
|
|
417
|
-
backend-3 (running) — packages/api/src/**
|
|
418
|
-
frontend-2 (running) — packages/web/src/**
|
|
419
|
-
planner-1 (completed)
|
|
420
|
-
|
|
421
|
-
[Send Message] [Sync Project Map] [Disconnect]
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
### C. Agent Tree View
|
|
425
|
-
|
|
426
|
-
Remote agents appear as ghost nodes — dimmed, with a network icon badge. Connected via dashed edges to the local ambassador.
|
|
427
|
-
|
|
428
|
-
```
|
|
429
|
-
[planner-1] ----> [frontend-1]
|
|
430
|
-
| |
|
|
431
|
-
| [ambassador-1] - - - - [remote:backend-3]
|
|
432
|
-
| - - - - [remote:frontend-2]
|
|
433
|
-
v
|
|
434
|
-
[backend-1]
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
Clicking a remote ghost node shows read-only status in the detail panel.
|
|
438
|
-
|
|
439
|
-
### D. Status Bar
|
|
440
|
-
|
|
441
|
-
When federation bridge is active:
|
|
442
|
-
|
|
443
|
-
```
|
|
444
|
-
Connected | 3/4 agents | Federation: 1 peer (3 remote agents)
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
### E. Dashboard — Federation Card
|
|
448
|
-
|
|
449
|
-
New card in the KPI strip:
|
|
450
|
-
|
|
451
|
-
| Metric | Value |
|
|
452
|
-
|--------|-------|
|
|
453
|
-
| Peers connected | 1 |
|
|
454
|
-
| Remote agents | 3 |
|
|
455
|
-
| Messages exchanged | 99 |
|
|
456
|
-
| Bridge uptime | 14m |
|
|
457
|
-
| Active jobs | 1 |
|
|
458
|
-
|
|
459
|
-
### F. Settings — Federation Section
|
|
460
|
-
|
|
461
|
-
```
|
|
462
|
-
Federation
|
|
463
|
-
Daemon ID: abc123
|
|
464
|
-
Keypair: Ready (Ed25519)
|
|
465
|
-
|
|
466
|
-
Paired Peers:
|
|
467
|
-
[my-vps] 100.64.0.2 paired 2026-04-10 [Unpair]
|
|
468
|
-
[staging] 100.64.0.3 paired 2026-04-11 [Unpair]
|
|
469
|
-
|
|
470
|
-
[Pair New Server] [Rotate Keys]
|
|
471
|
-
|
|
472
|
-
Bridge Settings:
|
|
473
|
-
Default idle timeout: [15] minutes
|
|
474
|
-
Default TTL: [2] hours
|
|
475
|
-
Bridge port: [31416]
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
---
|
|
479
|
-
|
|
480
|
-
## Integration Registry Entry
|
|
481
|
-
|
|
482
|
-
New entry in integrations-registry.json:
|
|
483
|
-
|
|
484
|
-
```json
|
|
485
|
-
{
|
|
486
|
-
"id": "federation",
|
|
487
|
-
"name": "Cross-Server Federation",
|
|
488
|
-
"description": "Connect agents across Groove instances for coordinated multi-server builds",
|
|
489
|
-
"category": "developer",
|
|
490
|
-
"icon": "network",
|
|
491
|
-
"tags": ["federation", "multi-server", "cross-instance", "ambassador"],
|
|
492
|
-
"roles": ["ambassador", "fullstack", "backend", "frontend"],
|
|
493
|
-
"authType": "none",
|
|
494
|
-
"envKeys": [],
|
|
495
|
-
"setupSteps": [
|
|
496
|
-
"Pair with a remote Groove instance: groove federation pair <ip>",
|
|
497
|
-
"Both daemons must be on Tailscale or have static IPs",
|
|
498
|
-
"Spawn an ambassador agent and select the paired peer"
|
|
499
|
-
],
|
|
500
|
-
"requiresApproval": ["send_message", "sync_project", "coordinate_task"],
|
|
501
|
-
"agentInstructions": "## Federation Integration\n\nYou are an ambassador agent connected to a remote Groove instance.\n\n### API\nSend message: POST http://localhost:31415/api/federation/bridge/message\nBody: {\"jobId\": \"...\", \"to\": {\"agent\": \"remote-agent-id\"}, \"payload\": {...}}\n\nCheck inbox: GET http://localhost:31415/api/federation/bridge/inbox/<your-agent-id>\n\nRemote agents: GET http://localhost:31415/api/federation/remote-agents\n\n### Rules\n- Coordinate, don't command — remote agents have their own scope and safety rules\n- Confirm with user before initiating cross-instance task handoffs\n- Keep messages concise — bridge has rate limits (100/min)"
|
|
502
|
-
}
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
---
|
|
506
|
-
|
|
507
|
-
## CLI Additions
|
|
508
|
-
|
|
509
|
-
```
|
|
510
|
-
groove federation pair <ip> — pair with remote daemon (existing)
|
|
511
|
-
groove federation unpair <id> — remove peer (existing)
|
|
512
|
-
groove federation list — list peers (existing)
|
|
513
|
-
groove federation status — federation status (existing)
|
|
514
|
-
groove federation rotate-keys — rotate Ed25519 keypair, invalidate old
|
|
515
|
-
groove federation bridge status — show active bridge connections
|
|
516
|
-
groove federation bridge close <id> — manually close a bridge connection
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
---
|
|
520
|
-
|
|
521
|
-
## Phased Rollout
|
|
522
|
-
|
|
523
|
-
### Phase 1 — Bridge + Ambassador (core)
|
|
524
|
-
|
|
525
|
-
- [ ] bridge.js — WebSocket bridge with IP whitelist, Ed25519 handshake, nonce replay protection
|
|
526
|
-
- [ ] Bridge port listener on 31416 with IP whitelist enforcement
|
|
527
|
-
- [ ] Agent registry sync (see remote agents via bridge)
|
|
528
|
-
- [ ] Direct agent-to-agent messaging through bridge
|
|
529
|
-
- [ ] Ambassador role in spawn wizard
|
|
530
|
-
- [ ] Ephemeral connection lifecycle (idle timeout, TTL, auto-close on job complete)
|
|
531
|
-
- [ ] API endpoints for bridge management
|
|
532
|
-
- [ ] Federation tab in agent config panel
|
|
533
|
-
|
|
534
|
-
### Phase 2 — Coordination Protocol
|
|
535
|
-
|
|
536
|
-
- [ ] Cross-instance scope negotiation (remote backend claims API files, local frontend claims UI files)
|
|
537
|
-
- [ ] Task handoff: remote agent completes, hands off to local agent with context
|
|
538
|
-
- [ ] Shared journalist: project map exchange so both sides have full picture
|
|
539
|
-
- [ ] Ghost nodes in agent tree view (remote agents as dimmed nodes)
|
|
540
|
-
- [ ] Federation card on dashboard
|
|
541
|
-
|
|
542
|
-
### Phase 3 — Polish + Scale
|
|
543
|
-
|
|
544
|
-
- [ ] Multi-peer mesh (3+ daemons)
|
|
545
|
-
- [ ] Keep-alive mode for persistent coordination
|
|
546
|
-
- [ ] Key rotation command and flow
|
|
547
|
-
- [ ] Anomaly detection (spike in message volume, auto-pause)
|
|
548
|
-
- [ ] Settings panel for federation config
|
|
549
|
-
- [ ] Message history / audit viewer in GUI
|
|
550
|
-
- [ ] Connection quality indicators
|
|
551
|
-
- [ ] TLS fallback for non-Tailscale public IP connections
|
|
552
|
-
|
|
553
|
-
---
|
|
554
|
-
|
|
555
|
-
## Team Structure
|
|
556
|
-
|
|
557
|
-
| Role | Scope | Work |
|
|
558
|
-
|------|-------|------|
|
|
559
|
-
| Backend | daemon/src/bridge.js (new), federation.js, api.js, index.js, process.js, introducer.js, integrations-registry.json | Bridge manager, IP whitelist, nonce tracking, API endpoints, ambassador spawning |
|
|
560
|
-
| Frontend | gui/src/components/agents/spawn-wizard.jsx, agent-panel.jsx, gui/src/views/agents.jsx, dashboard.jsx, settings.jsx, stores/groove.js | Federation tab, ghost nodes, spawn wizard federation section, dashboard card, settings panel |
|
|
561
|
-
| QC (fullstack) | All | Verify end-to-end bridge works, build, no regressions, security review |
|
|
562
|
-
|
|
563
|
-
---
|
|
564
|
-
|
|
565
|
-
## Key Dependencies
|
|
566
|
-
|
|
567
|
-
```
|
|
568
|
-
# Already in use
|
|
569
|
-
ws — WebSocket (already used by daemon)
|
|
570
|
-
crypto — Ed25519 (already used by federation.js)
|
|
571
|
-
minimatch — scope matching (already used by lockmanager.js)
|
|
572
|
-
|
|
573
|
-
# New (if TLS fallback needed)
|
|
574
|
-
selfsigned — generate self-signed certs for wss:// fallback
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
No new heavy dependencies. The bridge is built on ws (already a dependency) and crypto (built-in Node).
|
|
578
|
-
|
|
579
|
-
---
|
|
580
|
-
|
|
581
|
-
## Summary
|
|
582
|
-
|
|
583
|
-
Federation turns Groove from a single-machine orchestrator into a distributed one. The ambassador model keeps it simple — agents don't need to know about networking, they just talk to their ambassador. Security is layered: IP whitelist stops 99% of threats at the TCP level, Ed25519 + nonces handle the rest, and Tailscale encrypts the transport. Connections are ephemeral by default — open for a job, closed when done. The whole system scales by adding peers with `groove federation pair`.
|