@vainplex/nats-eventstore 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/LICENSE +21 -0
- package/README.md +273 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/src/config.d.ts +19 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +37 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/events.d.ts +18 -0
- package/dist/src/events.d.ts.map +1 -0
- package/dist/src/events.js +21 -0
- package/dist/src/events.js.map +1 -0
- package/dist/src/hooks.d.ts +14 -0
- package/dist/src/hooks.d.ts.map +1 -0
- package/dist/src/hooks.js +239 -0
- package/dist/src/hooks.js.map +1 -0
- package/dist/src/nats-client.d.ts +33 -0
- package/dist/src/nats-client.d.ts.map +1 -0
- package/dist/src/nats-client.js +151 -0
- package/dist/src/nats-client.js.map +1 -0
- package/dist/src/service.d.ts +18 -0
- package/dist/src/service.d.ts.map +1 -0
- package/dist/src/service.js +33 -0
- package/dist/src/service.js.map +1 -0
- package/dist/src/util.d.ts +17 -0
- package/dist/src/util.d.ts.map +1 -0
- package/dist/src/util.js +24 -0
- package/dist/src/util.js.map +1 -0
- package/openclaw.plugin.json +81 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vainplex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# @vainplex/openclaw-nats-eventstore
|
|
2
|
+
|
|
3
|
+
OpenClaw plugin that publishes agent events to **NATS JetStream** for audit, replay, and multi-agent sharing.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔄 **17 event types** — messages, tool calls, LLM I/O, sessions, gateway lifecycle
|
|
8
|
+
- 🛡️ **Non-fatal** — event store failures never affect agent operations
|
|
9
|
+
- 🔐 **Privacy-conscious** — LLM events log metadata (lengths, counts), not content
|
|
10
|
+
- ⚡ **Fire-and-forget** — async publish with automatic error handling
|
|
11
|
+
- 🔧 **Configurable** — include/exclude hooks, retention policies, custom subjects
|
|
12
|
+
- 📡 **Auto-reconnect** — built-in NATS reconnection with status monitoring
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### 1. Install NATS Server
|
|
17
|
+
|
|
18
|
+
**Docker (recommended):**
|
|
19
|
+
```bash
|
|
20
|
+
docker run -d --name nats \
|
|
21
|
+
-p 4222:4222 -p 8222:8222 \
|
|
22
|
+
nats:latest -js -m 8222
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**macOS:** `brew install nats-server && nats-server -js`
|
|
26
|
+
|
|
27
|
+
**Linux:**
|
|
28
|
+
```bash
|
|
29
|
+
curl -sf https://binaries.nats.dev/nats-io/nats-server/v2@latest | sh
|
|
30
|
+
nats-server -js
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The `-js` flag enables JetStream (required for event persistence).
|
|
34
|
+
|
|
35
|
+
Verify it's running: `curl http://localhost:8222/healthz` → `{"status":"ok"}`
|
|
36
|
+
|
|
37
|
+
### 2. Install the Plugin
|
|
38
|
+
|
|
39
|
+
Run from your **OpenClaw extensions directory** (`~/.openclaw/extensions/`):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd ~/.openclaw/extensions
|
|
43
|
+
npm install @vainplex/openclaw-nats-eventstore
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This installs the plugin where OpenClaw can discover it. Alternatively, create a symlink:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
ln -s /path/to/node_modules/@vainplex/nats-eventstore ~/.openclaw/extensions/nats-eventstore
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Configure & Restart
|
|
53
|
+
|
|
54
|
+
Add the plugin to your `openclaw.json` (see Configuration below), then restart the gateway.
|
|
55
|
+
|
|
56
|
+
### 4. Verify
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Install NATS CLI (optional, for debugging)
|
|
60
|
+
# brew install nats-io/nats-tools/nats OR go install github.com/nats-io/natscli/nats@latest
|
|
61
|
+
|
|
62
|
+
nats sub "openclaw.events.>"
|
|
63
|
+
# Send a message to your agent — you should see events flowing
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
Add to the `plugins.entries` section of your `openclaw.json`:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"plugins": {
|
|
73
|
+
"entries": {
|
|
74
|
+
"nats-eventstore": {
|
|
75
|
+
"enabled": true,
|
|
76
|
+
"config": {
|
|
77
|
+
"enabled": true,
|
|
78
|
+
"natsUrl": "nats://localhost:4222",
|
|
79
|
+
"streamName": "openclaw-events",
|
|
80
|
+
"subjectPrefix": "openclaw.events",
|
|
81
|
+
"retention": {
|
|
82
|
+
"maxMessages": -1,
|
|
83
|
+
"maxBytes": -1,
|
|
84
|
+
"maxAgeHours": 720
|
|
85
|
+
},
|
|
86
|
+
"publishTimeoutMs": 5000,
|
|
87
|
+
"connectTimeoutMs": 5000,
|
|
88
|
+
"drainTimeoutMs": 5000,
|
|
89
|
+
"excludeHooks": ["message_sending"]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Config Options
|
|
98
|
+
|
|
99
|
+
| Option | Type | Default | Description |
|
|
100
|
+
|--------|------|---------|-------------|
|
|
101
|
+
| `enabled` | boolean | `true` | Enable/disable event publishing |
|
|
102
|
+
| `natsUrl` | string | `nats://localhost:4222` | NATS server URL (supports `nats://user:pass@host:port`) |
|
|
103
|
+
| `streamName` | string | `openclaw-events` | JetStream stream name |
|
|
104
|
+
| `subjectPrefix` | string | `openclaw.events` | Subject prefix for all events |
|
|
105
|
+
| `retention.maxMessages` | integer | `-1` | Max messages to retain (-1 = unlimited) |
|
|
106
|
+
| `retention.maxBytes` | integer | `-1` | Max bytes to retain (-1 = unlimited) |
|
|
107
|
+
| `retention.maxAgeHours` | number | `0` | Max age in hours (0 = unlimited) |
|
|
108
|
+
| `publishTimeoutMs` | integer | `5000` | Timeout for publish operations |
|
|
109
|
+
| `connectTimeoutMs` | integer | `5000` | Timeout for initial connection |
|
|
110
|
+
| `drainTimeoutMs` | integer | `5000` | Timeout for graceful drain |
|
|
111
|
+
| `includeHooks` | string[] | `[]` | Whitelist of hooks to publish (empty = all) |
|
|
112
|
+
| `excludeHooks` | string[] | `[]` | Blacklist of hooks to skip |
|
|
113
|
+
|
|
114
|
+
### Authentication
|
|
115
|
+
|
|
116
|
+
Include credentials in the NATS URL:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
"natsUrl": "nats://myuser:mypassword@nats.example.com:4222"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Credentials are stripped from log output automatically.
|
|
123
|
+
|
|
124
|
+
## Event Types
|
|
125
|
+
|
|
126
|
+
| OpenClaw Hook | NATS Event Type | Subject Suffix |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `message_received` | `msg.in` | `msg_in` |
|
|
129
|
+
| `message_sent` | `msg.out` | `msg_out` |
|
|
130
|
+
| `message_sending` | `msg.sending` | `msg_sending` |
|
|
131
|
+
| `before_tool_call` | `tool.call` | `tool_call` |
|
|
132
|
+
| `after_tool_call` | `tool.result` | `tool_result` |
|
|
133
|
+
| `before_agent_start` | `run.start` | `run_start` |
|
|
134
|
+
| `agent_end` | `run.end` | `run_end` |
|
|
135
|
+
| `agent_end` (failure) | `run.error` | `run_error` |
|
|
136
|
+
| `llm_input` | `llm.input` | `llm_input` |
|
|
137
|
+
| `llm_output` | `llm.output` | `llm_output` |
|
|
138
|
+
| `before_compaction` | `session.compaction_start` | `session_compaction_start` |
|
|
139
|
+
| `after_compaction` | `session.compaction_end` | `session_compaction_end` |
|
|
140
|
+
| `before_reset` | `session.reset` | `session_reset` |
|
|
141
|
+
| `session_start` | `session.start` | `session_start` |
|
|
142
|
+
| `session_end` | `session.end` | `session_end` |
|
|
143
|
+
| `gateway_start` | `gateway.start` | `gateway_start` |
|
|
144
|
+
| `gateway_stop` | `gateway.stop` | `gateway_stop` |
|
|
145
|
+
|
|
146
|
+
## NATS Subject Schema
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
{subjectPrefix}.{agentId}.{eventType}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Examples:
|
|
153
|
+
```
|
|
154
|
+
openclaw.events.main.msg_in
|
|
155
|
+
openclaw.events.viola.tool_call
|
|
156
|
+
openclaw.events.system.gateway_start
|
|
157
|
+
openclaw.events.*.msg_in # wildcard: all agents
|
|
158
|
+
openclaw.events.> # all events
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Event Envelope
|
|
162
|
+
|
|
163
|
+
Every event follows this structure:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
168
|
+
"ts": 1739734800000,
|
|
169
|
+
"agent": "main",
|
|
170
|
+
"session": "main:matrix:albert",
|
|
171
|
+
"type": "msg.in",
|
|
172
|
+
"payload": {
|
|
173
|
+
"from": "albert",
|
|
174
|
+
"content": "Hello!"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Commands
|
|
180
|
+
|
|
181
|
+
### `/eventstatus`
|
|
182
|
+
|
|
183
|
+
Shows current NATS connection status:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
NATS Event Store
|
|
187
|
+
Connected: ✅
|
|
188
|
+
Stream: openclaw-events
|
|
189
|
+
Disconnects: 0
|
|
190
|
+
Publish failures: 0
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Gateway Method
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Programmatic status check
|
|
197
|
+
const status = await gateway.call("eventstore.status");
|
|
198
|
+
// { connected: true, stream: "openclaw-events", disconnectCount: 0, publishFailures: 0 }
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Subscribing to Events
|
|
202
|
+
|
|
203
|
+
Use the NATS CLI or any NATS client to subscribe:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# All events
|
|
207
|
+
nats sub "openclaw.events.>"
|
|
208
|
+
|
|
209
|
+
# All events for a specific agent
|
|
210
|
+
nats sub "openclaw.events.main.>"
|
|
211
|
+
|
|
212
|
+
# Only message events
|
|
213
|
+
nats sub "openclaw.events.*.msg_*"
|
|
214
|
+
|
|
215
|
+
# Only tool calls
|
|
216
|
+
nats sub "openclaw.events.*.tool_call"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Migration from Core Event Store (PR #18171)
|
|
220
|
+
|
|
221
|
+
If you were using the core event store:
|
|
222
|
+
|
|
223
|
+
1. Install this plugin
|
|
224
|
+
2. Move config from `"eventStore"` to `"plugins" → "entries" → "nats-eventstore" → "config"`
|
|
225
|
+
3. Remove the `"eventStore"` section from `openclaw.json`
|
|
226
|
+
4. Restart the gateway
|
|
227
|
+
|
|
228
|
+
The plugin publishes to the **same NATS subjects and stream** — existing consumers continue working.
|
|
229
|
+
|
|
230
|
+
## Performance
|
|
231
|
+
|
|
232
|
+
Benchmark results on a single-node NATS v2.12 server (JetStream, file storage, commodity hardware):
|
|
233
|
+
|
|
234
|
+
| Test | Throughput | Latency (p99) |
|
|
235
|
+
|------|-----------|---------------|
|
|
236
|
+
| Sequential publish | ~3,800 msg/s | 0.4ms |
|
|
237
|
+
| Concurrent (10 workers) | ~9,000 msg/s | 2.9ms |
|
|
238
|
+
| Multi-subject fan-out (56 subjects) | ~9,000 msg/s | — |
|
|
239
|
+
| Consumer read | ~20,000 msg/s | — |
|
|
240
|
+
| Sustained (15s continuous) | ~3,800 msg/s | — |
|
|
241
|
+
|
|
242
|
+
**Payload scaling:**
|
|
243
|
+
|
|
244
|
+
| Payload size | Throughput | Data rate |
|
|
245
|
+
|-------------|-----------|-----------|
|
|
246
|
+
| 100 B | ~4,000 msg/s | 390 KB/s |
|
|
247
|
+
| 1 KB | ~3,500 msg/s | 3.3 MB/s |
|
|
248
|
+
| 10 KB | ~2,600 msg/s | 25 MB/s |
|
|
249
|
+
| 50 KB | ~1,600 msg/s | 77 MB/s |
|
|
250
|
+
|
|
251
|
+
Typical OpenClaw event payloads are 200–500 bytes. At normal agent usage (~50–100 events/min), the plugin uses less than 1% of available throughput. Zero errors across all benchmark runs.
|
|
252
|
+
|
|
253
|
+
A benchmark script is included in the repository — see `scripts/nats-benchmark.mjs` (note: lives in the [companion workspace](https://git.vainplex.dev/claudia.keller/claudia-workspace), not this package).
|
|
254
|
+
|
|
255
|
+
## Development
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Run tests
|
|
259
|
+
npm test
|
|
260
|
+
|
|
261
|
+
# Run with integration tests (requires NATS on localhost:14222)
|
|
262
|
+
NATS_URL=nats://localhost:14222 npm test
|
|
263
|
+
|
|
264
|
+
# Watch mode
|
|
265
|
+
npm run test:watch
|
|
266
|
+
|
|
267
|
+
# Type check
|
|
268
|
+
npm run typecheck
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## License
|
|
272
|
+
|
|
273
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PluginLogger } from "./src/nats-client.js";
|
|
2
|
+
type OpenClawPluginApi = {
|
|
3
|
+
id: string;
|
|
4
|
+
pluginConfig?: Record<string, unknown>;
|
|
5
|
+
logger: PluginLogger;
|
|
6
|
+
config: Record<string, unknown>;
|
|
7
|
+
registerService: (service: {
|
|
8
|
+
id: string;
|
|
9
|
+
start: (ctx: any) => Promise<void>;
|
|
10
|
+
stop: (ctx: any) => Promise<void>;
|
|
11
|
+
}) => void;
|
|
12
|
+
registerCommand: (command: Record<string, unknown>) => void;
|
|
13
|
+
registerGatewayMethod: (method: string, handler: (...args: any[]) => any) => void;
|
|
14
|
+
on: (hookName: string, handler: (...args: any[]) => void, opts?: {
|
|
15
|
+
priority?: number;
|
|
16
|
+
}) => void;
|
|
17
|
+
};
|
|
18
|
+
declare const plugin: {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
version: string;
|
|
23
|
+
register(api: OpenClawPluginApi): void;
|
|
24
|
+
};
|
|
25
|
+
export default plugin;
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAKzD,KAAK,iBAAiB,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,eAAe,EAAE,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAC1H,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC5D,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,KAAK,IAAI,CAAC;IAClF,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACjG,CAAC;AAIF,QAAA,MAAM,MAAM;;;;;kBAMI,iBAAiB;CAqDhC,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createEventStoreService } from "./src/service.js";
|
|
2
|
+
import { registerEventHooks } from "./src/hooks.js";
|
|
3
|
+
import { resolveConfig } from "./src/config.js";
|
|
4
|
+
let client = null;
|
|
5
|
+
const plugin = {
|
|
6
|
+
id: "nats-eventstore",
|
|
7
|
+
name: "NATS Event Store",
|
|
8
|
+
description: "Publish agent events to NATS JetStream for audit, replay, and multi-agent sharing",
|
|
9
|
+
version: "0.1.0",
|
|
10
|
+
register(api) {
|
|
11
|
+
const config = resolveConfig(api.pluginConfig);
|
|
12
|
+
if (!config.enabled) {
|
|
13
|
+
api.logger.info("[nats-eventstore] Disabled via config");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Register service for connection lifecycle
|
|
17
|
+
api.registerService(createEventStoreService(() => client, (c) => { client = c; }));
|
|
18
|
+
// Register all hook handlers
|
|
19
|
+
registerEventHooks(api, config, () => client);
|
|
20
|
+
// Register /eventstatus command
|
|
21
|
+
api.registerCommand({
|
|
22
|
+
name: "eventstatus",
|
|
23
|
+
description: "Show NATS event store connection status",
|
|
24
|
+
requireAuth: true,
|
|
25
|
+
handler: () => {
|
|
26
|
+
const status = client?.getStatus() ?? {
|
|
27
|
+
connected: false,
|
|
28
|
+
stream: null,
|
|
29
|
+
disconnectCount: 0,
|
|
30
|
+
publishFailures: 0,
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
text: [
|
|
34
|
+
"**NATS Event Store**",
|
|
35
|
+
`Connected: ${status.connected ? "✅" : "❌"}`,
|
|
36
|
+
`Stream: ${status.stream ?? "n/a"}`,
|
|
37
|
+
`Disconnects: ${status.disconnectCount}`,
|
|
38
|
+
`Publish failures: ${status.publishFailures}`,
|
|
39
|
+
].join("\n"),
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
// Register gateway method for programmatic status
|
|
44
|
+
api.registerGatewayMethod("eventstore.status", async () => {
|
|
45
|
+
return client?.getStatus() ?? {
|
|
46
|
+
connected: false,
|
|
47
|
+
stream: null,
|
|
48
|
+
disconnectCount: 0,
|
|
49
|
+
publishFailures: 0,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
export default plugin;
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAahD,IAAI,MAAM,GAAsB,IAAI,CAAC;AAErC,MAAM,MAAM,GAAG;IACb,EAAE,EAAE,iBAAiB;IACrB,IAAI,EAAE,kBAAkB;IACxB,WAAW,EAAE,mFAAmF;IAChG,OAAO,EAAE,OAAO;IAEhB,QAAQ,CAAC,GAAsB;QAC7B,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAE/C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;YACzD,OAAO;QACT,CAAC;QAED,4CAA4C;QAC5C,GAAG,CAAC,eAAe,CACjB,uBAAuB,CACrB,GAAG,EAAE,CAAC,MAAM,EACZ,CAAC,CAAC,EAAE,EAAE,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CACvB,CACF,CAAC;QAEF,6BAA6B;QAC7B,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;QAE9C,gCAAgC;QAChC,GAAG,CAAC,eAAe,CAAC;YAClB,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE,yCAAyC;YACtD,WAAW,EAAE,IAAI;YACjB,OAAO,EAAE,GAAG,EAAE;gBACZ,MAAM,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,IAAI;oBACpC,SAAS,EAAE,KAAK;oBAChB,MAAM,EAAE,IAAI;oBACZ,eAAe,EAAE,CAAC;oBAClB,eAAe,EAAE,CAAC;iBACnB,CAAC;gBACF,OAAO;oBACL,IAAI,EAAE;wBACJ,sBAAsB;wBACtB,cAAc,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE;wBAC5C,WAAW,MAAM,CAAC,MAAM,IAAI,KAAK,EAAE;wBACnC,gBAAgB,MAAM,CAAC,eAAe,EAAE;wBACxC,qBAAqB,MAAM,CAAC,eAAe,EAAE;qBAC9C,CAAC,IAAI,CAAC,IAAI,CAAC;iBACb,CAAC;YACJ,CAAC;SACF,CAAC,CAAC;QAEH,kDAAkD;QAClD,GAAG,CAAC,qBAAqB,CAAC,mBAAmB,EAAE,KAAK,IAAI,EAAE;YACxD,OAAO,MAAM,EAAE,SAAS,EAAE,IAAI;gBAC5B,SAAS,EAAE,KAAK;gBAChB,MAAM,EAAE,IAAI;gBACZ,eAAe,EAAE,CAAC;gBAClB,eAAe,EAAE,CAAC;aACnB,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type NatsEventStoreConfig = {
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
natsUrl: string;
|
|
4
|
+
streamName: string;
|
|
5
|
+
subjectPrefix: string;
|
|
6
|
+
retention: {
|
|
7
|
+
maxMessages: number;
|
|
8
|
+
maxBytes: number;
|
|
9
|
+
maxAgeHours: number;
|
|
10
|
+
};
|
|
11
|
+
publishTimeoutMs: number;
|
|
12
|
+
connectTimeoutMs: number;
|
|
13
|
+
drainTimeoutMs: number;
|
|
14
|
+
includeHooks: string[];
|
|
15
|
+
excludeHooks: string[];
|
|
16
|
+
};
|
|
17
|
+
export declare const DEFAULTS: NatsEventStoreConfig;
|
|
18
|
+
export declare function resolveConfig(pluginConfig?: Record<string, unknown>): NatsEventStoreConfig;
|
|
19
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE;QACT,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,QAAQ,EAAE,oBAetB,CAAC;AAEF,wBAAgB,aAAa,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,oBAAoB,CAmB1F"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const DEFAULTS = {
|
|
2
|
+
enabled: true,
|
|
3
|
+
natsUrl: "nats://localhost:4222",
|
|
4
|
+
streamName: "openclaw-events",
|
|
5
|
+
subjectPrefix: "openclaw.events",
|
|
6
|
+
retention: {
|
|
7
|
+
maxMessages: -1,
|
|
8
|
+
maxBytes: -1,
|
|
9
|
+
maxAgeHours: 0,
|
|
10
|
+
},
|
|
11
|
+
publishTimeoutMs: 5000,
|
|
12
|
+
connectTimeoutMs: 5000,
|
|
13
|
+
drainTimeoutMs: 5000,
|
|
14
|
+
includeHooks: [],
|
|
15
|
+
excludeHooks: [],
|
|
16
|
+
};
|
|
17
|
+
export function resolveConfig(pluginConfig) {
|
|
18
|
+
const raw = pluginConfig ?? {};
|
|
19
|
+
const retention = raw.retention;
|
|
20
|
+
return {
|
|
21
|
+
enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULTS.enabled,
|
|
22
|
+
natsUrl: typeof raw.natsUrl === "string" ? raw.natsUrl : DEFAULTS.natsUrl,
|
|
23
|
+
streamName: typeof raw.streamName === "string" ? raw.streamName : DEFAULTS.streamName,
|
|
24
|
+
subjectPrefix: typeof raw.subjectPrefix === "string" ? raw.subjectPrefix : DEFAULTS.subjectPrefix,
|
|
25
|
+
retention: {
|
|
26
|
+
maxMessages: retention?.maxMessages != null ? Number(retention.maxMessages) : DEFAULTS.retention.maxMessages,
|
|
27
|
+
maxBytes: retention?.maxBytes != null ? Number(retention.maxBytes) : DEFAULTS.retention.maxBytes,
|
|
28
|
+
maxAgeHours: retention?.maxAgeHours != null ? Number(retention.maxAgeHours) : DEFAULTS.retention.maxAgeHours,
|
|
29
|
+
},
|
|
30
|
+
publishTimeoutMs: typeof raw.publishTimeoutMs === "number" ? raw.publishTimeoutMs : DEFAULTS.publishTimeoutMs,
|
|
31
|
+
connectTimeoutMs: typeof raw.connectTimeoutMs === "number" ? raw.connectTimeoutMs : DEFAULTS.connectTimeoutMs,
|
|
32
|
+
drainTimeoutMs: typeof raw.drainTimeoutMs === "number" ? raw.drainTimeoutMs : DEFAULTS.drainTimeoutMs,
|
|
33
|
+
includeHooks: Array.isArray(raw.includeHooks) ? raw.includeHooks : DEFAULTS.includeHooks,
|
|
34
|
+
excludeHooks: Array.isArray(raw.excludeHooks) ? raw.excludeHooks : DEFAULTS.excludeHooks,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,MAAM,QAAQ,GAAyB;IAC5C,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,uBAAuB;IAChC,UAAU,EAAE,iBAAiB;IAC7B,aAAa,EAAE,iBAAiB;IAChC,SAAS,EAAE;QACT,WAAW,EAAE,CAAC,CAAC;QACf,QAAQ,EAAE,CAAC,CAAC;QACZ,WAAW,EAAE,CAAC;KACf;IACD,gBAAgB,EAAE,IAAI;IACtB,gBAAgB,EAAE,IAAI;IACtB,cAAc,EAAE,IAAI;IACpB,YAAY,EAAE,EAAE;IAChB,YAAY,EAAE,EAAE;CACjB,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,YAAsC;IAClE,MAAM,GAAG,GAAG,YAAY,IAAI,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,GAAG,CAAC,SAAgD,CAAC;IACvE,OAAO;QACL,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO;QAC1E,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO;QACzE,UAAU,EAAE,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU;QACrF,aAAa,EAAE,OAAO,GAAG,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa;QACjG,SAAS,EAAE;YACT,WAAW,EAAE,SAAS,EAAE,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW;YAC5G,QAAQ,EAAE,SAAS,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ;YAChG,WAAW,EAAE,SAAS,EAAE,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW;SAC7G;QACD,gBAAgB,EAAE,OAAO,GAAG,CAAC,gBAAgB,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB;QAC7G,gBAAgB,EAAE,OAAO,GAAG,CAAC,gBAAgB,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB;QAC7G,cAAc,EAAE,OAAO,GAAG,CAAC,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc;QACrG,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY;QACxF,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY;KACzF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type EventType = "msg.in" | "msg.out" | "msg.sending" | "tool.call" | "tool.result" | "run.start" | "run.end" | "run.error" | "llm.input" | "llm.output" | "session.start" | "session.end" | "session.compaction_start" | "session.compaction_end" | "session.reset" | "gateway.start" | "gateway.stop";
|
|
2
|
+
export type ClawEvent = {
|
|
3
|
+
/** Unique event ID (UUIDv4) */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Unix timestamp in milliseconds */
|
|
6
|
+
ts: number;
|
|
7
|
+
/** Agent ID (e.g., "main", "viola") */
|
|
8
|
+
agent: string;
|
|
9
|
+
/** Session key (e.g., "main", "viola:telegram:12345") */
|
|
10
|
+
session: string;
|
|
11
|
+
/** Event type identifier */
|
|
12
|
+
type: EventType;
|
|
13
|
+
/** Event-specific payload */
|
|
14
|
+
payload: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
/** All known event types as an array (useful for validation/testing) */
|
|
17
|
+
export declare const ALL_EVENT_TYPES: EventType[];
|
|
18
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/events.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAEjB,QAAQ,GACR,SAAS,GACT,aAAa,GACb,WAAW,GACX,aAAa,GACb,WAAW,GACX,SAAS,GACT,WAAW,GAEX,WAAW,GACX,YAAY,GACZ,eAAe,GACf,aAAa,GACb,0BAA0B,GAC1B,wBAAwB,GACxB,eAAe,GACf,eAAe,GACf,cAAc,CAAC;AAEnB,MAAM,MAAM,SAAS,GAAG;IACtB,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,eAAe,EAAE,SAAS,EAkBtC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** All known event types as an array (useful for validation/testing) */
|
|
2
|
+
export const ALL_EVENT_TYPES = [
|
|
3
|
+
"msg.in",
|
|
4
|
+
"msg.out",
|
|
5
|
+
"msg.sending",
|
|
6
|
+
"tool.call",
|
|
7
|
+
"tool.result",
|
|
8
|
+
"run.start",
|
|
9
|
+
"run.end",
|
|
10
|
+
"run.error",
|
|
11
|
+
"llm.input",
|
|
12
|
+
"llm.output",
|
|
13
|
+
"session.start",
|
|
14
|
+
"session.end",
|
|
15
|
+
"session.compaction_start",
|
|
16
|
+
"session.compaction_end",
|
|
17
|
+
"session.reset",
|
|
18
|
+
"gateway.start",
|
|
19
|
+
"gateway.stop",
|
|
20
|
+
];
|
|
21
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/events.ts"],"names":[],"mappings":"AAoCA,wEAAwE;AACxE,MAAM,CAAC,MAAM,eAAe,GAAgB;IAC1C,QAAQ;IACR,SAAS;IACT,aAAa;IACb,WAAW;IACX,aAAa;IACb,WAAW;IACX,SAAS;IACT,WAAW;IACX,WAAW;IACX,YAAY;IACZ,eAAe;IACf,aAAa;IACb,0BAA0B;IAC1B,wBAAwB;IACxB,eAAe;IACf,eAAe;IACf,cAAc;CACf,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NatsClient } from "./nats-client.js";
|
|
2
|
+
import type { PluginLogger } from "./nats-client.js";
|
|
3
|
+
import type { NatsEventStoreConfig } from "./config.js";
|
|
4
|
+
type PluginApi = {
|
|
5
|
+
logger: PluginLogger;
|
|
6
|
+
on: (hookName: string, handler: (...args: any[]) => void) => void;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Register all event hook handlers on the plugin API.
|
|
10
|
+
* Uses a data-driven mapping table — each hook is registered via a single loop.
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerEventHooks(api: PluginApi, config: NatsEventStoreConfig, getClient: () => NatsClient | null): void;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAYxD,KAAK,SAAS,GAAG;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,IAAI,CAAC;CACnE,CAAC;AAoOF;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,oBAAoB,EAC5B,SAAS,EAAE,MAAM,UAAU,GAAG,IAAI,GACjC,IAAI,CAiDN"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { extractAgentId, buildSubject } from "./util.js";
|
|
3
|
+
/** Check if a hook should be published based on include/exclude config. */
|
|
4
|
+
function shouldPublish(hookName, config) {
|
|
5
|
+
if (config.includeHooks.length > 0) {
|
|
6
|
+
return config.includeHooks.includes(hookName);
|
|
7
|
+
}
|
|
8
|
+
if (config.excludeHooks.length > 0) {
|
|
9
|
+
return !config.excludeHooks.includes(hookName);
|
|
10
|
+
}
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
/** Fire-and-forget publish of a ClawEvent to NATS. */
|
|
14
|
+
function publish(getClient, config, type, agent, session, payload, logger) {
|
|
15
|
+
const client = getClient();
|
|
16
|
+
if (!client?.isConnected())
|
|
17
|
+
return;
|
|
18
|
+
const event = {
|
|
19
|
+
id: randomUUID(),
|
|
20
|
+
ts: Date.now(),
|
|
21
|
+
agent,
|
|
22
|
+
session,
|
|
23
|
+
type,
|
|
24
|
+
payload,
|
|
25
|
+
};
|
|
26
|
+
const subject = buildSubject(config.subjectPrefix, agent, type);
|
|
27
|
+
client.publish(subject, JSON.stringify(event)).catch((err) => {
|
|
28
|
+
logger.warn(`[nats-eventstore] Publish ${type} failed: ${err}`);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const HOOK_MAPPINGS = [
|
|
32
|
+
{
|
|
33
|
+
hookName: "message_received",
|
|
34
|
+
eventType: "msg.in",
|
|
35
|
+
mapper: (event, ctx) => ({
|
|
36
|
+
from: event.from,
|
|
37
|
+
content: event.content,
|
|
38
|
+
timestamp: event.timestamp,
|
|
39
|
+
channel: ctx?.channelId,
|
|
40
|
+
metadata: event.metadata,
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
hookName: "message_sending",
|
|
45
|
+
eventType: "msg.sending",
|
|
46
|
+
mapper: (event, ctx) => ({
|
|
47
|
+
to: event.to,
|
|
48
|
+
content: event.content,
|
|
49
|
+
channel: ctx?.channelId,
|
|
50
|
+
}),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
hookName: "message_sent",
|
|
54
|
+
eventType: "msg.out",
|
|
55
|
+
mapper: (event, ctx) => ({
|
|
56
|
+
to: event.to,
|
|
57
|
+
content: event.content,
|
|
58
|
+
success: event.success,
|
|
59
|
+
error: event.error,
|
|
60
|
+
channel: ctx?.channelId,
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
hookName: "before_tool_call",
|
|
65
|
+
eventType: "tool.call",
|
|
66
|
+
mapper: (event) => ({
|
|
67
|
+
toolName: event.toolName,
|
|
68
|
+
params: event.params,
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
hookName: "after_tool_call",
|
|
73
|
+
eventType: "tool.result",
|
|
74
|
+
mapper: (event) => ({
|
|
75
|
+
toolName: event.toolName,
|
|
76
|
+
params: event.params,
|
|
77
|
+
result: event.result,
|
|
78
|
+
error: event.error,
|
|
79
|
+
durationMs: event.durationMs,
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
hookName: "before_agent_start",
|
|
84
|
+
eventType: "run.start",
|
|
85
|
+
mapper: (event) => ({ prompt: event.prompt }),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
hookName: "agent_end",
|
|
89
|
+
eventType: "run.end",
|
|
90
|
+
mapper: (event) => ({
|
|
91
|
+
success: event.success,
|
|
92
|
+
error: event.error,
|
|
93
|
+
durationMs: event.durationMs,
|
|
94
|
+
messageCount: event.messages?.length ?? 0,
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
hookName: "llm_input",
|
|
99
|
+
eventType: "llm.input",
|
|
100
|
+
mapper: (event) => ({
|
|
101
|
+
runId: event.runId,
|
|
102
|
+
sessionId: event.sessionId,
|
|
103
|
+
provider: event.provider,
|
|
104
|
+
model: event.model,
|
|
105
|
+
systemPromptLength: event.systemPrompt?.length ?? 0,
|
|
106
|
+
promptLength: event.prompt?.length ?? 0,
|
|
107
|
+
historyMessageCount: event.historyMessages?.length ?? 0,
|
|
108
|
+
imagesCount: event.imagesCount ?? 0,
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
hookName: "llm_output",
|
|
113
|
+
eventType: "llm.output",
|
|
114
|
+
mapper: (event) => {
|
|
115
|
+
const texts = event.assistantTexts ?? [];
|
|
116
|
+
return {
|
|
117
|
+
runId: event.runId,
|
|
118
|
+
sessionId: event.sessionId,
|
|
119
|
+
provider: event.provider,
|
|
120
|
+
model: event.model,
|
|
121
|
+
assistantTextCount: texts.length,
|
|
122
|
+
assistantTextTotalLength: texts.reduce((s, t) => s + (t?.length ?? 0), 0),
|
|
123
|
+
usage: event.usage,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
hookName: "before_compaction",
|
|
129
|
+
eventType: "session.compaction_start",
|
|
130
|
+
mapper: (event) => ({
|
|
131
|
+
messageCount: event.messageCount,
|
|
132
|
+
compactingCount: event.compactingCount,
|
|
133
|
+
tokenCount: event.tokenCount,
|
|
134
|
+
}),
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
hookName: "after_compaction",
|
|
138
|
+
eventType: "session.compaction_end",
|
|
139
|
+
mapper: (event) => ({
|
|
140
|
+
messageCount: event.messageCount,
|
|
141
|
+
compactedCount: event.compactedCount,
|
|
142
|
+
tokenCount: event.tokenCount,
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
hookName: "before_reset",
|
|
147
|
+
eventType: "session.reset",
|
|
148
|
+
mapper: (event) => ({ reason: event.reason }),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
hookName: "session_start",
|
|
152
|
+
eventType: "session.start",
|
|
153
|
+
mapper: (event) => ({
|
|
154
|
+
sessionId: event.sessionId,
|
|
155
|
+
resumedFrom: event.resumedFrom,
|
|
156
|
+
}),
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
hookName: "session_end",
|
|
160
|
+
eventType: "session.end",
|
|
161
|
+
mapper: (event) => ({
|
|
162
|
+
sessionId: event.sessionId,
|
|
163
|
+
messageCount: event.messageCount,
|
|
164
|
+
durationMs: event.durationMs,
|
|
165
|
+
}),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
hookName: "gateway_start",
|
|
169
|
+
eventType: "gateway.start",
|
|
170
|
+
mapper: (event) => ({ port: event.port }),
|
|
171
|
+
systemEvent: true,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
hookName: "gateway_stop",
|
|
175
|
+
eventType: "gateway.stop",
|
|
176
|
+
mapper: (event) => ({ reason: event.reason }),
|
|
177
|
+
systemEvent: true,
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
/** Extra events emitted from existing hooks (backward compatibility). */
|
|
181
|
+
const EXTRA_EMITTERS = [
|
|
182
|
+
{
|
|
183
|
+
hookName: "agent_end",
|
|
184
|
+
eventType: "run.error",
|
|
185
|
+
condition: (event) => !event.success,
|
|
186
|
+
mapper: (event) => ({
|
|
187
|
+
success: false,
|
|
188
|
+
error: event.error,
|
|
189
|
+
durationMs: event.durationMs,
|
|
190
|
+
}),
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
/**
|
|
194
|
+
* Register all event hook handlers on the plugin API.
|
|
195
|
+
* Uses a data-driven mapping table — each hook is registered via a single loop.
|
|
196
|
+
*/
|
|
197
|
+
export function registerEventHooks(api, config, getClient) {
|
|
198
|
+
const logger = api.logger;
|
|
199
|
+
// Helper: publish with agent context extraction
|
|
200
|
+
const pub = (type, ctx, payload) => {
|
|
201
|
+
const agent = extractAgentId(ctx);
|
|
202
|
+
const session = ctx.sessionKey ?? ctx.sessionId ?? "unknown";
|
|
203
|
+
publish(getClient, config, type, agent, session, payload, logger);
|
|
204
|
+
};
|
|
205
|
+
// Build lookup for extra emitters per hook
|
|
206
|
+
const extrasByHook = new Map();
|
|
207
|
+
for (const extra of EXTRA_EMITTERS) {
|
|
208
|
+
const list = extrasByHook.get(extra.hookName) ?? [];
|
|
209
|
+
list.push(extra);
|
|
210
|
+
extrasByHook.set(extra.hookName, list);
|
|
211
|
+
}
|
|
212
|
+
// Register each hook from the mapping table
|
|
213
|
+
for (const mapping of HOOK_MAPPINGS) {
|
|
214
|
+
if (!shouldPublish(mapping.hookName, config))
|
|
215
|
+
continue;
|
|
216
|
+
const extras = extrasByHook.get(mapping.hookName) ?? [];
|
|
217
|
+
api.on(mapping.hookName, (event, ctx) => {
|
|
218
|
+
try {
|
|
219
|
+
const payload = mapping.mapper(event, ctx);
|
|
220
|
+
if (mapping.systemEvent) {
|
|
221
|
+
publish(getClient, config, mapping.eventType, "system", "system", payload, logger);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
pub(mapping.eventType, ctx, payload);
|
|
225
|
+
}
|
|
226
|
+
// Emit any extra events for this hook
|
|
227
|
+
for (const extra of extras) {
|
|
228
|
+
if (extra.condition(event)) {
|
|
229
|
+
pub(extra.eventType, ctx, extra.mapper(event, ctx));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
logger.warn(`[nats-eventstore] Hook ${mapping.hookName} error: ${err}`);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKzC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAezD,2EAA2E;AAC3E,SAAS,aAAa,CAAC,QAAgB,EAAE,MAA4B;IACnE,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;IACD,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sDAAsD;AACtD,SAAS,OAAO,CACd,SAAkC,EAClC,MAA4B,EAC5B,IAAe,EACf,KAAa,EACb,OAAe,EACf,OAAgC,EAChC,MAAoB;IAEpB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE;QAAE,OAAO;IAEnC,MAAM,KAAK,GAAc;QACvB,EAAE,EAAE,UAAU,EAAE;QAChB,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;QACd,KAAK;QACL,OAAO;QACP,IAAI;QACJ,OAAO;KACR,CAAC;IAEF,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAChE,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QAC3D,MAAM,CAAC,IAAI,CAAC,6BAA6B,IAAI,YAAY,GAAG,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC;AAsBD,MAAM,aAAa,GAAkB;IACnC;QACE,QAAQ,EAAE,kBAAkB;QAC5B,SAAS,EAAE,QAAQ;QACnB,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACvB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,OAAO,EAAE,GAAG,EAAE,SAAS;YACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB,CAAC;KACH;IACD;QACE,QAAQ,EAAE,iBAAiB;QAC3B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACvB,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,GAAG,EAAE,SAAS;SACxB,CAAC;KACH;IACD;QACE,QAAQ,EAAE,cAAc;QACxB,SAAS,EAAE,SAAS;QACpB,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACvB,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,OAAO,EAAE,GAAG,EAAE,SAAS;SACxB,CAAC;KACH;IACD;QACE,QAAQ,EAAE,kBAAkB;QAC5B,SAAS,EAAE,WAAW;QACtB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM;SACrB,CAAC;KACH;IACD;QACE,QAAQ,EAAE,iBAAiB;QAC3B,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,CAAC;KACH;IACD;QACE,QAAQ,EAAE,oBAAoB;QAC9B,SAAS,EAAE,WAAW;QACtB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;KAC9C;IACD;QACE,QAAQ,EAAE,WAAW;QACrB,SAAS,EAAE,SAAS;QACpB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,YAAY,EAAE,KAAK,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;SAC1C,CAAC;KACH;IACD;QACE,QAAQ,EAAE,WAAW;QACrB,SAAS,EAAE,WAAW;QACtB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,kBAAkB,EAAE,KAAK,CAAC,YAAY,EAAE,MAAM,IAAI,CAAC;YACnD,YAAY,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;YACvC,mBAAmB,EAAE,KAAK,CAAC,eAAe,EAAE,MAAM,IAAI,CAAC;YACvD,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,CAAC;SACpC,CAAC;KACH;IACD;QACE,QAAQ,EAAE,YAAY;QACtB,SAAS,EAAE,YAAY;QACvB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;YAChB,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC;YACzC,OAAO;gBACL,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,kBAAkB,EAAE,KAAK,CAAC,MAAM;gBAChC,wBAAwB,EAAE,KAAK,CAAC,MAAM,CACpC,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,EAC9C,CAAC,CACF;gBACD,KAAK,EAAE,KAAK,CAAC,KAAK;aACnB,CAAC;QACJ,CAAC;KACF;IACD;QACE,QAAQ,EAAE,mBAAmB;QAC7B,SAAS,EAAE,0BAA0B;QACrC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,CAAC;KACH;IACD;QACE,QAAQ,EAAE,kBAAkB;QAC5B,SAAS,EAAE,wBAAwB;QACnC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,CAAC;KACH;IACD;QACE,QAAQ,EAAE,cAAc;QACxB,SAAS,EAAE,eAAe;QAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;KAC9C;IACD;QACE,QAAQ,EAAE,eAAe;QACzB,SAAS,EAAE,eAAe;QAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;SAC/B,CAAC;KACH;IACD;QACE,QAAQ,EAAE,aAAa;QACvB,SAAS,EAAE,aAAa;QACxB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,CAAC;KACH;IACD;QACE,QAAQ,EAAE,eAAe;QACzB,SAAS,EAAE,eAAe;QAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QACzC,WAAW,EAAE,IAAI;KAClB;IACD;QACE,QAAQ,EAAE,cAAc;QACxB,SAAS,EAAE,cAAc;QACzB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;QAC7C,WAAW,EAAE,IAAI;KAClB;CACF,CAAC;AAEF,yEAAyE;AACzE,MAAM,cAAc,GAAmB;IACrC;QACE,QAAQ,EAAE,WAAW;QACrB,SAAS,EAAE,WAAW;QACtB,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO;QACpC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAClB,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,CAAC;KACH;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,GAAc,EACd,MAA4B,EAC5B,SAAkC;IAElC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAE1B,gDAAgD;IAChD,MAAM,GAAG,GAAG,CACV,IAAe,EACf,GAAY,EACZ,OAAgC,EAChC,EAAE;QACF,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,SAAS,IAAI,SAAS,CAAC;QAC7D,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACpE,CAAC,CAAC;IAEF,2CAA2C;IAC3C,MAAM,YAAY,GAAG,IAAI,GAAG,EAA0B,CAAC;IACvD,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjB,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,4CAA4C;IAC5C,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QACpC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAE,SAAS;QAEvD,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAExD,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAU,EAAE,GAAQ,EAAE,EAAE;YAChD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAE3C,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;oBACxB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;gBACrF,CAAC;qBAAM,CAAC;oBACN,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;gBACvC,CAAC;gBAED,sCAAsC;gBACtC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;oBAC3B,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;wBAC3B,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;oBACtD,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,0BAA0B,OAAO,CAAC,QAAQ,WAAW,GAAG,EAAE,CAAC,CAAC;YAC1E,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { NatsEventStoreConfig } from "./config.js";
|
|
2
|
+
export type PluginLogger = {
|
|
3
|
+
info: (msg: string) => void;
|
|
4
|
+
warn: (msg: string) => void;
|
|
5
|
+
error: (msg: string) => void;
|
|
6
|
+
debug: (msg: string) => void;
|
|
7
|
+
};
|
|
8
|
+
export type NatsClient = {
|
|
9
|
+
publish(subject: string, data: string): Promise<void>;
|
|
10
|
+
isConnected(): boolean;
|
|
11
|
+
getStatus(): NatsClientStatus;
|
|
12
|
+
drain(): Promise<void>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export type NatsClientStatus = {
|
|
16
|
+
connected: boolean;
|
|
17
|
+
stream: string | null;
|
|
18
|
+
disconnectCount: number;
|
|
19
|
+
publishFailures: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Parse a NATS URL, extracting user/pass and returning a safe URL for logging.
|
|
23
|
+
*
|
|
24
|
+
* Example: "nats://user:pass@host:4222" → { servers: "host:4222", user: "user", pass: "pass", safeUrl: "nats://host:4222" }
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseNatsUrl(url: string): {
|
|
27
|
+
servers: string;
|
|
28
|
+
user?: string;
|
|
29
|
+
pass?: string;
|
|
30
|
+
safeUrl: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function createNatsClient(config: NatsEventStoreConfig, logger: PluginLogger): Promise<NatsClient>;
|
|
33
|
+
//# sourceMappingURL=nats-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nats-client.d.ts","sourceRoot":"","sources":["../../src/nats-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,WAAW,IAAI,OAAO,CAAC;IACvB,SAAS,IAAI,gBAAgB,CAAC;IAC9B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAIF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAcA;AAmED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,UAAU,CAAC,CAqFrB"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const MAX_PUBLISH_FAILURES_BEFORE_WARN = 10;
|
|
2
|
+
/**
|
|
3
|
+
* Parse a NATS URL, extracting user/pass and returning a safe URL for logging.
|
|
4
|
+
*
|
|
5
|
+
* Example: "nats://user:pass@host:4222" → { servers: "host:4222", user: "user", pass: "pass", safeUrl: "nats://host:4222" }
|
|
6
|
+
*/
|
|
7
|
+
export function parseNatsUrl(url) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = new URL(url);
|
|
10
|
+
const user = parsed.username || undefined;
|
|
11
|
+
const pass = parsed.password || undefined;
|
|
12
|
+
const host = parsed.hostname;
|
|
13
|
+
const port = parsed.port || "4222";
|
|
14
|
+
const servers = `${host}:${port}`;
|
|
15
|
+
const safeUrl = `${parsed.protocol}//${host}:${port}`;
|
|
16
|
+
return { servers, user, pass, safeUrl };
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// If URL parsing fails, return as-is
|
|
20
|
+
return { servers: url, safeUrl: url };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function ensureStream(nc, config, logger) {
|
|
24
|
+
const jsm = await nc.jetstreamManager();
|
|
25
|
+
try {
|
|
26
|
+
await jsm.streams.info(config.streamName);
|
|
27
|
+
logger.debug(`[nats-eventstore] Stream "${config.streamName}" exists`);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
// Stream doesn't exist — create it
|
|
31
|
+
// nats.js v2+: JetStream API errors use err.api_error?.code (numeric)
|
|
32
|
+
const apiCode = err?.api_error?.code;
|
|
33
|
+
const errCode = err?.code;
|
|
34
|
+
const isNotFound = apiCode === 404 ||
|
|
35
|
+
errCode === 404 ||
|
|
36
|
+
errCode === "404" ||
|
|
37
|
+
err?.message?.includes("not found") ||
|
|
38
|
+
err?.message?.includes("stream not found");
|
|
39
|
+
if (isNotFound) {
|
|
40
|
+
const streamConfig = {
|
|
41
|
+
name: config.streamName,
|
|
42
|
+
subjects: [`${config.subjectPrefix}.>`],
|
|
43
|
+
retention: "limits",
|
|
44
|
+
max_msgs: config.retention.maxMessages,
|
|
45
|
+
max_bytes: config.retention.maxBytes,
|
|
46
|
+
max_age: config.retention.maxAgeHours > 0
|
|
47
|
+
? config.retention.maxAgeHours * 60 * 60 * 1_000_000_000 // nanoseconds
|
|
48
|
+
: 0,
|
|
49
|
+
};
|
|
50
|
+
await jsm.streams.add(streamConfig);
|
|
51
|
+
logger.info(`[nats-eventstore] Created stream "${config.streamName}"`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function monitorConnection(nc, counters, logger) {
|
|
59
|
+
(async () => {
|
|
60
|
+
for await (const status of nc.status()) {
|
|
61
|
+
switch (status.type) {
|
|
62
|
+
case "disconnect":
|
|
63
|
+
counters.disconnects++;
|
|
64
|
+
logger.warn(`[nats-eventstore] Disconnected (${counters.disconnects} total)`);
|
|
65
|
+
break;
|
|
66
|
+
case "reconnect":
|
|
67
|
+
logger.info("[nats-eventstore] Reconnected");
|
|
68
|
+
break;
|
|
69
|
+
case "error":
|
|
70
|
+
logger.error(`[nats-eventstore] Connection error: ${status.data}`);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})().catch(() => {
|
|
75
|
+
// Iterator ends when connection closes — expected
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export async function createNatsClient(config, logger) {
|
|
79
|
+
const nats = await import("nats");
|
|
80
|
+
const parsed = parseNatsUrl(config.natsUrl);
|
|
81
|
+
const nc = await nats.connect({
|
|
82
|
+
servers: parsed.servers,
|
|
83
|
+
user: parsed.user,
|
|
84
|
+
pass: parsed.pass,
|
|
85
|
+
reconnect: true,
|
|
86
|
+
maxReconnectAttempts: -1,
|
|
87
|
+
timeout: config.connectTimeoutMs,
|
|
88
|
+
});
|
|
89
|
+
logger.info(`[nats-eventstore] Connected to ${parsed.safeUrl}`);
|
|
90
|
+
const js = nc.jetstream();
|
|
91
|
+
const sc = nats.StringCodec();
|
|
92
|
+
// Ensure stream exists
|
|
93
|
+
await ensureStream(nc, config, logger);
|
|
94
|
+
// Monitor connection status
|
|
95
|
+
const counters = { disconnects: 0, publishFailures: 0 };
|
|
96
|
+
monitorConnection(nc, counters, logger);
|
|
97
|
+
return {
|
|
98
|
+
async publish(subject, data) {
|
|
99
|
+
try {
|
|
100
|
+
const publishPromise = js.publish(subject, sc.encode(data));
|
|
101
|
+
if (config.publishTimeoutMs > 0) {
|
|
102
|
+
let timer;
|
|
103
|
+
await Promise.race([
|
|
104
|
+
publishPromise,
|
|
105
|
+
new Promise((_resolve, reject) => {
|
|
106
|
+
timer = setTimeout(() => reject(new Error(`Publish timeout after ${config.publishTimeoutMs}ms`)), config.publishTimeoutMs);
|
|
107
|
+
}),
|
|
108
|
+
]).finally(() => clearTimeout(timer));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
await publishPromise;
|
|
112
|
+
}
|
|
113
|
+
counters.publishFailures = 0;
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
counters.publishFailures++;
|
|
117
|
+
if (counters.publishFailures === 1 ||
|
|
118
|
+
counters.publishFailures % MAX_PUBLISH_FAILURES_BEFORE_WARN === 0) {
|
|
119
|
+
logger.warn(`[nats-eventstore] Publish failed (${counters.publishFailures} consecutive): ${err}`);
|
|
120
|
+
}
|
|
121
|
+
// Non-fatal: do not throw. Agent operations must never be blocked by event store.
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
isConnected: () => !nc.isClosed(),
|
|
125
|
+
getStatus: () => ({
|
|
126
|
+
connected: !nc.isClosed(),
|
|
127
|
+
stream: config.streamName,
|
|
128
|
+
disconnectCount: counters.disconnects,
|
|
129
|
+
publishFailures: counters.publishFailures,
|
|
130
|
+
}),
|
|
131
|
+
async drain() {
|
|
132
|
+
let timer;
|
|
133
|
+
try {
|
|
134
|
+
await Promise.race([
|
|
135
|
+
nc.drain(),
|
|
136
|
+
new Promise((_resolve, reject) => {
|
|
137
|
+
timer = setTimeout(() => reject(new Error("Drain timeout")), config.drainTimeoutMs);
|
|
138
|
+
}),
|
|
139
|
+
]).finally(() => clearTimeout(timer));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
logger.warn("[nats-eventstore] Drain timed out, forcing close");
|
|
143
|
+
await nc.close().catch(() => { });
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
async close() {
|
|
147
|
+
await nc.close().catch(() => { });
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=nats-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nats-client.js","sourceRoot":"","sources":["../../src/nats-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { NatsClient, PluginLogger } from "./nats-client.js";
|
|
2
|
+
export type ServiceContext = {
|
|
3
|
+
logger: PluginLogger;
|
|
4
|
+
config: {
|
|
5
|
+
plugins?: {
|
|
6
|
+
entries?: Record<string, {
|
|
7
|
+
config?: Record<string, unknown>;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export type PluginService = {
|
|
13
|
+
id: string;
|
|
14
|
+
start: (ctx: ServiceContext) => Promise<void>;
|
|
15
|
+
stop: (ctx: ServiceContext) => Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
export declare function createEventStoreService(getClient: () => NatsClient | null, setClient: (client: NatsClient | null) => void): PluginService;
|
|
18
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAIjE,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE;QACN,OAAO,CAAC,EAAE;YACR,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;gBAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;aAAE,CAAC,CAAC;SAChE,CAAC;KACH,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C,CAAC;AAEF,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,UAAU,GAAG,IAAI,EAClC,SAAS,EAAE,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,KAAK,IAAI,GAC7C,aAAa,CA6Bf"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createNatsClient } from "./nats-client.js";
|
|
2
|
+
import { resolveConfig } from "./config.js";
|
|
3
|
+
export function createEventStoreService(getClient, setClient) {
|
|
4
|
+
return {
|
|
5
|
+
id: "nats-eventstore",
|
|
6
|
+
async start(ctx) {
|
|
7
|
+
const pluginEntry = ctx.config.plugins?.entries?.["nats-eventstore"];
|
|
8
|
+
const config = resolveConfig(pluginEntry?.config);
|
|
9
|
+
if (!config.enabled) {
|
|
10
|
+
ctx.logger.info("[nats-eventstore] Disabled");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const client = await createNatsClient(config, ctx.logger);
|
|
15
|
+
setClient(client);
|
|
16
|
+
ctx.logger.info("[nats-eventstore] Ready");
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
ctx.logger.error(`[nats-eventstore] Init failed: ${err}`);
|
|
20
|
+
// Non-fatal: gateway continues without event store
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
async stop(ctx) {
|
|
24
|
+
const client = getClient();
|
|
25
|
+
if (!client)
|
|
26
|
+
return;
|
|
27
|
+
await client.drain();
|
|
28
|
+
setClient(null);
|
|
29
|
+
ctx.logger.info("[nats-eventstore] Shutdown");
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiB5C,MAAM,UAAU,uBAAuB,CACrC,SAAkC,EAClC,SAA8C;IAE9C,OAAO;QACL,EAAE,EAAE,iBAAiB;QACrB,KAAK,CAAC,KAAK,CAAC,GAAG;YACb,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,iBAAiB,CAAC,CAAC;YACrE,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,EAAE,MAAiC,CAAC,CAAC;YAE7E,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;gBAC9C,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC1D,SAAS,CAAC,MAAM,CAAC,CAAC;gBAClB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,GAAG,EAAE,CAAC,CAAC;gBAC1D,mDAAmD;YACrD,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAG;YACZ,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACrB,SAAS,CAAC,IAAI,CAAC,CAAC;YAChB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAChD,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EventType } from "./events.js";
|
|
2
|
+
/**
|
|
3
|
+
* Extract agent ID from context.
|
|
4
|
+
* Priority: ctx.agentId → ctx.sessionKey first segment → "main"
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractAgentId(ctx: {
|
|
7
|
+
agentId?: string;
|
|
8
|
+
sessionKey?: string;
|
|
9
|
+
}): string;
|
|
10
|
+
/**
|
|
11
|
+
* Build a NATS subject from prefix, agent ID, and event type.
|
|
12
|
+
* Dots in the event type are replaced with underscores for NATS subject compatibility.
|
|
13
|
+
*
|
|
14
|
+
* Example: buildSubject("openclaw.events", "main", "msg.in") → "openclaw.events.main.msg_in"
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildSubject(prefix: string, agent: string, eventType: EventType): string;
|
|
17
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAOrF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM,CAExF"}
|
package/dist/src/util.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract agent ID from context.
|
|
3
|
+
* Priority: ctx.agentId → ctx.sessionKey first segment → "main"
|
|
4
|
+
*/
|
|
5
|
+
export function extractAgentId(ctx) {
|
|
6
|
+
if (ctx.agentId && ctx.agentId !== "main")
|
|
7
|
+
return ctx.agentId;
|
|
8
|
+
if (ctx.sessionKey) {
|
|
9
|
+
if (ctx.sessionKey === "main")
|
|
10
|
+
return "main";
|
|
11
|
+
return ctx.sessionKey.split(":")[0] ?? "main";
|
|
12
|
+
}
|
|
13
|
+
return "main";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build a NATS subject from prefix, agent ID, and event type.
|
|
17
|
+
* Dots in the event type are replaced with underscores for NATS subject compatibility.
|
|
18
|
+
*
|
|
19
|
+
* Example: buildSubject("openclaw.events", "main", "msg.in") → "openclaw.events.main.msg_in"
|
|
20
|
+
*/
|
|
21
|
+
export function buildSubject(prefix, agent, eventType) {
|
|
22
|
+
return `${prefix}.${agent}.${eventType.replace(/\./g, "_")}`;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/util.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAA8C;IAC3E,IAAI,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO,KAAK,MAAM;QAAE,OAAO,GAAG,CAAC,OAAO,CAAC;IAC9D,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,GAAG,CAAC,UAAU,KAAK,MAAM;YAAE,OAAO,MAAM,CAAC;QAC7C,OAAO,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IAChD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,KAAa,EAAE,SAAoB;IAC9E,OAAO,GAAG,MAAM,IAAI,KAAK,IAAI,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nats-eventstore",
|
|
3
|
+
"configSchema": {
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"properties": {
|
|
7
|
+
"enabled": {
|
|
8
|
+
"type": "boolean",
|
|
9
|
+
"default": true,
|
|
10
|
+
"description": "Enable/disable event publishing"
|
|
11
|
+
},
|
|
12
|
+
"natsUrl": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"default": "nats://localhost:4222",
|
|
15
|
+
"description": "NATS server URL. Supports auth: nats://user:pass@host:port"
|
|
16
|
+
},
|
|
17
|
+
"streamName": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"default": "openclaw-events",
|
|
20
|
+
"description": "JetStream stream name"
|
|
21
|
+
},
|
|
22
|
+
"subjectPrefix": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"default": "openclaw.events",
|
|
25
|
+
"description": "Subject prefix for all published events"
|
|
26
|
+
},
|
|
27
|
+
"retention": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": false,
|
|
30
|
+
"properties": {
|
|
31
|
+
"maxMessages": {
|
|
32
|
+
"type": "integer",
|
|
33
|
+
"minimum": -1,
|
|
34
|
+
"default": -1,
|
|
35
|
+
"description": "Maximum messages to retain (-1 = unlimited)"
|
|
36
|
+
},
|
|
37
|
+
"maxBytes": {
|
|
38
|
+
"type": "integer",
|
|
39
|
+
"minimum": -1,
|
|
40
|
+
"default": -1,
|
|
41
|
+
"description": "Maximum bytes to retain (-1 = unlimited)"
|
|
42
|
+
},
|
|
43
|
+
"maxAgeHours": {
|
|
44
|
+
"type": "number",
|
|
45
|
+
"minimum": 0,
|
|
46
|
+
"default": 0,
|
|
47
|
+
"description": "Maximum age of messages in hours (0 = unlimited)"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"publishTimeoutMs": {
|
|
52
|
+
"type": "integer",
|
|
53
|
+
"minimum": 100,
|
|
54
|
+
"default": 5000,
|
|
55
|
+
"description": "Timeout for individual publish operations"
|
|
56
|
+
},
|
|
57
|
+
"connectTimeoutMs": {
|
|
58
|
+
"type": "integer",
|
|
59
|
+
"minimum": 1000,
|
|
60
|
+
"default": 5000,
|
|
61
|
+
"description": "Timeout for initial NATS connection"
|
|
62
|
+
},
|
|
63
|
+
"drainTimeoutMs": {
|
|
64
|
+
"type": "integer",
|
|
65
|
+
"minimum": 1000,
|
|
66
|
+
"default": 5000,
|
|
67
|
+
"description": "Timeout for graceful drain on shutdown"
|
|
68
|
+
},
|
|
69
|
+
"includeHooks": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": { "type": "string" },
|
|
72
|
+
"description": "Whitelist of hook names to publish (empty = all)"
|
|
73
|
+
},
|
|
74
|
+
"excludeHooks": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": { "type": "string" },
|
|
77
|
+
"description": "Blacklist of hook names to skip"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vainplex/nats-eventstore",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "OpenClaw plugin: publish agent events to NATS JetStream for audit, replay, and multi-agent sharing",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"openclaw.plugin.json",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"nats": "^2.29.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"vitest": "^3.0.0",
|
|
26
|
+
"@types/node": "^22.0.0",
|
|
27
|
+
"typescript": "^5.7.0"
|
|
28
|
+
},
|
|
29
|
+
"openclaw": {
|
|
30
|
+
"extensions": [
|
|
31
|
+
"./dist/index.js"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"openclaw",
|
|
37
|
+
"plugin",
|
|
38
|
+
"nats",
|
|
39
|
+
"jetstream",
|
|
40
|
+
"event-store",
|
|
41
|
+
"audit"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/alberthild/openclaw-nats-eventstore.git"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/alberthild/openclaw-nats-eventstore#readme",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/alberthild/openclaw-nats-eventstore/issues"
|
|
51
|
+
},
|
|
52
|
+
"author": "Vainplex <hildalbert@gmail.com>"
|
|
53
|
+
}
|