@perkos/perkos-a2a 0.8.35 → 0.9.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 +99 -21
- package/dist/agent.d.ts +14 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +363 -0
- package/dist/agent.js.map +1 -0
- package/dist/hermes-cli.d.ts +30 -0
- package/dist/hermes-cli.d.ts.map +1 -0
- package/dist/hermes-cli.js +120 -0
- package/dist/hermes-cli.js.map +1 -0
- package/dist/hermes-plugin.d.ts +91 -0
- package/dist/hermes-plugin.d.ts.map +1 -0
- package/dist/hermes-plugin.js +196 -0
- package/dist/hermes-plugin.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1022 -26
- package/dist/index.js.map +4 -4
- package/dist/types.d.ts +36 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -1
- package/dist/types.js.map +1 -1
- package/docs/chat-client.md +190 -0
- package/docs/demo-setup.md +273 -0
- package/openclaw.plugin.json +38 -1
- package/package.json +33 -1
- package/scripts/hermes/install.mjs +211 -0
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** A2A Protocol types (v0.3.0) */
|
|
2
|
+
export type { AckFrame, AuthErrorFrame, AuthFrame, AuthOkFrame, ChannelJoinFrame, ChatConfig, ChatDeliverFrame, ChatErrorFrame, ChatFrame, ChatIdentity, ChatMessage, ChatMessageInboundFrame, ChatReplyFrame, HistoryChunkFrame, HistoryRequestFrame, PingFrame, PongFrame, TypingFrame, } from "./chat-types.js";
|
|
2
3
|
export interface AgentCard {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
|
@@ -114,6 +115,15 @@ export interface A2APluginConfig {
|
|
|
114
115
|
};
|
|
115
116
|
relay?: RelayConfig;
|
|
116
117
|
auth?: AuthConfig;
|
|
118
|
+
/**
|
|
119
|
+
* Optional PerkOS-Chat client. When `enabled`, the plugin opens a WS to
|
|
120
|
+
* `chat.perkos.xyz` alongside the A2A relay so the agent can participate
|
|
121
|
+
* in user-facing project chats and 1-on-1 DMs.
|
|
122
|
+
*
|
|
123
|
+
* Separate from the relay because chat traffic has different volume and
|
|
124
|
+
* rate-limit characteristics than task dispatch. See `src/chat-client.ts`.
|
|
125
|
+
*/
|
|
126
|
+
chat?: import("./chat-types.js").ChatConfig;
|
|
117
127
|
}
|
|
118
128
|
export type RuntimeKind = "openclaw" | "hermes" | "hermes-api" | "none";
|
|
119
129
|
export interface RuntimeDeliveryConfig {
|
|
@@ -129,7 +139,32 @@ export interface RuntimeDeliveryConfig {
|
|
|
129
139
|
hermesEndpoint?: string;
|
|
130
140
|
}
|
|
131
141
|
/** Wire protocol for relay WebSocket messages */
|
|
132
|
-
export type RelayMessageType = "register" | "register_ack" | "task" | "task_response" | "discover" | "discover_response" | "heartbeat" | "heartbeat_ack" | "error";
|
|
142
|
+
export type RelayMessageType = "register" | "register_ack" | "task" | "task_response" | "discover" | "discover_response" | "heartbeat" | "heartbeat_ack" | "webhook_event" | "error";
|
|
143
|
+
/**
|
|
144
|
+
* Payload of a `webhook_event` frame. Transport delivers these when an
|
|
145
|
+
* external system (GitHub, GitLab, Linear, generic) fires a webhook
|
|
146
|
+
* that matches one of this agent's subscriptions. Hooks-first design:
|
|
147
|
+
* the agent does not need to expose its own HTTP surface.
|
|
148
|
+
*
|
|
149
|
+
* `headers` contains only the small set of upstream headers that are
|
|
150
|
+
* useful downstream (delivery id, user-agent). `body` is the parsed
|
|
151
|
+
* JSON body, or `null` when the upstream payload wasn't JSON.
|
|
152
|
+
*/
|
|
153
|
+
export interface WebhookEventPayload {
|
|
154
|
+
subscriptionId: string;
|
|
155
|
+
source: "github" | "gitlab" | "linear" | "generic" | string;
|
|
156
|
+
eventType: string;
|
|
157
|
+
headers: {
|
|
158
|
+
delivery?: string;
|
|
159
|
+
userAgent?: string;
|
|
160
|
+
};
|
|
161
|
+
body: unknown;
|
|
162
|
+
}
|
|
163
|
+
/** Convenience helper: narrows a RelayMessage to a webhook_event frame. */
|
|
164
|
+
export declare function isWebhookEvent(msg: RelayMessage): msg is RelayMessage & {
|
|
165
|
+
type: "webhook_event";
|
|
166
|
+
payload: WebhookEventPayload;
|
|
167
|
+
};
|
|
133
168
|
export interface RelayMessage {
|
|
134
169
|
type: RelayMessageType;
|
|
135
170
|
/** Sender agent name */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,kCAAkC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,kCAAkC"}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,kCAAkC;AA6MlC,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAC5B,GAAiB;IAEjB,OAAO,GAAG,CAAC,IAAI,KAAK,eAAe,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# PerkOS-Chat client
|
|
2
|
+
|
|
3
|
+
The A2A package ships with a `ChatClient` that connects to PerkOS-Chat
|
|
4
|
+
(`wss://chat.perkos.xyz/chat`) alongside the existing A2A relay. Tasks and
|
|
5
|
+
chat travel on separate sockets — different rate limits, different
|
|
6
|
+
semantics — but both use the same agent identity and the same relay API key
|
|
7
|
+
issued during PerkOS-Transport pairing.
|
|
8
|
+
|
|
9
|
+
Server reference: [PerkOS-Chat](https://github.com/PerkOS-xyz/PerkOS-Chat)
|
|
10
|
+
and `docs/protocol.md` therein.
|
|
11
|
+
|
|
12
|
+
## Privacy model (C-hybrid)
|
|
13
|
+
|
|
14
|
+
The agent owns the canonical conversation history.
|
|
15
|
+
|
|
16
|
+
- **PerkOS cloud (Firestore)** — metadata only: title, participants,
|
|
17
|
+
`historyHost`, `lastMessageAt`. No bodies, no previews.
|
|
18
|
+
- **This client** — receives every frame addressed to the agent and appends
|
|
19
|
+
it to `<storeRoot>/<convId>/messages.jsonl`. The store is **the** record
|
|
20
|
+
of conversation content for this agent.
|
|
21
|
+
- **History queries** — when a user scrolls back in the MiniApp, the chat
|
|
22
|
+
server forwards the request to the `historyHost` agent. The client
|
|
23
|
+
paginates from the JSONL and responds.
|
|
24
|
+
|
|
25
|
+
Backups are the operator's responsibility. The standard advice: rsync
|
|
26
|
+
`~/.perkos/conversations/` somewhere durable on a schedule.
|
|
27
|
+
|
|
28
|
+
## Wiring it in — OpenClaw
|
|
29
|
+
|
|
30
|
+
The A2A plugin already wires chat for you when you set `chat.enabled: true`
|
|
31
|
+
in your `openclaw.plugin.json` config:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"perkos-a2a": {
|
|
36
|
+
"config": {
|
|
37
|
+
"agentName": "apollo",
|
|
38
|
+
"relay": {
|
|
39
|
+
"enabled": true,
|
|
40
|
+
"url": "wss://transport.perkos.xyz/a2a",
|
|
41
|
+
"apiKey": "<your relay key>"
|
|
42
|
+
},
|
|
43
|
+
"chat": {
|
|
44
|
+
"enabled": true,
|
|
45
|
+
"url": "wss://chat.perkos.xyz/chat"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`chat.apiKey` defaults to `relay.apiKey` — one key, two sockets. When an
|
|
53
|
+
inbound message arrives, the plugin enqueues a system event with a marker
|
|
54
|
+
`[PERKOS_CHAT:<convId>]`, wakes the runtime, and the LLM should call the
|
|
55
|
+
`perkos_chat_reply` tool to respond.
|
|
56
|
+
|
|
57
|
+
Two tools are added to the agent's tool surface:
|
|
58
|
+
|
|
59
|
+
- `perkos_chat_reply({ convId, walletAddress, text, replyTo? })`
|
|
60
|
+
- `perkos_chat_history({ convId, before?, limit? })`
|
|
61
|
+
|
|
62
|
+
Run `openclaw perkos-a2a status` (or hit the gateway method
|
|
63
|
+
`perkos-a2a.status`) to verify the chat client is connected.
|
|
64
|
+
|
|
65
|
+
## Wiring it in — Hermes (via `perkos-a2a-agent` bridge)
|
|
66
|
+
|
|
67
|
+
The standalone bridge binary (`bin/agent.ts`, shipped as `perkos-a2a-agent`)
|
|
68
|
+
multiplexes A2A tasks **and** chat in one process. Run it next to Hermes
|
|
69
|
+
on the same VPS:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
A2A_AGENT_NAME=apollo \
|
|
73
|
+
A2A_RUNTIME=hermes-api \
|
|
74
|
+
HERMES_API_URL=http://127.0.0.1:8642 \
|
|
75
|
+
A2A_RELAY_URL=wss://transport.perkos.xyz/a2a \
|
|
76
|
+
A2A_RELAY_API_KEY=<your relay key> \
|
|
77
|
+
A2A_CHAT_ENABLED=true \
|
|
78
|
+
A2A_CHAT_URL=wss://chat.perkos.xyz/chat \
|
|
79
|
+
A2A_CHAT_API_KEY=<your relay key> # defaults to A2A_RELAY_API_KEY \
|
|
80
|
+
A2A_CHAT_REPLY_PORT=5060 \
|
|
81
|
+
perkos-a2a-agent
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Inbound flow:
|
|
85
|
+
|
|
86
|
+
1. `chat.perkos.xyz` sends `chat_deliver` over WS to the bridge.
|
|
87
|
+
2. Bridge appends to local JSONL **and** POSTs the message to Hermes at
|
|
88
|
+
`POST $HERMES_API_URL/v1/responses` with a `[PERKOS_CHAT:<convId>]`
|
|
89
|
+
marker and explicit instructions: *"reply via POST to
|
|
90
|
+
http://127.0.0.1:$A2A_CHAT_REPLY_PORT/chat/reply"*.
|
|
91
|
+
3. Hermes (via a tool you configure on the Hermes side — `http_request`
|
|
92
|
+
or a custom plugin) POSTs the reply to:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
POST http://127.0.0.1:5060/chat/reply
|
|
96
|
+
{ "convId": "...", "walletAddress": "0x...", "text": "...", "replyTo": "..." }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
4. The bridge calls `chatClient.sendReply(...)` which appends to the
|
|
100
|
+
JSONL log and broadcasts to other conversation participants via
|
|
101
|
+
`chat.perkos.xyz`.
|
|
102
|
+
|
|
103
|
+
The reply listener is bound to `127.0.0.1` only — never expose it to the
|
|
104
|
+
public internet. The relay API key never leaves the bridge.
|
|
105
|
+
|
|
106
|
+
If Hermes is offline or returns 503, the reply is still appended to the
|
|
107
|
+
local JSONL store, so reconciliation on reconnect is possible later.
|
|
108
|
+
|
|
109
|
+
## Wiring it in — programmatic
|
|
110
|
+
|
|
111
|
+
If you're not in an OpenClaw plugin context (e.g. a Hermes bridge), use the
|
|
112
|
+
class directly:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { ChatClient } from "@perkos/perkos-a2a";
|
|
116
|
+
|
|
117
|
+
const chat = new ChatClient({
|
|
118
|
+
agentName: "apollo",
|
|
119
|
+
config: {
|
|
120
|
+
enabled: true,
|
|
121
|
+
url: "wss://chat.perkos.xyz/chat",
|
|
122
|
+
apiKey: process.env.PERKOS_RELAY_API_KEY!,
|
|
123
|
+
// storeRoot defaults to ~/.perkos/conversations
|
|
124
|
+
},
|
|
125
|
+
handlers: {
|
|
126
|
+
onChatDeliver: async (frame) => {
|
|
127
|
+
// Hand the message to the agent runtime (Hermes /v1/responses, custom
|
|
128
|
+
// LLM loop, …). When the runtime produces a reply, call sendReply.
|
|
129
|
+
},
|
|
130
|
+
onChannelJoin: async (frame) => {
|
|
131
|
+
// A new conv exists. The store already has metadata written; this
|
|
132
|
+
// hook is for runtime-side wiring (subscribe a memory module, etc.).
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
chat.start();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
To respond:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
await chat.sendReply({
|
|
144
|
+
convId: "c-1234",
|
|
145
|
+
walletAddress: "0xabc...", // user wallet that owns the conv tree
|
|
146
|
+
text: "Aquí tienes el resumen.",
|
|
147
|
+
replyTo: "u-1234", // optional id of the user's message
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The reply is appended to the local JSONL before being sent on the wire, so
|
|
152
|
+
it shows up in history even if delivery fails.
|
|
153
|
+
|
|
154
|
+
## Storage layout
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
<storeRoot>/
|
|
158
|
+
<convId>/
|
|
159
|
+
metadata.json ConversationMetadata (participants, historyHost, ...)
|
|
160
|
+
messages.jsonl Append-only, one JSON object per line
|
|
161
|
+
attachments/ Reserved for future use
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`messages.jsonl` is **append-only**. Each line is `JSON.stringify(msg)`
|
|
165
|
+
where `msg: ChatMessage`. Out-of-order timestamps are preserved as written;
|
|
166
|
+
pagination uses `timestamp < before` as the filter.
|
|
167
|
+
|
|
168
|
+
## Pagination
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const store = chat.getStore();
|
|
172
|
+
const { messages, hasMore } = await store.readPage("c-1234", {
|
|
173
|
+
before: "2026-05-19T18:00:00.000Z",
|
|
174
|
+
limit: 50,
|
|
175
|
+
});
|
|
176
|
+
// messages: chronological ascending (oldest of the page first)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`readPage` reads the whole jsonl into memory and walks backward — fine for
|
|
180
|
+
conversations up to ~tens of thousands of messages. If you anticipate
|
|
181
|
+
larger logs, swap the implementation for a streaming reverse-iterator
|
|
182
|
+
(left as a future task).
|
|
183
|
+
|
|
184
|
+
## Reconnect, heartbeat, rate limit
|
|
185
|
+
|
|
186
|
+
- Exponential backoff: 1s → 60s.
|
|
187
|
+
- Heartbeat: a `ping` frame every 25s (the server's default reap timeout is
|
|
188
|
+
90s).
|
|
189
|
+
- The server rate-limits each agent socket to 600 messages/min by default;
|
|
190
|
+
history responses and acks are counted.
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# PerkOS multi-agent demo — provisioning playbook
|
|
2
|
+
|
|
3
|
+
End-to-end recipe for standing up the three-agent demo on a Hetzner VPS
|
|
4
|
+
(or any Linux host with Docker, Caddy, and outbound HTTPS). Read it
|
|
5
|
+
once, then `scripts/demo/` does most of the work.
|
|
6
|
+
|
|
7
|
+
## Topology
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌──── PerkOS-Transport (transport.perkos.xyz) ────┐
|
|
11
|
+
│ tasks routing, pairing registry │
|
|
12
|
+
└──────────────────────────────────────────────────┘
|
|
13
|
+
▲ ▲
|
|
14
|
+
│ wss /a2a │ wss /a2a
|
|
15
|
+
│ │
|
|
16
|
+
┌──── PerkOS-Chat (chat.perkos.xyz) ────┐ │
|
|
17
|
+
│ chat routing, Firebase metadata write │ │
|
|
18
|
+
└────────────────────────────────────────┘ │
|
|
19
|
+
▲ │
|
|
20
|
+
│ wss /chat (user) │ wss /chat
|
|
21
|
+
│ │ (agent)
|
|
22
|
+
browser │
|
|
23
|
+
│
|
|
24
|
+
┌────────────────────────────────────────┴────────┐
|
|
25
|
+
│ Apollo (orchestrator, Hermes runtime) │
|
|
26
|
+
│ Builder (worker, OpenClaw) │
|
|
27
|
+
│ QA (worker, OpenClaw) │
|
|
28
|
+
│ each: perkos-a2a-agent → Transport + Chat WS │
|
|
29
|
+
└─────────────────────────────────────────────────-┘
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For the demo we use a **single VPS** with all three agents running as
|
|
33
|
+
separate `perkos-a2a-agent` processes (different ports, different
|
|
34
|
+
config dirs). Apollo runs **Hermes** as its runtime — the others run
|
|
35
|
+
**OpenClaw**.
|
|
36
|
+
|
|
37
|
+
## Prerequisites on the VPS
|
|
38
|
+
|
|
39
|
+
1. Docker + docker compose
|
|
40
|
+
2. Caddy already serving `transport.perkos.xyz` and `chat.perkos.xyz`
|
|
41
|
+
3. Hermes Agent installed and reachable on `http://127.0.0.1:8642`
|
|
42
|
+
(the Apollo orchestrator) — see [hermes-workspace install
|
|
43
|
+
guide](https://github.com/NousResearch/hermes-agent)
|
|
44
|
+
4. OpenClaw installed (the worker agents) — see [OpenClaw
|
|
45
|
+
docs](https://openclaw.dev) — they don't need a separate HTTP
|
|
46
|
+
server; the A2A plugin handles networking
|
|
47
|
+
5. Node.js 22+ on the host (or use the published
|
|
48
|
+
`@perkos/perkos-a2a` npm package)
|
|
49
|
+
6. The Transport admin key (`PAIRING_ADMIN_KEY` from Transport's
|
|
50
|
+
`.env`) — needed to mint invites
|
|
51
|
+
7. SSH access as a non-root user with sudo
|
|
52
|
+
|
|
53
|
+
## Step 0 — Decide identities
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
ORG_SYSTEM=perkos-demo
|
|
57
|
+
PROJECT_ID=demo-q4
|
|
58
|
+
|
|
59
|
+
# Three agents, one orchestrator + two workers
|
|
60
|
+
APOLLO_NAME=apollo
|
|
61
|
+
BUILDER_NAME=builder
|
|
62
|
+
QA_NAME=qa
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Step 1 — Mint Transport pairing invites
|
|
66
|
+
|
|
67
|
+
For each agent, ask Transport for an invite. The wrapper script in
|
|
68
|
+
`scripts/demo/mint-pairing.sh` does it; or call the HTTP API directly:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
export TRANSPORT_BASE=https://transport.perkos.xyz
|
|
72
|
+
export TRANSPORT_ADMIN_KEY=<from your Transport .env>
|
|
73
|
+
|
|
74
|
+
for agent in apollo builder qa; do
|
|
75
|
+
echo "=== minting invite for $agent ==="
|
|
76
|
+
curl -fsS -X POST "$TRANSPORT_BASE/api/systems/perkos-demo/agents/invites" \
|
|
77
|
+
-H "x-api-key: $TRANSPORT_ADMIN_KEY" \
|
|
78
|
+
-H "content-type: application/json" \
|
|
79
|
+
-d "{
|
|
80
|
+
\"agentName\": \"$agent\",
|
|
81
|
+
\"runtime\": \"$( [[ $agent == apollo ]] && echo hermes-api || echo openclaw )\",
|
|
82
|
+
\"scopes\": [\"a2a:connect\", \"tasks:receive\", \"messages:send\", \"chat:send\"]
|
|
83
|
+
}" | tee /tmp/invite-$agent.json
|
|
84
|
+
done
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Each invite response looks like:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"inviteId": "inv_…",
|
|
92
|
+
"pairingUrl": "https://transport.perkos.xyz/pairing/invites/inv_…",
|
|
93
|
+
"expiresAt": "2026-05-20T..."
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Keep them — the agent uses the URL to claim and the operator (you)
|
|
98
|
+
approves.
|
|
99
|
+
|
|
100
|
+
## Step 2 — Claim and approve
|
|
101
|
+
|
|
102
|
+
The pairing flow is two-step: the agent claims with a public key, then
|
|
103
|
+
admin approves to issue the scoped `relayApiKey`.
|
|
104
|
+
|
|
105
|
+
For the demo we **auto-approve** by passing the admin key on the claim
|
|
106
|
+
step. `scripts/demo/claim-and-approve.sh` wraps this:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
scripts/demo/claim-and-approve.sh apollo /tmp/invite-apollo.json
|
|
110
|
+
scripts/demo/claim-and-approve.sh builder /tmp/invite-builder.json
|
|
111
|
+
scripts/demo/claim-and-approve.sh qa /tmp/invite-qa.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Each call prints the `relayApiKey` for that agent. Save them:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
APOLLO_KEY=…
|
|
118
|
+
BUILDER_KEY=…
|
|
119
|
+
QA_KEY=…
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Step 3 — Update chat.perkos.xyz registry
|
|
123
|
+
|
|
124
|
+
Until the production bridge between Transport's pairing registry and
|
|
125
|
+
Chat's agent-key store lands, chat.perkos.xyz reads agent keys from
|
|
126
|
+
its `RELAY_API_KEYS` env var. SSH to the chat host and add the new
|
|
127
|
+
keys:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
ssh hetzner
|
|
131
|
+
sudo nano /opt/PerkOS-Chat/.env
|
|
132
|
+
|
|
133
|
+
# Update or extend:
|
|
134
|
+
RELAY_API_KEYS=apollo:<APOLLO_KEY>,builder:<BUILDER_KEY>,qa:<QA_KEY>
|
|
135
|
+
|
|
136
|
+
# Reload:
|
|
137
|
+
cd /opt/PerkOS-Chat
|
|
138
|
+
docker compose -f docker-compose.example.yml up -d
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Verify:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
curl https://chat.perkos.xyz/health | jq .agents
|
|
145
|
+
# expect 0 (no agents connected yet — that's fine)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Step 4 — Start Apollo (Hermes orchestrator)
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# On the Hermes host:
|
|
152
|
+
export A2A_AGENT_NAME=apollo
|
|
153
|
+
export A2A_RUNTIME=hermes-api
|
|
154
|
+
export HERMES_API_URL=http://127.0.0.1:8642
|
|
155
|
+
export A2A_RELAY_URL=wss://transport.perkos.xyz/a2a
|
|
156
|
+
export A2A_RELAY_API_KEY=<APOLLO_KEY>
|
|
157
|
+
export A2A_CHAT_ENABLED=true
|
|
158
|
+
export A2A_CHAT_URL=wss://chat.perkos.xyz/chat
|
|
159
|
+
export A2A_CHAT_API_KEY=<APOLLO_KEY>
|
|
160
|
+
export A2A_CHAT_REPLY_PORT=5060
|
|
161
|
+
export A2A_PORT=5050
|
|
162
|
+
export A2A_PEERS='{"builder":"http://127.0.0.1:5051","qa":"http://127.0.0.1:5052"}'
|
|
163
|
+
|
|
164
|
+
npx -p @perkos/perkos-a2a perkos-a2a-agent
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
A systemd unit template is in `scripts/demo/systemd/apollo.service`.
|
|
168
|
+
|
|
169
|
+
## Step 5 — Start Builder + QA (OpenClaw workers)
|
|
170
|
+
|
|
171
|
+
OpenClaw agents load the A2A plugin natively. Drop this config into
|
|
172
|
+
each worker's OpenClaw plugins directory:
|
|
173
|
+
|
|
174
|
+
```jsonc
|
|
175
|
+
// builder.plugin.config.json
|
|
176
|
+
{
|
|
177
|
+
"perkos-a2a": {
|
|
178
|
+
"config": {
|
|
179
|
+
"agentName": "builder",
|
|
180
|
+
"port": 5051,
|
|
181
|
+
"bindHost": "127.0.0.1",
|
|
182
|
+
"mode": "client-only",
|
|
183
|
+
"peers": {
|
|
184
|
+
"apollo": "http://127.0.0.1:5050"
|
|
185
|
+
},
|
|
186
|
+
"relay": {
|
|
187
|
+
"enabled": true,
|
|
188
|
+
"url": "wss://transport.perkos.xyz/a2a",
|
|
189
|
+
"apiKey": "<BUILDER_KEY>"
|
|
190
|
+
},
|
|
191
|
+
"chat": {
|
|
192
|
+
"enabled": true,
|
|
193
|
+
"url": "wss://chat.perkos.xyz/chat",
|
|
194
|
+
"apiKey": "<BUILDER_KEY>"
|
|
195
|
+
},
|
|
196
|
+
"runtime": {
|
|
197
|
+
"kind": "openclaw"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`qa` is identical except `agentName`, `port=5052`, and `apiKey`.
|
|
205
|
+
|
|
206
|
+
Restart each OpenClaw process to pick up the plugin config.
|
|
207
|
+
|
|
208
|
+
## Step 6 — Verify all three connect
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
scripts/demo/verify.sh
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Output should look like:
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
✓ transport.perkos.xyz reachable
|
|
218
|
+
✓ chat.perkos.xyz reachable
|
|
219
|
+
✓ apollo — A2A connected, chat connected
|
|
220
|
+
✓ builder — A2A connected, chat connected
|
|
221
|
+
✓ qa — A2A connected, chat connected
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Or hit the health endpoints manually:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
curl https://chat.perkos.xyz/health | jq .agents
|
|
228
|
+
# expect 3
|
|
229
|
+
curl https://transport.perkos.xyz/health | jq .connectedAgents
|
|
230
|
+
# expect 3
|
|
231
|
+
curl "https://transport.perkos.xyz/api/agents/apollo/heartbeat" -H "x-api-key: $TRANSPORT_ADMIN_KEY" | jq
|
|
232
|
+
# expect { "ok": true, "approved": true, "connected": true }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Step 7 — Smoke from the MiniApp
|
|
236
|
+
|
|
237
|
+
Open the MiniApp, sign in with a wallet, and walk through the
|
|
238
|
+
[chat E2E checklist](https://github.com/PerkOS-xyz/PerkOS/blob/main/docs/CHAT-E2E.md).
|
|
239
|
+
|
|
240
|
+
Quickest happy-path: click **+ New**, pick "Direct message" + apollo,
|
|
241
|
+
type "hola apollo" — you should see Apollo's reply within 5 seconds
|
|
242
|
+
(Hermes processing time).
|
|
243
|
+
|
|
244
|
+
## Troubleshooting
|
|
245
|
+
|
|
246
|
+
| Symptom | First-pass diagnosis |
|
|
247
|
+
|---|---|
|
|
248
|
+
| MiniApp sidebar empty | Firestore rules deployed? — see `Perkos/firestore.rules` |
|
|
249
|
+
| `auth_error` on the chat WS | Firebase project mismatch between MiniApp and chat.perkos.xyz |
|
|
250
|
+
| Agent not connecting | Check the `RELAY_API_KEYS` value matches what claim-and-approve issued |
|
|
251
|
+
| Apollo gets the chat_deliver but no reply | Hermes is not configured to POST to `127.0.0.1:5060/chat/reply` (see `docs/chat-client.md` "Wiring it in — Hermes") |
|
|
252
|
+
| `HOST_OFFLINE` banner stays up | The host agent's WS dropped; check the agent process logs |
|
|
253
|
+
| Builder/QA never receive sub-tasks | Apollo's peer config wrong; check the JSON peers map in apollo's env |
|
|
254
|
+
|
|
255
|
+
## Decommissioning the demo
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Revoke each agent on Transport
|
|
259
|
+
for agent in apollo builder qa; do
|
|
260
|
+
curl -X POST "$TRANSPORT_BASE/pairing/agents/$agent/revoke" \
|
|
261
|
+
-H "x-api-key: $TRANSPORT_ADMIN_KEY"
|
|
262
|
+
done
|
|
263
|
+
|
|
264
|
+
# Remove from chat.perkos.xyz
|
|
265
|
+
ssh hetzner 'sudo sed -i "s/RELAY_API_KEYS=.*/RELAY_API_KEYS=/" /opt/PerkOS-Chat/.env && \
|
|
266
|
+
cd /opt/PerkOS-Chat && docker compose -f docker-compose.example.yml up -d'
|
|
267
|
+
|
|
268
|
+
# Stop the agent processes (systemd or direct)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Conversation metadata stays in Firestore. Conversation content stays
|
|
272
|
+
in each agent's `~/.perkos/conversations/` jsonl. Delete those if you
|
|
273
|
+
want a clean wipe.
|
package/openclaw.plugin.json
CHANGED
|
@@ -170,6 +170,41 @@
|
|
|
170
170
|
"pairingStorePath": {
|
|
171
171
|
"type": "string",
|
|
172
172
|
"description": "Path to a local pairing credential profile, e.g. ~/.perkos/a2a/agents/Alice.json. The plugin may load relay credentials from this store without printing secrets."
|
|
173
|
+
},
|
|
174
|
+
"chat": {
|
|
175
|
+
"type": "object",
|
|
176
|
+
"additionalProperties": false,
|
|
177
|
+
"properties": {
|
|
178
|
+
"enabled": {
|
|
179
|
+
"type": "boolean",
|
|
180
|
+
"default": false,
|
|
181
|
+
"description": "Connect to PerkOS-Chat for user-facing chat (DMs + channels). Independent of the A2A relay used for tasks."
|
|
182
|
+
},
|
|
183
|
+
"url": {
|
|
184
|
+
"type": "string",
|
|
185
|
+
"default": "wss://chat.perkos.xyz/chat",
|
|
186
|
+
"description": "PerkOS-Chat WebSocket URL"
|
|
187
|
+
},
|
|
188
|
+
"apiKey": {
|
|
189
|
+
"type": "string",
|
|
190
|
+
"description": "Relay API key for this agent. If omitted, falls back to relay.apiKey."
|
|
191
|
+
},
|
|
192
|
+
"storeRoot": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"description": "Directory for the conversations JSONL store. Defaults to ~/.perkos/conversations."
|
|
195
|
+
},
|
|
196
|
+
"defaultHistoryLimit": {
|
|
197
|
+
"type": "number",
|
|
198
|
+
"default": 50,
|
|
199
|
+
"description": "Max messages returned per history page (1-500)."
|
|
200
|
+
},
|
|
201
|
+
"heartbeatIntervalMs": {
|
|
202
|
+
"type": "number",
|
|
203
|
+
"default": 25000,
|
|
204
|
+
"description": "Ping cadence in ms. The server reaps after 90s of silence."
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"description": "PerkOS-Chat client config — opt-in. The agent owns conversation history on local disk; the PerkOS cloud stores metadata only."
|
|
173
208
|
}
|
|
174
209
|
}
|
|
175
210
|
},
|
|
@@ -204,7 +239,9 @@
|
|
|
204
239
|
"tools": [
|
|
205
240
|
"perkos_a2a_send",
|
|
206
241
|
"perkos_a2a_discover",
|
|
207
|
-
"perkos_a2a_status"
|
|
242
|
+
"perkos_a2a_status",
|
|
243
|
+
"perkos_chat_reply",
|
|
244
|
+
"perkos_chat_history"
|
|
208
245
|
]
|
|
209
246
|
},
|
|
210
247
|
"activation": {
|
package/package.json
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perkos/perkos-a2a",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "A2A Protocol communication plugin for OpenClaw — Agent-to-Agent protocol implementation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"perkos-a2a-agent": "./dist/agent.js",
|
|
10
|
+
"perkos-a2a-hermes": "./dist/hermes-cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./hermes": {
|
|
18
|
+
"types": "./dist/hermes-plugin.d.ts",
|
|
19
|
+
"default": "./dist/hermes-plugin.js"
|
|
20
|
+
},
|
|
21
|
+
"./types": {
|
|
22
|
+
"types": "./dist/types.d.ts",
|
|
23
|
+
"default": "./dist/types.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
8
26
|
"files": [
|
|
9
27
|
"dist/index.js",
|
|
10
28
|
"dist/index.js.map",
|
|
@@ -26,6 +44,20 @@
|
|
|
26
44
|
"dist/runtime-reply.d.ts.map",
|
|
27
45
|
"dist/agentic-actions.d.ts",
|
|
28
46
|
"dist/agentic-actions.d.ts.map",
|
|
47
|
+
"dist/agent.js",
|
|
48
|
+
"dist/agent.js.map",
|
|
49
|
+
"dist/agent.d.ts",
|
|
50
|
+
"dist/agent.d.ts.map",
|
|
51
|
+
"dist/hermes-cli.js",
|
|
52
|
+
"dist/hermes-cli.js.map",
|
|
53
|
+
"dist/hermes-cli.d.ts",
|
|
54
|
+
"dist/hermes-cli.d.ts.map",
|
|
55
|
+
"dist/hermes-plugin.js",
|
|
56
|
+
"dist/hermes-plugin.js.map",
|
|
57
|
+
"dist/hermes-plugin.d.ts",
|
|
58
|
+
"dist/hermes-plugin.d.ts.map",
|
|
59
|
+
"scripts/install-hermes.mjs",
|
|
60
|
+
"scripts/hermes",
|
|
29
61
|
"docs",
|
|
30
62
|
"skills",
|
|
31
63
|
"openclaw.plugin.json",
|