@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 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
@@ -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":"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"}
@@ -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"}
@@ -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
+ }