@omnixal/openclaw-nats-plugin 0.1.18 → 0.2.1
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/openclaw.plugin.json +1 -0
- package/package.json +4 -2
- package/plugins/nats-context-engine/http-handler.ts +1 -1
- package/plugins/nats-context-engine/index.ts +153 -0
- package/sidecar/bun.lock +8 -6
- package/sidecar/package.json +4 -4
- package/sidecar/src/app.module.ts +11 -2
- package/sidecar/src/consumer/consumer.controller.ts +29 -30
- package/sidecar/src/consumer/consumer.module.ts +3 -1
- package/sidecar/src/db/migrations/0003_wet_deathbird.sql +12 -0
- package/sidecar/src/db/migrations/0004_familiar_zaladane.sql +17 -0
- package/sidecar/src/db/migrations/meta/0003_snapshot.json +194 -0
- package/sidecar/src/db/migrations/meta/0004_snapshot.json +306 -0
- package/sidecar/src/db/migrations/meta/_journal.json +14 -0
- package/sidecar/src/db/schema.ts +35 -0
- package/sidecar/src/health/health.service.ts +1 -1
- package/sidecar/src/index.ts +6 -5
- package/sidecar/src/metrics/metrics.controller.ts +16 -0
- package/sidecar/src/metrics/metrics.module.ts +10 -0
- package/sidecar/src/metrics/metrics.service.ts +64 -0
- package/sidecar/src/publisher/publisher.controller.ts +2 -2
- package/sidecar/src/publisher/publisher.module.ts +2 -0
- package/sidecar/src/publisher/publisher.service.ts +6 -1
- package/sidecar/src/router/router.controller.ts +76 -0
- package/sidecar/src/router/router.module.ts +11 -0
- package/sidecar/src/router/router.repository.ts +78 -0
- package/sidecar/src/router/router.service.ts +73 -0
- package/sidecar/src/scheduler/scheduler.controller.ts +68 -0
- package/sidecar/src/scheduler/scheduler.module.ts +13 -0
- package/sidecar/src/scheduler/scheduler.repository.ts +64 -0
- package/sidecar/src/scheduler/scheduler.service.ts +138 -0
- package/sidecar/src/validation/schemas.ts +18 -0
- package/skills/nats-events/SKILL.md +106 -0
- package/skills/nats-events/scripts/nats-cron-trigger.sh +29 -0
- package/dashboard/dist/assets/index--UFIkwvP.js +0 -2
- package/dashboard/dist/assets/index-CafgidIc.css +0 -2
- package/dashboard/dist/index.html +0 -13
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnixal/openclaw-nats-plugin",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "NATS JetStream event-driven plugin for OpenClaw",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build:dashboard": "cd dashboard && bun run build",
|
|
13
|
-
"prepublishOnly": "bun run build:dashboard"
|
|
13
|
+
"prepublishOnly": "bun run build:dashboard",
|
|
14
|
+
"nats": "docker run -d --name nats -p 4222:4222 nats:2.10-alpine -js"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"index.ts",
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
"hooks/",
|
|
20
21
|
"plugins/",
|
|
21
22
|
"sidecar/",
|
|
23
|
+
"skills/",
|
|
22
24
|
"docker/",
|
|
23
25
|
"dashboard/dist/",
|
|
24
26
|
"openclaw.plugin.json",
|
|
@@ -78,6 +78,159 @@ export default function (api: any) {
|
|
|
78
78
|
}, { priority: 8 });
|
|
79
79
|
}, { priority: 99 });
|
|
80
80
|
|
|
81
|
+
// ── Agent Tools ─────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
const SIDECAR_URL = process.env.NATS_SIDECAR_URL || 'http://127.0.0.1:3104';
|
|
84
|
+
const SIDECAR_KEY = process.env.NATS_PLUGIN_API_KEY || 'dev-nats-plugin-key';
|
|
85
|
+
|
|
86
|
+
const sidecarFetch = async (path: string, options: RequestInit = {}) => {
|
|
87
|
+
const res = await fetch(`${SIDECAR_URL}${path}`, {
|
|
88
|
+
...options,
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
'Authorization': `Bearer ${SIDECAR_KEY}`,
|
|
92
|
+
...options.headers,
|
|
93
|
+
},
|
|
94
|
+
signal: AbortSignal.timeout(5000),
|
|
95
|
+
});
|
|
96
|
+
return res.json();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
api.registerTool({
|
|
100
|
+
name: 'nats_publish',
|
|
101
|
+
description: 'Publish an event to the NATS event bus. Use for cron triggers, custom events, task notifications.',
|
|
102
|
+
parameters: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
subject: { type: 'string', description: 'Event subject (must start with agent.events.)' },
|
|
106
|
+
payload: { type: 'object', description: 'Event payload data' },
|
|
107
|
+
},
|
|
108
|
+
required: ['subject', 'payload'],
|
|
109
|
+
},
|
|
110
|
+
async execute(_id: string, params: any) {
|
|
111
|
+
const result = await sidecarFetch('/api/publish', {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
body: JSON.stringify({ subject: params.subject, payload: params.payload }),
|
|
114
|
+
});
|
|
115
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
api.registerTool({
|
|
120
|
+
name: 'nats_subscribe',
|
|
121
|
+
description: 'Subscribe to events matching a pattern. Matched events will be delivered to the target session as messages.',
|
|
122
|
+
parameters: {
|
|
123
|
+
type: 'object',
|
|
124
|
+
properties: {
|
|
125
|
+
pattern: { type: 'string', description: 'Subject pattern (exact, or wildcard with * for one level, > for all descendants)' },
|
|
126
|
+
target: { type: 'string', description: 'Session key to deliver to (default: main)' },
|
|
127
|
+
},
|
|
128
|
+
required: ['pattern'],
|
|
129
|
+
},
|
|
130
|
+
async execute(_id: string, params: any) {
|
|
131
|
+
const result = await sidecarFetch('/api/routes', {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
body: JSON.stringify({ pattern: params.pattern, target: params.target ?? 'main' }),
|
|
134
|
+
});
|
|
135
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
api.registerTool({
|
|
140
|
+
name: 'nats_unsubscribe',
|
|
141
|
+
description: 'Remove an event subscription by its ID.',
|
|
142
|
+
parameters: {
|
|
143
|
+
type: 'object',
|
|
144
|
+
properties: {
|
|
145
|
+
id: { type: 'string', description: 'Route ID to delete (from nats_subscriptions)' },
|
|
146
|
+
},
|
|
147
|
+
required: ['id'],
|
|
148
|
+
},
|
|
149
|
+
async execute(_id: string, params: any) {
|
|
150
|
+
const result = await sidecarFetch(`/api/routes/${params.id}`, { method: 'DELETE' });
|
|
151
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
api.registerTool({
|
|
156
|
+
name: 'nats_subscriptions',
|
|
157
|
+
description: 'List event subscriptions. Optionally filter by pattern or target session.',
|
|
158
|
+
parameters: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
pattern: { type: 'string', description: 'Filter: show routes matching this pattern' },
|
|
162
|
+
target: { type: 'string', description: 'Filter: show routes delivering to this session' },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
async execute(_id: string, params: any) {
|
|
166
|
+
const qs = new URLSearchParams();
|
|
167
|
+
if (params?.pattern) qs.set('pattern', params.pattern);
|
|
168
|
+
if (params?.target) qs.set('target', params.target);
|
|
169
|
+
const path = qs.toString() ? `/api/routes?${qs}` : '/api/routes';
|
|
170
|
+
const result = await sidecarFetch(path);
|
|
171
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// ── Cron Scheduler Tools ────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
api.registerTool({
|
|
178
|
+
name: 'nats_cron_add',
|
|
179
|
+
description: 'Create or update a scheduled cron job that publishes a NATS event on a schedule. No LLM wake — fires directly.',
|
|
180
|
+
parameters: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
properties: {
|
|
183
|
+
name: { type: 'string', description: 'Unique job name (e.g., daily-report, hourly-check)' },
|
|
184
|
+
cron: { type: 'string', description: 'Cron expression (e.g., "0 9 * * *" for daily at 9am)' },
|
|
185
|
+
subject: { type: 'string', description: 'NATS subject to publish (must start with agent.events.)' },
|
|
186
|
+
payload: { type: 'object', description: 'Event payload data' },
|
|
187
|
+
timezone: { type: 'string', description: 'Timezone (default: UTC). e.g., Europe/Moscow' },
|
|
188
|
+
},
|
|
189
|
+
required: ['name', 'cron', 'subject'],
|
|
190
|
+
},
|
|
191
|
+
async execute(_id: string, params: any) {
|
|
192
|
+
const result = await sidecarFetch('/api/cron', {
|
|
193
|
+
method: 'POST',
|
|
194
|
+
body: JSON.stringify({
|
|
195
|
+
name: params.name,
|
|
196
|
+
cron: params.cron,
|
|
197
|
+
subject: params.subject,
|
|
198
|
+
payload: params.payload ?? {},
|
|
199
|
+
timezone: params.timezone,
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
api.registerTool({
|
|
207
|
+
name: 'nats_cron_remove',
|
|
208
|
+
description: 'Remove a scheduled cron job by name.',
|
|
209
|
+
parameters: {
|
|
210
|
+
type: 'object',
|
|
211
|
+
properties: {
|
|
212
|
+
name: { type: 'string', description: 'Job name to remove' },
|
|
213
|
+
},
|
|
214
|
+
required: ['name'],
|
|
215
|
+
},
|
|
216
|
+
async execute(_id: string, params: any) {
|
|
217
|
+
const result = await sidecarFetch(`/api/cron/${encodeURIComponent(params.name)}`, {
|
|
218
|
+
method: 'DELETE',
|
|
219
|
+
});
|
|
220
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
api.registerTool({
|
|
225
|
+
name: 'nats_cron_list',
|
|
226
|
+
description: 'List all scheduled cron jobs with their next run time and status.',
|
|
227
|
+
parameters: { type: 'object', properties: {} },
|
|
228
|
+
async execute() {
|
|
229
|
+
const result = await sidecarFetch('/api/cron');
|
|
230
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
81
234
|
// ── Dashboard UI ─────────────────────────────────────────────────
|
|
82
235
|
|
|
83
236
|
api.registerHttpRoute({
|
package/sidecar/bun.lock
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
"": {
|
|
6
6
|
"name": "@ai-entrepreneur/nats-sidecar",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@onebun/core": "^0.2.
|
|
8
|
+
"@onebun/core": "^0.2.14",
|
|
9
9
|
"@onebun/drizzle": "^0.2.4",
|
|
10
|
-
"@onebun/envs": "^0.2.
|
|
10
|
+
"@onebun/envs": "^0.2.2",
|
|
11
11
|
"@onebun/logger": "^0.2.1",
|
|
12
|
-
"@onebun/nats": "^0.2.
|
|
12
|
+
"@onebun/nats": "^0.2.6",
|
|
13
13
|
"arktype": "^2.2.0",
|
|
14
14
|
"ulid": "^2.3.0",
|
|
15
15
|
},
|
|
@@ -104,17 +104,17 @@
|
|
|
104
104
|
|
|
105
105
|
"@nats-io/transport-node": ["@nats-io/transport-node@3.3.1", "", { "dependencies": { "@nats-io/nats-core": "3.3.1", "@nats-io/nkeys": "2.0.3", "@nats-io/nuid": "2.0.3" } }, "sha512-GBvY0VcvyQEILgy5bjpqU1GpDYmSF06bW59I7cewZuNGS9u3AoV/gf+a+3ep45T/Z+UC661atq/b7x+QV12w+Q=="],
|
|
106
106
|
|
|
107
|
-
"@onebun/core": ["@onebun/core@0.2.
|
|
107
|
+
"@onebun/core": ["@onebun/core@0.2.14", "", { "dependencies": { "@onebun/envs": "^0.2.2", "@onebun/logger": "^0.2.1", "@onebun/metrics": "^0.2.2", "@onebun/requests": "^0.2.1", "@onebun/trace": "^0.2.1", "arktype": "^2.0.0", "effect": "^3.13.10" }, "peerDependencies": { "testcontainers": ">=10.0.0" }, "optionalPeers": ["testcontainers"] }, "sha512-uClu4Oez18y6BldubdE6R/I02Brrk5eXCoxxPatgRJ9qYRM+jKSTNmeZVoqqo7ai/rMYM9lJ1WuF9d7p6/RtDA=="],
|
|
108
108
|
|
|
109
109
|
"@onebun/drizzle": ["@onebun/drizzle@0.2.4", "", { "dependencies": { "@onebun/envs": "^0.2.1", "@onebun/logger": "^0.2.1", "arktype": "^2.0.0", "drizzle-arktype": "^0.1.3", "drizzle-kit": "^0.31.6", "drizzle-orm": "^0.44.7", "effect": "^3.13.10" }, "peerDependencies": { "@onebun/core": ">=0.2.0" }, "bin": { "onebun-drizzle": "bin/drizzle-kit.ts" } }, "sha512-LbkW2hU9pTKZU/rlrHNdwhI4jYoMl+v22c3G2zc0L0aW77nW7ZCfp5YqOJYufWJbfOTSWEnNOVZQXMueYhBxsA=="],
|
|
110
110
|
|
|
111
|
-
"@onebun/envs": ["@onebun/envs@0.2.
|
|
111
|
+
"@onebun/envs": ["@onebun/envs@0.2.2", "", { "dependencies": { "effect": "^3.13.10" } }, "sha512-WIjc1LpGnecYArSWsZhheyUSYJlo+iz9SA7ZfIXQnt1vkLd7ILCmVCtODBvqG9Mh86CMmromf1lCyRkjNZyLoA=="],
|
|
112
112
|
|
|
113
113
|
"@onebun/logger": ["@onebun/logger@0.2.1", "", { "dependencies": { "effect": "^3.13.10" } }, "sha512-u/zirsUSGBfbjVv274qqIHG5jzPBWY3vl8HzM6hjzsMCCpExgstkQiP1eP9rF1isIzhetwmyfBpYYc9eYsbrrw=="],
|
|
114
114
|
|
|
115
115
|
"@onebun/metrics": ["@onebun/metrics@0.2.2", "", { "dependencies": { "@onebun/requests": "^0.2.1", "effect": "^3.13.10", "prom-client": "^15.1.3" } }, "sha512-8oN74MZeaWyyPHi5H3pZyY0V3cM8ORupHe2fR0gGYsZqHyM4S9UJStV29rNQlvhyesHzL7p5x3Ux6n/SRYBszw=="],
|
|
116
116
|
|
|
117
|
-
"@onebun/nats": ["@onebun/nats@0.2.
|
|
117
|
+
"@onebun/nats": ["@onebun/nats@0.2.6", "", { "dependencies": { "@nats-io/jetstream": "^3.0.0-31", "@nats-io/transport-node": "^3.0.0-31", "effect": "^3.13.10" }, "peerDependencies": { "@onebun/core": ">=0.2.0" } }, "sha512-b7b0PUu0eGDLPbstOVjaCW0GCKsBzQb8qyYQmjKPu6RCsm02qvUU8HWDytoy01mYnQxWpI93qVogGiQKd6Ps4A=="],
|
|
118
118
|
|
|
119
119
|
"@onebun/requests": ["@onebun/requests@0.2.1", "", { "dependencies": { "effect": "^3.13.10" } }, "sha512-Wit+o3zRiuZOM7O0nAJ0rpFVLgkypJ1UR5uuHi0IZuiCvGmxv+Vus2+QqHoCL141L7SPO1Xlywt8dVqqu4NP7w=="],
|
|
120
120
|
|
|
@@ -446,6 +446,8 @@
|
|
|
446
446
|
|
|
447
447
|
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
|
448
448
|
|
|
449
|
+
"@onebun/drizzle/@onebun/envs": ["@onebun/envs@0.2.1", "", { "dependencies": { "effect": "^3.13.10" } }, "sha512-kiXJcA4ct194+aNJK8zkrVuaAgPPVpTkcW8tJU9XN9KOh8003lENOOuUZUcieMCxdMWUgo08lp9UgiwawLan+Q=="],
|
|
450
|
+
|
|
449
451
|
"bcrypt-pbkdf/tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
|
|
450
452
|
|
|
451
453
|
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
package/sidecar/package.json
CHANGED
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
"typecheck": "bunx tsc --noEmit",
|
|
12
12
|
"db:generate": "bunx onebun-drizzle generate",
|
|
13
13
|
"db:push": "bunx onebun-drizzle push",
|
|
14
|
-
"db:studio": "bunx onebun-drizzle studio"
|
|
14
|
+
"db:studio": "bunx onebun-drizzle studio",
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@onebun/core": "^0.2.
|
|
17
|
+
"@onebun/core": "^0.2.14",
|
|
18
18
|
"@onebun/drizzle": "^0.2.4",
|
|
19
|
-
"@onebun/envs": "^0.2.
|
|
19
|
+
"@onebun/envs": "^0.2.2",
|
|
20
20
|
"@onebun/logger": "^0.2.1",
|
|
21
|
-
"@onebun/nats": "^0.2.
|
|
21
|
+
"@onebun/nats": "^0.2.6",
|
|
22
22
|
"arktype": "^2.2.0",
|
|
23
23
|
"ulid": "^2.3.0"
|
|
24
24
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Module } from '@onebun/core';
|
|
1
|
+
import { getConfig, Module } from '@onebun/core';
|
|
2
2
|
import { DrizzleModule, DatabaseType } from '@onebun/drizzle';
|
|
3
|
+
import { envSchema, type AppConfig } from './config';
|
|
3
4
|
import { DedupModule } from './dedup/dedup.module';
|
|
4
5
|
import { PublisherModule } from './publisher/publisher.module';
|
|
5
6
|
import { PreHandlersModule } from './pre-handlers/pre-handlers.module';
|
|
@@ -7,6 +8,11 @@ import { GatewayClientModule } from './gateway/gateway-client.module';
|
|
|
7
8
|
import { ConsumerModule } from './consumer/consumer.module';
|
|
8
9
|
import { PendingModule } from './pending/pending.module';
|
|
9
10
|
import { HealthModule } from './health/health.module';
|
|
11
|
+
import { RouterModule } from './router/router.module';
|
|
12
|
+
import { SchedulerModule } from './scheduler/scheduler.module';
|
|
13
|
+
import { MetricsModule } from './metrics/metrics.module';
|
|
14
|
+
|
|
15
|
+
const config = getConfig<AppConfig>(envSchema);
|
|
10
16
|
|
|
11
17
|
@Module({
|
|
12
18
|
imports: [
|
|
@@ -14,7 +20,7 @@ import { HealthModule } from './health/health.module';
|
|
|
14
20
|
connection: {
|
|
15
21
|
type: DatabaseType.SQLITE,
|
|
16
22
|
options: {
|
|
17
|
-
url:
|
|
23
|
+
url: config.get('database.url'),
|
|
18
24
|
},
|
|
19
25
|
},
|
|
20
26
|
migrationsFolder: './src/db/migrations',
|
|
@@ -26,6 +32,9 @@ import { HealthModule } from './health/health.module';
|
|
|
26
32
|
ConsumerModule,
|
|
27
33
|
PendingModule,
|
|
28
34
|
HealthModule,
|
|
35
|
+
RouterModule,
|
|
36
|
+
SchedulerModule,
|
|
37
|
+
MetricsModule,
|
|
29
38
|
],
|
|
30
39
|
})
|
|
31
40
|
export class AppModule {}
|
|
@@ -2,6 +2,8 @@ import { Controller, BaseController, Subscribe, OnQueueReady, type Message } fro
|
|
|
2
2
|
import { PipelineService } from '../pre-handlers/pipeline.service';
|
|
3
3
|
import { GatewayClientService } from '../gateway/gateway-client.service';
|
|
4
4
|
import { PendingService } from '../pending/pending.service';
|
|
5
|
+
import { RouterService } from '../router/router.service';
|
|
6
|
+
import { MetricsService } from '../metrics/metrics.service';
|
|
5
7
|
import type { NatsEventEnvelope } from '../publisher/envelope';
|
|
6
8
|
|
|
7
9
|
@Controller('/consumer')
|
|
@@ -10,6 +12,8 @@ export class ConsumerController extends BaseController {
|
|
|
10
12
|
private pipeline: PipelineService,
|
|
11
13
|
private gatewayClient: GatewayClientService,
|
|
12
14
|
private pendingService: PendingService,
|
|
15
|
+
private routerService: RouterService,
|
|
16
|
+
private metrics: MetricsService,
|
|
13
17
|
) {
|
|
14
18
|
super();
|
|
15
19
|
}
|
|
@@ -20,7 +24,7 @@ export class ConsumerController extends BaseController {
|
|
|
20
24
|
this.logger.info(`Queue connected, consuming as ${consumerName}`);
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
@Subscribe('agent.
|
|
27
|
+
@Subscribe('agent.events.>', {
|
|
24
28
|
ackMode: 'manual',
|
|
25
29
|
group: 'openclaw-main',
|
|
26
30
|
})
|
|
@@ -29,29 +33,39 @@ export class ConsumerController extends BaseController {
|
|
|
29
33
|
const envelope = this.extractEnvelope(message);
|
|
30
34
|
|
|
31
35
|
const { result, ctx } = await this.pipeline.process(envelope);
|
|
32
|
-
|
|
33
36
|
if (result === 'drop') {
|
|
34
37
|
await message.ack();
|
|
35
38
|
return;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
//
|
|
41
|
+
// Check routing rules
|
|
42
|
+
const routes = await this.routerService.findMatchingRoutes(envelope.subject);
|
|
43
|
+
if (routes.length === 0) {
|
|
44
|
+
// No route — just ack (event is in JetStream for audit)
|
|
45
|
+
await message.ack();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Deliver to each matching target
|
|
39
50
|
if (this.gatewayClient.isAlive()) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
for (const route of routes) {
|
|
52
|
+
await this.gatewayClient.inject({
|
|
53
|
+
target: route.target,
|
|
54
|
+
message: this.formatMessage(envelope),
|
|
55
|
+
metadata: {
|
|
56
|
+
source: 'nats',
|
|
57
|
+
eventId: envelope.id,
|
|
58
|
+
subject: envelope.subject,
|
|
59
|
+
priority: (ctx.enrichments['priority'] as number) ?? envelope.meta?.priority ?? 5,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
await this.routerService.recordDelivery(route.id, envelope.subject);
|
|
63
|
+
this.metrics.recordConsume(envelope.subject);
|
|
64
|
+
}
|
|
50
65
|
await message.ack();
|
|
51
66
|
} else {
|
|
52
|
-
// Gateway not available — store as pending for ContextEngine pickup
|
|
53
67
|
await this.pendingService.addPending(envelope);
|
|
54
|
-
await message.ack();
|
|
68
|
+
await message.ack();
|
|
55
69
|
this.logger.warn(`Gateway unavailable, stored pending event ${envelope.id}`);
|
|
56
70
|
}
|
|
57
71
|
} catch (err) {
|
|
@@ -60,29 +74,14 @@ export class ConsumerController extends BaseController {
|
|
|
60
74
|
}
|
|
61
75
|
}
|
|
62
76
|
|
|
63
|
-
/**
|
|
64
|
-
* Extract the NatsEventEnvelope from the adapter message.
|
|
65
|
-
*
|
|
66
|
-
* The JetStreamQueueAdapter wraps messages in its own envelope:
|
|
67
|
-
* { id, pattern, data, timestamp, metadata }
|
|
68
|
-
*
|
|
69
|
-
* Our NatsEventEnvelope is inside `data` when published via PublisherService,
|
|
70
|
-
* or the raw data itself when published externally.
|
|
71
|
-
*/
|
|
72
77
|
private extractEnvelope(message: Message<unknown>): NatsEventEnvelope {
|
|
73
78
|
const data = message.data as any;
|
|
74
|
-
|
|
75
|
-
// If the data already looks like a NatsEventEnvelope (has id, subject, payload),
|
|
76
|
-
// use it directly.
|
|
77
79
|
if (data && typeof data === 'object' && 'subject' in data && 'payload' in data) {
|
|
78
80
|
return data as NatsEventEnvelope;
|
|
79
81
|
}
|
|
80
|
-
|
|
81
|
-
// Otherwise, treat it as a raw payload string that needs parsing
|
|
82
82
|
if (typeof data === 'string') {
|
|
83
83
|
return JSON.parse(data) as NatsEventEnvelope;
|
|
84
84
|
}
|
|
85
|
-
|
|
86
85
|
throw new Error('Unable to extract envelope from message');
|
|
87
86
|
}
|
|
88
87
|
|
|
@@ -2,9 +2,11 @@ import { Module } from '@onebun/core';
|
|
|
2
2
|
import { ConsumerController } from './consumer.controller';
|
|
3
3
|
import { PreHandlersModule } from '../pre-handlers/pre-handlers.module';
|
|
4
4
|
import { PendingModule } from '../pending/pending.module';
|
|
5
|
+
import { RouterModule } from '../router/router.module';
|
|
6
|
+
import { MetricsModule } from '../metrics/metrics.module';
|
|
5
7
|
|
|
6
8
|
@Module({
|
|
7
|
-
imports: [PreHandlersModule, PendingModule],
|
|
9
|
+
imports: [PreHandlersModule, PendingModule, RouterModule, MetricsModule],
|
|
8
10
|
controllers: [ConsumerController],
|
|
9
11
|
})
|
|
10
12
|
export class ConsumerModule {}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE TABLE `event_routes` (
|
|
2
|
+
`id` text PRIMARY KEY NOT NULL,
|
|
3
|
+
`pattern` text NOT NULL,
|
|
4
|
+
`target` text DEFAULT 'main' NOT NULL,
|
|
5
|
+
`enabled` integer DEFAULT true NOT NULL,
|
|
6
|
+
`priority` integer DEFAULT 5 NOT NULL,
|
|
7
|
+
`created_at` integer NOT NULL
|
|
8
|
+
);
|
|
9
|
+
--> statement-breakpoint
|
|
10
|
+
CREATE UNIQUE INDEX `event_routes_pattern_unique` ON `event_routes` (`pattern`);--> statement-breakpoint
|
|
11
|
+
CREATE INDEX `event_routes_pattern_idx` ON `event_routes` (`pattern`);--> statement-breakpoint
|
|
12
|
+
CREATE INDEX `event_routes_target_idx` ON `event_routes` (`target`);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
CREATE TABLE `cron_jobs` (
|
|
2
|
+
`id` text PRIMARY KEY NOT NULL,
|
|
3
|
+
`name` text NOT NULL,
|
|
4
|
+
`expr` text NOT NULL,
|
|
5
|
+
`subject` text NOT NULL,
|
|
6
|
+
`payload` text,
|
|
7
|
+
`timezone` text DEFAULT 'UTC' NOT NULL,
|
|
8
|
+
`enabled` integer DEFAULT true NOT NULL,
|
|
9
|
+
`last_run_at` integer,
|
|
10
|
+
`created_at` integer NOT NULL
|
|
11
|
+
);
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
CREATE UNIQUE INDEX `cron_jobs_name_unique` ON `cron_jobs` (`name`);--> statement-breakpoint
|
|
14
|
+
CREATE INDEX `cron_jobs_name_idx` ON `cron_jobs` (`name`);--> statement-breakpoint
|
|
15
|
+
ALTER TABLE `event_routes` ADD `last_delivered_at` integer;--> statement-breakpoint
|
|
16
|
+
ALTER TABLE `event_routes` ADD `last_event_subject` text;--> statement-breakpoint
|
|
17
|
+
ALTER TABLE `event_routes` ADD `delivery_count` integer DEFAULT 0 NOT NULL;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "6",
|
|
3
|
+
"dialect": "sqlite",
|
|
4
|
+
"id": "cb92737c-c257-4b58-bbf0-798ab1494961",
|
|
5
|
+
"prevId": "3836f3c7-1186-453d-bb01-c2f5e58a1f4f",
|
|
6
|
+
"tables": {
|
|
7
|
+
"dedup_events": {
|
|
8
|
+
"name": "dedup_events",
|
|
9
|
+
"columns": {
|
|
10
|
+
"event_id": {
|
|
11
|
+
"name": "event_id",
|
|
12
|
+
"type": "text",
|
|
13
|
+
"primaryKey": true,
|
|
14
|
+
"notNull": true,
|
|
15
|
+
"autoincrement": false
|
|
16
|
+
},
|
|
17
|
+
"subject": {
|
|
18
|
+
"name": "subject",
|
|
19
|
+
"type": "text",
|
|
20
|
+
"primaryKey": false,
|
|
21
|
+
"notNull": true,
|
|
22
|
+
"autoincrement": false
|
|
23
|
+
},
|
|
24
|
+
"seen_at": {
|
|
25
|
+
"name": "seen_at",
|
|
26
|
+
"type": "integer",
|
|
27
|
+
"primaryKey": false,
|
|
28
|
+
"notNull": true,
|
|
29
|
+
"autoincrement": false
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"indexes": {
|
|
33
|
+
"dedup_events_seen_at_idx": {
|
|
34
|
+
"name": "dedup_events_seen_at_idx",
|
|
35
|
+
"columns": [
|
|
36
|
+
"seen_at"
|
|
37
|
+
],
|
|
38
|
+
"isUnique": false
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"foreignKeys": {},
|
|
42
|
+
"compositePrimaryKeys": {},
|
|
43
|
+
"uniqueConstraints": {},
|
|
44
|
+
"checkConstraints": {}
|
|
45
|
+
},
|
|
46
|
+
"event_routes": {
|
|
47
|
+
"name": "event_routes",
|
|
48
|
+
"columns": {
|
|
49
|
+
"id": {
|
|
50
|
+
"name": "id",
|
|
51
|
+
"type": "text",
|
|
52
|
+
"primaryKey": true,
|
|
53
|
+
"notNull": true,
|
|
54
|
+
"autoincrement": false
|
|
55
|
+
},
|
|
56
|
+
"pattern": {
|
|
57
|
+
"name": "pattern",
|
|
58
|
+
"type": "text",
|
|
59
|
+
"primaryKey": false,
|
|
60
|
+
"notNull": true,
|
|
61
|
+
"autoincrement": false
|
|
62
|
+
},
|
|
63
|
+
"target": {
|
|
64
|
+
"name": "target",
|
|
65
|
+
"type": "text",
|
|
66
|
+
"primaryKey": false,
|
|
67
|
+
"notNull": true,
|
|
68
|
+
"autoincrement": false,
|
|
69
|
+
"default": "'main'"
|
|
70
|
+
},
|
|
71
|
+
"enabled": {
|
|
72
|
+
"name": "enabled",
|
|
73
|
+
"type": "integer",
|
|
74
|
+
"primaryKey": false,
|
|
75
|
+
"notNull": true,
|
|
76
|
+
"autoincrement": false,
|
|
77
|
+
"default": true
|
|
78
|
+
},
|
|
79
|
+
"priority": {
|
|
80
|
+
"name": "priority",
|
|
81
|
+
"type": "integer",
|
|
82
|
+
"primaryKey": false,
|
|
83
|
+
"notNull": true,
|
|
84
|
+
"autoincrement": false,
|
|
85
|
+
"default": 5
|
|
86
|
+
},
|
|
87
|
+
"created_at": {
|
|
88
|
+
"name": "created_at",
|
|
89
|
+
"type": "integer",
|
|
90
|
+
"primaryKey": false,
|
|
91
|
+
"notNull": true,
|
|
92
|
+
"autoincrement": false
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"indexes": {
|
|
96
|
+
"event_routes_pattern_unique": {
|
|
97
|
+
"name": "event_routes_pattern_unique",
|
|
98
|
+
"columns": [
|
|
99
|
+
"pattern"
|
|
100
|
+
],
|
|
101
|
+
"isUnique": true
|
|
102
|
+
},
|
|
103
|
+
"event_routes_pattern_idx": {
|
|
104
|
+
"name": "event_routes_pattern_idx",
|
|
105
|
+
"columns": [
|
|
106
|
+
"pattern"
|
|
107
|
+
],
|
|
108
|
+
"isUnique": false
|
|
109
|
+
},
|
|
110
|
+
"event_routes_target_idx": {
|
|
111
|
+
"name": "event_routes_target_idx",
|
|
112
|
+
"columns": [
|
|
113
|
+
"target"
|
|
114
|
+
],
|
|
115
|
+
"isUnique": false
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"foreignKeys": {},
|
|
119
|
+
"compositePrimaryKeys": {},
|
|
120
|
+
"uniqueConstraints": {},
|
|
121
|
+
"checkConstraints": {}
|
|
122
|
+
},
|
|
123
|
+
"pending_events": {
|
|
124
|
+
"name": "pending_events",
|
|
125
|
+
"columns": {
|
|
126
|
+
"id": {
|
|
127
|
+
"name": "id",
|
|
128
|
+
"type": "text",
|
|
129
|
+
"primaryKey": true,
|
|
130
|
+
"notNull": true,
|
|
131
|
+
"autoincrement": false
|
|
132
|
+
},
|
|
133
|
+
"session_key": {
|
|
134
|
+
"name": "session_key",
|
|
135
|
+
"type": "text",
|
|
136
|
+
"primaryKey": false,
|
|
137
|
+
"notNull": true,
|
|
138
|
+
"autoincrement": false
|
|
139
|
+
},
|
|
140
|
+
"subject": {
|
|
141
|
+
"name": "subject",
|
|
142
|
+
"type": "text",
|
|
143
|
+
"primaryKey": false,
|
|
144
|
+
"notNull": true,
|
|
145
|
+
"autoincrement": false
|
|
146
|
+
},
|
|
147
|
+
"payload": {
|
|
148
|
+
"name": "payload",
|
|
149
|
+
"type": "text",
|
|
150
|
+
"primaryKey": false,
|
|
151
|
+
"notNull": false,
|
|
152
|
+
"autoincrement": false
|
|
153
|
+
},
|
|
154
|
+
"priority": {
|
|
155
|
+
"name": "priority",
|
|
156
|
+
"type": "integer",
|
|
157
|
+
"primaryKey": false,
|
|
158
|
+
"notNull": true,
|
|
159
|
+
"autoincrement": false,
|
|
160
|
+
"default": 5
|
|
161
|
+
},
|
|
162
|
+
"created_at": {
|
|
163
|
+
"name": "created_at",
|
|
164
|
+
"type": "integer",
|
|
165
|
+
"primaryKey": false,
|
|
166
|
+
"notNull": true,
|
|
167
|
+
"autoincrement": false
|
|
168
|
+
},
|
|
169
|
+
"delivered_at": {
|
|
170
|
+
"name": "delivered_at",
|
|
171
|
+
"type": "integer",
|
|
172
|
+
"primaryKey": false,
|
|
173
|
+
"notNull": false,
|
|
174
|
+
"autoincrement": false
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"indexes": {},
|
|
178
|
+
"foreignKeys": {},
|
|
179
|
+
"compositePrimaryKeys": {},
|
|
180
|
+
"uniqueConstraints": {},
|
|
181
|
+
"checkConstraints": {}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
"views": {},
|
|
185
|
+
"enums": {},
|
|
186
|
+
"_meta": {
|
|
187
|
+
"schemas": {},
|
|
188
|
+
"tables": {},
|
|
189
|
+
"columns": {}
|
|
190
|
+
},
|
|
191
|
+
"internal": {
|
|
192
|
+
"indexes": {}
|
|
193
|
+
}
|
|
194
|
+
}
|