@dp-pcs/ogp 0.2.31 → 0.3.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/README.md +11 -0
- package/dist/daemon/notify.d.ts +20 -0
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +165 -48
- package/dist/daemon/notify.js.map +1 -1
- package/dist/shared/config.d.ts +3 -0
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js.map +1 -1
- package/docs/TESTING-HERMES-BACKEND.md +278 -0
- package/docs/extending-to-hermes.md +462 -0
- package/docs/hermes-implementation-checklist.md +448 -0
- package/docs/hermes-local-testing.md +478 -0
- package/docs/hermes-tunnel-setup.md +214 -0
- package/docs/platform-agnostic-architecture.md +472 -0
- package/package.json +3 -2
- package/scripts/install-skills.js +142 -20
- package/skills/ogp-project/SKILL.md +321 -226
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
# Platform-Agnostic OGP Architecture
|
|
2
|
+
|
|
3
|
+
> Design principles and implementation plan for making OGP work with any AI assistant platform
|
|
4
|
+
|
|
5
|
+
## Executive Summary
|
|
6
|
+
|
|
7
|
+
OGP is designed to enable federation between AI assistants regardless of their underlying platform. The core protocol (Ed25519 signatures, peer management, scope enforcement) is platform-agnostic. Only the **notification mechanism** (how OGP communicates with the local agent) needs to be adapted per platform.
|
|
8
|
+
|
|
9
|
+
## Core Design Principle
|
|
10
|
+
|
|
11
|
+
**One OGP daemon instance per AI assistant instance.**
|
|
12
|
+
|
|
13
|
+
Each OGP daemon is:
|
|
14
|
+
- **Independent**: Own port, state directory, keypair, configuration
|
|
15
|
+
- **Platform-agnostic**: Core protocol implementation never changes
|
|
16
|
+
- **Notification-pluggable**: Backend adapter for communicating with local agent
|
|
17
|
+
|
|
18
|
+
This design allows:
|
|
19
|
+
- Multiple AI platforms to federate seamlessly
|
|
20
|
+
- Multiple OGP instances on the same machine (for testing or multi-agent setups)
|
|
21
|
+
- Easy addition of new platforms without modifying core protocol
|
|
22
|
+
|
|
23
|
+
## Architecture Layers
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
27
|
+
│ Layer 1: Core OGP Protocol (Platform-Agnostic) │
|
|
28
|
+
│ - Ed25519 signing/verification │
|
|
29
|
+
│ - Peer management (peers.json) │
|
|
30
|
+
│ - Scope enforcement (Doorman) │
|
|
31
|
+
│ - Message routing (intent handlers) │
|
|
32
|
+
│ - HTTP endpoints (/federation/*, /.well-known/ogp) │
|
|
33
|
+
│ - Rendezvous integration │
|
|
34
|
+
└────────────────────────┬────────────────────────────────────┘
|
|
35
|
+
│
|
|
36
|
+
▼
|
|
37
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
38
|
+
│ Layer 2: Notification Backend (Platform-Specific) │
|
|
39
|
+
│ │
|
|
40
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
41
|
+
│ │ OpenClaw │ │ Hermes │ │ Future │ │
|
|
42
|
+
│ │ Backend │ │ Backend │ │ Platform │ │
|
|
43
|
+
│ │ │ │ │ │ Backend │ │
|
|
44
|
+
│ │ POST /hooks/ │ │ POST webhook │ │ POST /api/v1/│ │
|
|
45
|
+
│ │ agent │ │ /ogp_fed │ │ messages │ │
|
|
46
|
+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
47
|
+
└─────────────────────────────────────────────────────────────┘
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Notification Backend System
|
|
51
|
+
|
|
52
|
+
### Current Implementation (OpenClaw-Only)
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// src/daemon/notify.ts
|
|
56
|
+
async function notifyOpenClaw(
|
|
57
|
+
peerId: string,
|
|
58
|
+
intent: string,
|
|
59
|
+
payload: any
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const openclawUrl = config.openclawUrl || "http://localhost:18789";
|
|
62
|
+
const token = config.openclawToken;
|
|
63
|
+
|
|
64
|
+
if (config.hooks?.enabled && config.openclawHooksToken) {
|
|
65
|
+
// Method 1: Webhook (preferred)
|
|
66
|
+
await fetch(`${openclawUrl}/hooks/agent`, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: {
|
|
69
|
+
"Content-Type": "application/json",
|
|
70
|
+
"Authorization": `Bearer ${config.openclawHooksToken}`
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
agentId: config.agentId,
|
|
74
|
+
peerId,
|
|
75
|
+
intent,
|
|
76
|
+
topic: payload.topic,
|
|
77
|
+
message: payload.message,
|
|
78
|
+
notifyTarget: resolveNotifyTarget(config.agentId),
|
|
79
|
+
timestamp: new Date().toISOString()
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
// Method 2: CLI fallback
|
|
84
|
+
await execAsync(`openclaw system event --mode now`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Proposed Refactor (Platform-Agnostic)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// src/daemon/notify.ts
|
|
93
|
+
|
|
94
|
+
interface NotificationBackend {
|
|
95
|
+
name: string;
|
|
96
|
+
notify(context: NotificationContext): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface NotificationContext {
|
|
100
|
+
peerId: string;
|
|
101
|
+
peerDisplayName: string;
|
|
102
|
+
intent: string;
|
|
103
|
+
payload: any;
|
|
104
|
+
timestamp: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
class OpenClawBackend implements NotificationBackend {
|
|
108
|
+
name = "openclaw";
|
|
109
|
+
|
|
110
|
+
async notify(ctx: NotificationContext): Promise<void> {
|
|
111
|
+
const url = config.openclawUrl || "http://localhost:18789";
|
|
112
|
+
|
|
113
|
+
if (config.hooks?.enabled && config.openclawHooksToken) {
|
|
114
|
+
await fetch(`${url}/hooks/agent`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
"Content-Type": "application/json",
|
|
118
|
+
"Authorization": `Bearer ${config.openclawHooksToken}`
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
agentId: config.agentId,
|
|
122
|
+
peerId: ctx.peerId,
|
|
123
|
+
intent: ctx.intent,
|
|
124
|
+
topic: ctx.payload.topic,
|
|
125
|
+
message: ctx.payload.message,
|
|
126
|
+
notifyTarget: resolveNotifyTarget(config.agentId),
|
|
127
|
+
timestamp: ctx.timestamp
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
await execAsync(`openclaw system event --mode now`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class HermesBackend implements NotificationBackend {
|
|
137
|
+
name = "hermes";
|
|
138
|
+
|
|
139
|
+
async notify(ctx: NotificationContext): Promise<void> {
|
|
140
|
+
const webhookUrl = config.hermesWebhookUrl || "http://localhost:8644/webhooks/ogp_federation";
|
|
141
|
+
const secret = config.hermesWebhookSecret;
|
|
142
|
+
|
|
143
|
+
const body = {
|
|
144
|
+
peer_id: ctx.peerId,
|
|
145
|
+
peer_display_name: ctx.peerDisplayName,
|
|
146
|
+
intent: ctx.intent,
|
|
147
|
+
topic: ctx.payload.topic || "",
|
|
148
|
+
message: ctx.payload.message || "",
|
|
149
|
+
priority: ctx.payload.priority || "normal",
|
|
150
|
+
conversation_id: ctx.payload.conversationId,
|
|
151
|
+
timestamp: ctx.timestamp,
|
|
152
|
+
...ctx.payload
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const signature = computeHMAC(JSON.stringify(body), secret);
|
|
156
|
+
|
|
157
|
+
await fetch(webhookUrl, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: {
|
|
160
|
+
"Content-Type": "application/json",
|
|
161
|
+
"X-Hub-Signature-256": `sha256=${signature}`
|
|
162
|
+
},
|
|
163
|
+
body: JSON.stringify(body)
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Factory for backend selection
|
|
169
|
+
function getNotificationBackend(): NotificationBackend {
|
|
170
|
+
const platform = config.platform || "openclaw";
|
|
171
|
+
|
|
172
|
+
switch (platform) {
|
|
173
|
+
case "openclaw":
|
|
174
|
+
return new OpenClawBackend();
|
|
175
|
+
case "hermes":
|
|
176
|
+
return new HermesBackend();
|
|
177
|
+
default:
|
|
178
|
+
throw new Error(`Unknown platform: ${platform}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Main notification entry point
|
|
183
|
+
export async function notifyLocalAgent(
|
|
184
|
+
peerId: string,
|
|
185
|
+
peerDisplayName: string,
|
|
186
|
+
intent: string,
|
|
187
|
+
payload: any
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
const backend = getNotificationBackend();
|
|
190
|
+
|
|
191
|
+
await backend.notify({
|
|
192
|
+
peerId,
|
|
193
|
+
peerDisplayName,
|
|
194
|
+
intent,
|
|
195
|
+
payload,
|
|
196
|
+
timestamp: new Date().toISOString()
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Configuration Schema
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
// ~/.ogp/config.json (OpenClaw instance)
|
|
205
|
+
{
|
|
206
|
+
"daemonPort": 18790,
|
|
207
|
+
"platform": "openclaw",
|
|
208
|
+
"gatewayUrl": "https://alice-openclaw.example.com",
|
|
209
|
+
"displayName": "Alice (OpenClaw)",
|
|
210
|
+
"email": "alice@example.com",
|
|
211
|
+
"stateDir": "~/.ogp",
|
|
212
|
+
|
|
213
|
+
"openclawUrl": "http://localhost:18789",
|
|
214
|
+
"openclawToken": "...",
|
|
215
|
+
"openclawHooksToken": "...",
|
|
216
|
+
"agentId": "main",
|
|
217
|
+
"notifyTarget": "telegram:123456789",
|
|
218
|
+
|
|
219
|
+
"rendezvous": {
|
|
220
|
+
"enabled": true,
|
|
221
|
+
"url": "https://rendezvous.elelem.expert"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
// ~/.ogp-hermes/config.json (Hermes instance)
|
|
228
|
+
{
|
|
229
|
+
"daemonPort": 18791,
|
|
230
|
+
"platform": "hermes",
|
|
231
|
+
"gatewayUrl": "https://alice-hermes.example.com",
|
|
232
|
+
"displayName": "Alice (Hermes)",
|
|
233
|
+
"email": "alice@example.com",
|
|
234
|
+
"stateDir": "~/.ogp-hermes",
|
|
235
|
+
|
|
236
|
+
"hermesWebhookUrl": "http://localhost:8644/webhooks/ogp_federation",
|
|
237
|
+
"hermesWebhookSecret": "shared-secret-here",
|
|
238
|
+
|
|
239
|
+
"rendezvous": {
|
|
240
|
+
"enabled": true,
|
|
241
|
+
"url": "https://rendezvous.elelem.expert"
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Multi-Instance Setup (Local Testing)
|
|
247
|
+
|
|
248
|
+
### Running Both OpenClaw and Hermes Locally
|
|
249
|
+
|
|
250
|
+
**Step 1: Configure Hermes Webhook**
|
|
251
|
+
|
|
252
|
+
```yaml
|
|
253
|
+
# ~/.hermes/config.yaml
|
|
254
|
+
platforms:
|
|
255
|
+
webhook:
|
|
256
|
+
enabled: true
|
|
257
|
+
port: 8644
|
|
258
|
+
host: "127.0.0.1"
|
|
259
|
+
routes:
|
|
260
|
+
ogp_federation:
|
|
261
|
+
secret: "shared-secret-here"
|
|
262
|
+
events: ["*"] # Accept all OGP intents
|
|
263
|
+
prompt: |
|
|
264
|
+
📡 **OGP Federation Message**
|
|
265
|
+
|
|
266
|
+
**From:** {{peer_display_name}} ({{peer_id}})
|
|
267
|
+
**Intent:** {{intent}}
|
|
268
|
+
{{#if topic}}**Topic:** {{topic}}{{/if}}
|
|
269
|
+
{{#if priority}}**Priority:** {{priority}}{{/if}}
|
|
270
|
+
|
|
271
|
+
{{message}}
|
|
272
|
+
deliver: "telegram" # Or wherever you want responses
|
|
273
|
+
deliver_extra:
|
|
274
|
+
chat_id: "your-telegram-chat-id"
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Step 2: Start OGP for OpenClaw (existing)**
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
# Uses ~/.ogp and port 18790
|
|
281
|
+
ogp start
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Step 3: Start OGP for Hermes (new instance)**
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Create separate state directory
|
|
288
|
+
mkdir -p ~/.ogp-hermes
|
|
289
|
+
|
|
290
|
+
# Create config (see above)
|
|
291
|
+
cat > ~/.ogp-hermes/config.json <<EOF
|
|
292
|
+
{
|
|
293
|
+
"daemonPort": 18791,
|
|
294
|
+
"platform": "hermes",
|
|
295
|
+
"gatewayUrl": "http://localhost:18791",
|
|
296
|
+
"displayName": "Alice (Hermes)",
|
|
297
|
+
"email": "alice@example.com",
|
|
298
|
+
"stateDir": "~/.ogp-hermes",
|
|
299
|
+
"hermesWebhookUrl": "http://localhost:8644/webhooks/ogp_federation",
|
|
300
|
+
"hermesWebhookSecret": "shared-secret-here"
|
|
301
|
+
}
|
|
302
|
+
EOF
|
|
303
|
+
|
|
304
|
+
# Start second OGP daemon
|
|
305
|
+
OGP_STATE_DIR=~/.ogp-hermes ogp start --port 18791
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Step 4: Federate the Two Local Instances**
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
# From OpenClaw's OGP, request federation with Hermes's OGP
|
|
312
|
+
ogp federation request http://localhost:18791 --alias hermes-local
|
|
313
|
+
|
|
314
|
+
# From Hermes's OGP, approve the request
|
|
315
|
+
OGP_STATE_DIR=~/.ogp-hermes ogp federation approve <openclaw-peer-id>
|
|
316
|
+
|
|
317
|
+
# Now send a message from OpenClaw to Hermes
|
|
318
|
+
ogp federation send hermes-local message '{"text":"Hello from OpenClaw!"}'
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The message will:
|
|
322
|
+
1. Leave OpenClaw's OGP (port 18790)
|
|
323
|
+
2. Arrive at Hermes's OGP (port 18791)
|
|
324
|
+
3. Be verified by Doorman
|
|
325
|
+
4. POST to Hermes webhook (port 8644)
|
|
326
|
+
5. Hermes processes and responds
|
|
327
|
+
|
|
328
|
+
### Why This Works
|
|
329
|
+
|
|
330
|
+
1. **Independent Keypairs**: Each OGP instance generates its own Ed25519 keypair
|
|
331
|
+
2. **Independent Peer Lists**: `~/.ogp/peers.json` vs `~/.ogp-hermes/peers.json`
|
|
332
|
+
3. **No Port Conflicts**: 18790 vs 18791
|
|
333
|
+
4. **Standard P2P Flow**: Same cryptographic verification as if they were remote
|
|
334
|
+
|
|
335
|
+
## Implementation Phases
|
|
336
|
+
|
|
337
|
+
### Phase 1: Sidecar Integration (Week 1)
|
|
338
|
+
|
|
339
|
+
**Goal:** Get Hermes working with OGP via webhook without modifying core protocol
|
|
340
|
+
|
|
341
|
+
**Tasks:**
|
|
342
|
+
1. ✅ Document architecture (this file)
|
|
343
|
+
2. Add Hermes notification backend to `src/daemon/notify.ts`
|
|
344
|
+
3. Add `platform` config field to config schema
|
|
345
|
+
4. Test with local multi-instance setup
|
|
346
|
+
5. Update CLI to support `OGP_STATE_DIR` environment variable
|
|
347
|
+
|
|
348
|
+
**Deliverables:**
|
|
349
|
+
- OGP works with both OpenClaw and Hermes
|
|
350
|
+
- Can run multiple instances simultaneously
|
|
351
|
+
- Documentation for setup
|
|
352
|
+
|
|
353
|
+
### Phase 2: Native Hermes Platform (Month 1-2)
|
|
354
|
+
|
|
355
|
+
**Goal:** Create Hermes platform adapter that speaks OGP natively
|
|
356
|
+
|
|
357
|
+
**Tasks:**
|
|
358
|
+
1. Create `~/.hermes/hermes-agent/gateway/platforms/ogp.py`
|
|
359
|
+
2. Implement Ed25519 crypto using Python `cryptography` library
|
|
360
|
+
3. Implement Doorman access control
|
|
361
|
+
4. Implement peer management (JSON storage)
|
|
362
|
+
5. Implement HTTP endpoints (`/.well-known/ogp`, `/federation/*`)
|
|
363
|
+
6. Test interoperability with Node.js OGP instances
|
|
364
|
+
|
|
365
|
+
**Deliverables:**
|
|
366
|
+
- Hermes can speak OGP without separate daemon
|
|
367
|
+
- Full protocol compatibility
|
|
368
|
+
- Performance benchmarks
|
|
369
|
+
|
|
370
|
+
### Phase 3: Protocol Standardization (Month 3+)
|
|
371
|
+
|
|
372
|
+
**Goal:** Formalize OGP as a true multi-platform standard
|
|
373
|
+
|
|
374
|
+
**Tasks:**
|
|
375
|
+
1. Extract protocol specification into standalone repo
|
|
376
|
+
2. Create reference test suite
|
|
377
|
+
3. Add protocol versioning and negotiation
|
|
378
|
+
4. Support for other platforms (claude-code, OpenHands, etc.)
|
|
379
|
+
5. Performance optimizations
|
|
380
|
+
6. Security audit
|
|
381
|
+
|
|
382
|
+
**Deliverables:**
|
|
383
|
+
- OGP protocol specification v1.0
|
|
384
|
+
- Reference implementations: Node.js, Python
|
|
385
|
+
- Compliance test suite
|
|
386
|
+
|
|
387
|
+
## Remote Federation (Typical Use Case)
|
|
388
|
+
|
|
389
|
+
When users are on different machines (the normal scenario):
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
┌─────────────────────────┐ ┌─────────────────────────┐
|
|
393
|
+
│ Alice's Machine │ │ Bob's Machine │
|
|
394
|
+
│ │ │ │
|
|
395
|
+
│ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │
|
|
396
|
+
│ │ OGP Daemon │◄├───────────┤►│ OGP Daemon │ │
|
|
397
|
+
│ │ :18790 │ │ Internet │ │ :18790 │ │
|
|
398
|
+
│ │ │ │ (HTTPS) │ │ │ │
|
|
399
|
+
│ │ Platform: OpenClaw │ │ │ │ Platform: Hermes │ │
|
|
400
|
+
│ └──────────┬──────────┘ │ │ └──────────┬──────────┘ │
|
|
401
|
+
│ │ │ │ │ │
|
|
402
|
+
│ ▼ │ │ ▼ │
|
|
403
|
+
│ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │
|
|
404
|
+
│ │ OpenClaw Instance │ │ │ │ Hermes Instance │ │
|
|
405
|
+
│ │ :18789 │ │ │ │ Webhook :8644 │ │
|
|
406
|
+
│ └─────────────────────┘ │ │ └─────────────────────┘ │
|
|
407
|
+
└─────────────────────────┘ └─────────────────────────┘
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
**Key Point:** Alice doesn't need to know Bob uses Hermes. Bob doesn't need to know Alice uses OpenClaw. The OGP protocol is the same; only the local notification mechanism differs.
|
|
411
|
+
|
|
412
|
+
## Benefits of This Architecture
|
|
413
|
+
|
|
414
|
+
1. **True Platform Independence**: Core protocol never changes
|
|
415
|
+
2. **Easy Testing**: Can run multiple instances locally
|
|
416
|
+
3. **No Breaking Changes**: OpenClaw integration continues working unchanged
|
|
417
|
+
4. **Extensible**: Add new platforms by implementing notification backend
|
|
418
|
+
5. **Federation is P2P**: Each instance is independent, regardless of platform
|
|
419
|
+
6. **Router Analogy Holds**: Like routers running BGP regardless of vendor
|
|
420
|
+
|
|
421
|
+
## Security Considerations
|
|
422
|
+
|
|
423
|
+
### Running Multiple Local Instances
|
|
424
|
+
|
|
425
|
+
When running multiple OGP instances on the same machine:
|
|
426
|
+
- Each instance has its own keypair (different identities)
|
|
427
|
+
- Localhost federation is cryptographically identical to remote federation
|
|
428
|
+
- Useful for testing but not a security boundary
|
|
429
|
+
- Each instance can have different scope policies
|
|
430
|
+
|
|
431
|
+
### Production Deployment
|
|
432
|
+
|
|
433
|
+
For remote federation:
|
|
434
|
+
- Always use HTTPS tunnels (cloudflared, ngrok)
|
|
435
|
+
- Verify peer identity out-of-band before approval
|
|
436
|
+
- Use scope negotiation to limit what peers can access
|
|
437
|
+
- Monitor peer activity via agent-comms logs
|
|
438
|
+
|
|
439
|
+
## Next Steps
|
|
440
|
+
|
|
441
|
+
1. **Immediate:** Implement Hermes notification backend in `notify.ts`
|
|
442
|
+
2. **Week 1:** Test local multi-instance federation
|
|
443
|
+
3. **Week 2:** Document Hermes setup for OGP users
|
|
444
|
+
4. **Month 1:** Begin native Hermes platform adapter
|
|
445
|
+
5. **Month 2:** Interoperability testing
|
|
446
|
+
6. **Month 3:** Protocol specification v1.0
|
|
447
|
+
|
|
448
|
+
## Questions & Answers
|
|
449
|
+
|
|
450
|
+
**Q: Will adding Hermes support break OpenClaw integration?**
|
|
451
|
+
A: No. The changes are additive (new notification backend). OpenClaw continues using the existing backend.
|
|
452
|
+
|
|
453
|
+
**Q: Can I federate OpenClaw and Hermes on the same machine?**
|
|
454
|
+
A: Yes! Run two OGP instances with different ports and state directories.
|
|
455
|
+
|
|
456
|
+
**Q: Do I need to run separate OGP daemons for different platforms?**
|
|
457
|
+
A: Yes, in the multi-instance model. Each agent instance gets its own OGP daemon. This keeps federation truly P2P.
|
|
458
|
+
|
|
459
|
+
**Q: What if I want one OGP daemon to notify multiple platforms?**
|
|
460
|
+
A: Not recommended. The clean architecture is one daemon per agent. Multi-platform notification adds complexity and breaks the P2P model.
|
|
461
|
+
|
|
462
|
+
**Q: How does this scale to future platforms (Anthropic Claude, OpenHands, etc.)?**
|
|
463
|
+
A: Add a new notification backend class. The core protocol stays unchanged.
|
|
464
|
+
|
|
465
|
+
**Q: Can the native Hermes adapter replace the sidecar?**
|
|
466
|
+
A: Eventually, yes. The native adapter implements the full OGP protocol in Python, eliminating the need for the Node.js daemon.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
**Last Updated:** 2026-04-04
|
|
471
|
+
**OGP Version:** 0.2.31
|
|
472
|
+
**Status:** Design Document
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dp-pcs/ogp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Open Gateway Protocol (OGP) - Peer-to-peer federation daemon for OpenClaw AI gateways with cryptographic signatures",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc",
|
|
14
14
|
"dev": "tsc --watch",
|
|
15
|
-
"prepublishOnly": "npm run build"
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"postinstall": "node scripts/install-skills.js || echo 'Note: Run ogp-install-skills to install OGP skills for your AI agent'"
|
|
16
17
|
},
|
|
17
18
|
"keywords": [
|
|
18
19
|
"ogp",
|
|
@@ -5,10 +5,50 @@ import { homedir } from 'node:os';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { dirname } from 'node:path';
|
|
7
7
|
import { createInterface } from 'node:readline';
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
8
9
|
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const skillsSrc = join(__dirname, '..', 'skills');
|
|
11
|
-
|
|
12
|
+
|
|
13
|
+
// Detect which AI platform(s) are installed
|
|
14
|
+
function detectPlatforms() {
|
|
15
|
+
const platforms = [];
|
|
16
|
+
|
|
17
|
+
// Check for OpenClaw
|
|
18
|
+
if (existsSync(join(homedir(), '.openclaw')) || existsSync(join(homedir(), '.claw'))) {
|
|
19
|
+
platforms.push({
|
|
20
|
+
name: 'OpenClaw',
|
|
21
|
+
skillsDir: join(homedir(), '.openclaw', 'skills'),
|
|
22
|
+
check: () => existsSync(join(homedir(), '.openclaw')) || existsSync(join(homedir(), '.claw'))
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check for Hermes
|
|
27
|
+
if (existsSync(join(homedir(), '.hermes'))) {
|
|
28
|
+
platforms.push({
|
|
29
|
+
name: 'Hermes',
|
|
30
|
+
skillsDir: join(homedir(), '.claude', 'skills'),
|
|
31
|
+
check: () => existsSync(join(homedir(), '.hermes'))
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check for Claude Code (generic)
|
|
36
|
+
if (existsSync(join(homedir(), '.claude', 'skills'))) {
|
|
37
|
+
platforms.push({
|
|
38
|
+
name: 'Claude Code',
|
|
39
|
+
skillsDir: join(homedir(), '.claude', 'skills'),
|
|
40
|
+
check: () => existsSync(join(homedir(), '.claude'))
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Remove duplicates by skillsDir path
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
return platforms.filter(p => {
|
|
47
|
+
if (seen.has(p.skillsDir)) return false;
|
|
48
|
+
seen.add(p.skillsDir);
|
|
49
|
+
return true;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
12
52
|
|
|
13
53
|
// Auto-discover all skill folders
|
|
14
54
|
const availableSkills = readdirSync(skillsSrc, { withFileTypes: true })
|
|
@@ -67,33 +107,115 @@ async function selectSkills() {
|
|
|
67
107
|
return indices.map(i => availableSkills[i]);
|
|
68
108
|
}
|
|
69
109
|
|
|
70
|
-
async function
|
|
71
|
-
|
|
110
|
+
async function selectPlatforms(platforms) {
|
|
111
|
+
if (!isInteractive || platforms.length === 1) {
|
|
112
|
+
// Non-interactive or only one platform: install to all
|
|
113
|
+
return platforms;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log('\n🤖 Detected AI Platforms:\n');
|
|
117
|
+
platforms.forEach((p, i) => {
|
|
118
|
+
console.log(` ${i + 1}. ${p.name} (${p.skillsDir})`);
|
|
119
|
+
});
|
|
120
|
+
console.log(` ${platforms.length + 1}. Install to all`);
|
|
121
|
+
console.log(` 0. Cancel\n`);
|
|
122
|
+
|
|
123
|
+
const answer = await prompt(`Select platforms to install to (e.g. "1 2" or "${platforms.length + 1}" for all): `);
|
|
124
|
+
|
|
125
|
+
if (!answer || answer === '0') {
|
|
126
|
+
console.log('Cancelled.');
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
72
129
|
|
|
73
|
-
if (
|
|
74
|
-
|
|
130
|
+
if (answer === String(platforms.length + 1) || answer.toLowerCase() === 'all') {
|
|
131
|
+
return platforms;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const indices = answer.split(/[\s,]+/).map(n => parseInt(n) - 1).filter(i => i >= 0 && i < platforms.length);
|
|
135
|
+
if (indices.length === 0) {
|
|
136
|
+
console.log('No valid selection. Installing to all platforms.');
|
|
137
|
+
return platforms;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return indices.map(i => platforms[i]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function main() {
|
|
144
|
+
const detectedPlatforms = detectPlatforms();
|
|
145
|
+
|
|
146
|
+
if (detectedPlatforms.length === 0) {
|
|
147
|
+
console.log('\n⚠️ No supported AI platforms detected.');
|
|
148
|
+
console.log('');
|
|
149
|
+
console.log('OGP skills can be installed to:');
|
|
150
|
+
console.log(' - OpenClaw: ~/.openclaw/skills/');
|
|
151
|
+
console.log(' - Hermes: ~/.claude/skills/');
|
|
152
|
+
console.log(' - Claude Code: ~/.claude/skills/');
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log('To install manually:');
|
|
155
|
+
console.log(' cp -r $(npm root -g)/@dp-pcs/ogp/skills/* ~/.openclaw/skills/');
|
|
156
|
+
console.log(' # or');
|
|
157
|
+
console.log(' cp -r $(npm root -g)/@dp-pcs/ogp/skills/* ~/.claude/skills/');
|
|
158
|
+
process.exit(1);
|
|
75
159
|
}
|
|
76
160
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
console.
|
|
161
|
+
const selectedSkills = await selectSkills();
|
|
162
|
+
const selectedPlatforms = await selectPlatforms(detectedPlatforms);
|
|
163
|
+
|
|
164
|
+
let totalInstalled = 0;
|
|
165
|
+
|
|
166
|
+
for (const platform of selectedPlatforms) {
|
|
167
|
+
console.log(`\n📥 Installing to ${platform.name} (${platform.skillsDir})...`);
|
|
168
|
+
|
|
169
|
+
if (!existsSync(platform.skillsDir)) {
|
|
170
|
+
mkdirSync(platform.skillsDir, { recursive: true });
|
|
171
|
+
console.log(` Created directory: ${platform.skillsDir}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let installed = 0;
|
|
175
|
+
for (const skill of selectedSkills) {
|
|
176
|
+
const src = join(skillsSrc, skill);
|
|
177
|
+
const dest = join(platform.skillsDir, skill);
|
|
178
|
+
try {
|
|
179
|
+
cpSync(src, dest, { recursive: true });
|
|
180
|
+
console.log(` ✓ ${skill}`);
|
|
181
|
+
installed++;
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.warn(` ✗ ${skill}: ${err.message}`);
|
|
184
|
+
}
|
|
88
185
|
}
|
|
186
|
+
|
|
187
|
+
console.log(` ${installed}/${selectedSkills.length} skills installed`);
|
|
188
|
+
totalInstalled += installed;
|
|
89
189
|
}
|
|
90
190
|
|
|
91
|
-
if (
|
|
92
|
-
console.log(`\n${
|
|
191
|
+
if (totalInstalled > 0) {
|
|
192
|
+
console.log(`\n✅ Successfully installed ${totalInstalled} skill(s) to ${selectedPlatforms.length} platform(s)`);
|
|
193
|
+
console.log('');
|
|
194
|
+
|
|
195
|
+
// Platform-specific restart instructions
|
|
196
|
+
const platformNames = selectedPlatforms.map(p => p.name);
|
|
197
|
+
if (platformNames.includes('OpenClaw')) {
|
|
198
|
+
console.log('OpenClaw: Restart your gateway to load the skills');
|
|
199
|
+
console.log(' openclaw restart');
|
|
200
|
+
}
|
|
201
|
+
if (platformNames.includes('Hermes')) {
|
|
202
|
+
console.log('Hermes: Skills are loaded automatically');
|
|
203
|
+
console.log(' hermes skills list (to verify)');
|
|
204
|
+
}
|
|
205
|
+
if (platformNames.includes('Claude Code')) {
|
|
206
|
+
console.log('Claude Code: Restart Claude Code to load the skills');
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
console.log('\n⚠️ No skills were installed');
|
|
93
210
|
}
|
|
94
211
|
}
|
|
95
212
|
|
|
96
213
|
main().catch(err => {
|
|
97
|
-
console.warn('Install failed:', err.message);
|
|
98
|
-
console.warn('
|
|
214
|
+
console.warn('\n❌ Install failed:', err.message);
|
|
215
|
+
console.warn('');
|
|
216
|
+
console.warn('Manual install:');
|
|
217
|
+
console.warn(' cp -r $(npm root -g)/@dp-pcs/ogp/skills/* ~/.openclaw/skills/');
|
|
218
|
+
console.warn(' # or');
|
|
219
|
+
console.warn(' cp -r $(npm root -g)/@dp-pcs/ogp/skills/* ~/.claude/skills/');
|
|
220
|
+
process.exit(1);
|
|
99
221
|
});
|