@omnixal/openclaw-nats-plugin 0.2.10 → 0.2.11
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/PLUGIN.md +5 -1
- package/dashboard/src/lib/EventsReferenceModal.svelte +59 -0
- package/dashboard/src/lib/MetricsPanel.svelte +12 -1
- package/dashboard/src/lib/components/ui/modal/modal.svelte +3 -1
- package/package.json +1 -1
- package/plugins/nats-context-engine/index.ts +46 -0
- package/sidecar/src/consumer/consumer.controller.ts +2 -7
- package/sidecar/src/gateway/gateway-client.service.ts +6 -10
- package/sidecar/src/logs/log.repository.ts +1 -1
- package/sidecar/src/logs/log.service.ts +1 -1
- package/sidecar/src/pending/pending.repository.ts +1 -1
- package/sidecar/src/router/router.repository.ts +1 -1
package/PLUGIN.md
CHANGED
|
@@ -53,6 +53,7 @@ Plugin hooks make lightweight HTTP calls to a NATS sidecar service for event pub
|
|
|
53
53
|
| Subject | Trigger |
|
|
54
54
|
|---|---|
|
|
55
55
|
| `agent.events.gateway.startup` | Gateway starts |
|
|
56
|
+
| `agent.events.gateway.stopped` | Gateway stops |
|
|
56
57
|
| `agent.events.session.new` | `/new` command |
|
|
57
58
|
| `agent.events.session.reset` | `/reset` command |
|
|
58
59
|
| `agent.events.session.stop` | `/stop` command |
|
|
@@ -60,10 +61,13 @@ Plugin hooks make lightweight HTTP calls to a NATS sidecar service for event pub
|
|
|
60
61
|
| `agent.events.session.ended` | Session ends |
|
|
61
62
|
| `agent.events.tool.{name}.completed` | Tool succeeds |
|
|
62
63
|
| `agent.events.tool.{name}.failed` | Tool fails |
|
|
64
|
+
| `agent.events.message.received` | Inbound message received |
|
|
65
|
+
| `agent.events.message.sent` | Message delivered |
|
|
66
|
+
| `agent.events.llm.output` | LLM response received |
|
|
67
|
+
| `agent.events.subagent.spawning` | Subagent about to spawn |
|
|
63
68
|
| `agent.events.subagent.spawned` | Subagent created |
|
|
64
69
|
| `agent.events.subagent.ended` | Subagent finished |
|
|
65
70
|
| `agent.events.agent.run_ended` | Agent run completes |
|
|
66
|
-
| `agent.events.message.sent` | Message delivered |
|
|
67
71
|
| `agent.events.context.compacted` | Context history compressed |
|
|
68
72
|
|
|
69
73
|
## Dashboard
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Modal } from '$lib/components/ui/modal';
|
|
3
|
+
import * as Table from '$lib/components/ui/table';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
open: boolean;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { open, onClose }: Props = $props();
|
|
11
|
+
|
|
12
|
+
const events = [
|
|
13
|
+
{ subject: 'agent.events.gateway.startup', trigger: 'Gateway started', payload: '{}' },
|
|
14
|
+
{ subject: 'agent.events.gateway.stopped', trigger: 'Gateway stopped', payload: '{ reason }' },
|
|
15
|
+
{ subject: 'agent.events.session.new', trigger: '/new command', payload: '{ sessionKey, command }' },
|
|
16
|
+
{ subject: 'agent.events.session.reset', trigger: '/reset command', payload: '{ sessionKey, command }' },
|
|
17
|
+
{ subject: 'agent.events.session.stop', trigger: '/stop command', payload: '{ sessionKey, command }' },
|
|
18
|
+
{ subject: 'agent.events.session.started', trigger: 'Session began', payload: '{ sessionKey, sessionId, channel }' },
|
|
19
|
+
{ subject: 'agent.events.session.ended', trigger: 'Session ended', payload: '{ sessionKey, sessionId, channel }' },
|
|
20
|
+
{ subject: 'agent.events.agent.run_ended', trigger: 'Agent run completed', payload: '{ sessionKey, runId, messageCount }' },
|
|
21
|
+
{ subject: 'agent.events.tool.{name}.completed', trigger: 'Tool succeeded', payload: '{ sessionKey, toolName, durationMs }' },
|
|
22
|
+
{ subject: 'agent.events.tool.{name}.failed', trigger: 'Tool failed', payload: '{ sessionKey, toolName, durationMs }' },
|
|
23
|
+
{ subject: 'agent.events.message.received', trigger: 'Inbound message', payload: '{ from, content, metadata }' },
|
|
24
|
+
{ subject: 'agent.events.message.sent', trigger: 'Message delivered', payload: '{ sessionKey, to, success, error }' },
|
|
25
|
+
{ subject: 'agent.events.llm.output', trigger: 'LLM response', payload: '{ provider, model, usage }' },
|
|
26
|
+
{ subject: 'agent.events.subagent.spawning', trigger: 'Subagent creating', payload: '{ childSessionKey, agentId, label, mode }' },
|
|
27
|
+
{ subject: 'agent.events.subagent.spawned', trigger: 'Subagent created', payload: '{ sessionKey, subagentId, task }' },
|
|
28
|
+
{ subject: 'agent.events.subagent.ended', trigger: 'Subagent finished', payload: '{ sessionKey, subagentId, result, durationMs }' },
|
|
29
|
+
{ subject: 'agent.events.context.compacted', trigger: 'Context compressed', payload: '{ sessionKey }' },
|
|
30
|
+
{ subject: 'agent.events.cron.*', trigger: 'Cron trigger', payload: '(user-defined)' },
|
|
31
|
+
{ subject: 'agent.events.custom.*', trigger: 'Custom event', payload: '(user-defined)' },
|
|
32
|
+
];
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<Modal {open} title="Events Reference" {onClose} class="max-w-3xl">
|
|
36
|
+
<div class="max-h-[60vh] overflow-y-auto -mx-6 px-6">
|
|
37
|
+
<p class="text-xs text-muted-foreground mb-3">
|
|
38
|
+
All events published by the NATS plugin. Each event also includes a <code class="text-xs">timestamp</code> field.
|
|
39
|
+
</p>
|
|
40
|
+
<Table.Root>
|
|
41
|
+
<Table.Header>
|
|
42
|
+
<Table.Row>
|
|
43
|
+
<Table.Head>Subject</Table.Head>
|
|
44
|
+
<Table.Head>Trigger</Table.Head>
|
|
45
|
+
<Table.Head>Payload</Table.Head>
|
|
46
|
+
</Table.Row>
|
|
47
|
+
</Table.Header>
|
|
48
|
+
<Table.Body>
|
|
49
|
+
{#each events as ev}
|
|
50
|
+
<Table.Row>
|
|
51
|
+
<Table.Cell class="font-mono text-xs whitespace-nowrap">{ev.subject}</Table.Cell>
|
|
52
|
+
<Table.Cell class="text-xs">{ev.trigger}</Table.Cell>
|
|
53
|
+
<Table.Cell class="font-mono text-xs text-muted-foreground">{ev.payload}</Table.Cell>
|
|
54
|
+
</Table.Row>
|
|
55
|
+
{/each}
|
|
56
|
+
</Table.Body>
|
|
57
|
+
</Table.Root>
|
|
58
|
+
</div>
|
|
59
|
+
</Modal>
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import * as Table from '$lib/components/ui/table';
|
|
3
3
|
import { Badge } from '$lib/components/ui/badge';
|
|
4
|
+
import { Button } from '$lib/components/ui/button';
|
|
4
5
|
import * as Card from '$lib/components/ui/card';
|
|
5
6
|
import type { SubjectMetric } from '$lib/api';
|
|
6
7
|
import { relativeAge } from '$lib/utils';
|
|
8
|
+
import CircleHelp from '@lucide/svelte/icons/circle-help';
|
|
9
|
+
import EventsReferenceModal from './EventsReferenceModal.svelte';
|
|
7
10
|
|
|
8
11
|
interface Props {
|
|
9
12
|
metrics: SubjectMetric[];
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
let { metrics }: Props = $props();
|
|
16
|
+
let showEventsRef = $state(false);
|
|
13
17
|
|
|
14
18
|
let totalPublished = $derived(metrics.reduce((acc, m) => acc + m.published, 0));
|
|
15
19
|
let totalConsumed = $derived(metrics.reduce((acc, m) => acc + m.consumed, 0));
|
|
@@ -17,7 +21,12 @@
|
|
|
17
21
|
|
|
18
22
|
<Card.Root>
|
|
19
23
|
<Card.Header class="pb-2">
|
|
20
|
-
<
|
|
24
|
+
<div class="flex items-center justify-between">
|
|
25
|
+
<Card.Title class="text-sm font-medium">Queue Metrics</Card.Title>
|
|
26
|
+
<Button variant="ghost" size="icon-sm" onclick={() => showEventsRef = true} title="Events reference">
|
|
27
|
+
<CircleHelp size={14} />
|
|
28
|
+
</Button>
|
|
29
|
+
</div>
|
|
21
30
|
</Card.Header>
|
|
22
31
|
<Card.Content>
|
|
23
32
|
<div class="flex items-center gap-2 mb-3">
|
|
@@ -58,3 +67,5 @@
|
|
|
58
67
|
{/if}
|
|
59
68
|
</Card.Content>
|
|
60
69
|
</Card.Root>
|
|
70
|
+
|
|
71
|
+
<EventsReferenceModal open={showEventsRef} onClose={() => showEventsRef = false} />
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
onClose: () => void;
|
|
9
9
|
children: Snippet;
|
|
10
10
|
actions?: Snippet;
|
|
11
|
+
class?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
let {
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
onClose,
|
|
17
18
|
children,
|
|
18
19
|
actions,
|
|
20
|
+
class: className,
|
|
19
21
|
}: Props = $props();
|
|
20
22
|
|
|
21
23
|
function handleKeydown(e: KeyboardEvent) {
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
>
|
|
32
34
|
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
33
35
|
<div class="fixed inset-0" onclick={onClose}></div>
|
|
34
|
-
<div class="relative z-10 w-full max-w-lg rounded-lg border bg-background p-6 shadow-lg">
|
|
36
|
+
<div class="relative z-10 w-full {className ?? 'max-w-lg'} rounded-lg border bg-background p-6 shadow-lg">
|
|
35
37
|
<div class="flex items-center justify-between mb-4">
|
|
36
38
|
<h3 class="text-lg font-semibold">{title}</h3>
|
|
37
39
|
<Button variant="ghost" size="icon-sm" onclick={onClose}>
|
package/package.json
CHANGED
|
@@ -78,6 +78,52 @@ export default function (api: any) {
|
|
|
78
78
|
}, { priority: 8 });
|
|
79
79
|
}, { priority: 99 });
|
|
80
80
|
|
|
81
|
+
// ── Gateway stop ───────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
api.on('gateway_stop', (event: any) => {
|
|
84
|
+
void publishToSidecar('agent.events.gateway.stopped', {
|
|
85
|
+
reason: event.reason,
|
|
86
|
+
timestamp: new Date().toISOString(),
|
|
87
|
+
});
|
|
88
|
+
}, { priority: 99 });
|
|
89
|
+
|
|
90
|
+
// ── Message received ─────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
api.on('message_received', (event: any) => {
|
|
93
|
+
void publishToSidecar('agent.events.message.received', {
|
|
94
|
+
from: event.from,
|
|
95
|
+
content: event.content,
|
|
96
|
+
metadata: event.metadata,
|
|
97
|
+
timestamp: new Date().toISOString(),
|
|
98
|
+
});
|
|
99
|
+
}, { priority: 99 });
|
|
100
|
+
|
|
101
|
+
// ── LLM output ───────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
api.on('llm_output', (event: any) => {
|
|
104
|
+
void publishToSidecar('agent.events.llm.output', {
|
|
105
|
+
sessionKey: event.sessionId,
|
|
106
|
+
runId: event.runId,
|
|
107
|
+
provider: event.provider,
|
|
108
|
+
model: event.model,
|
|
109
|
+
usage: event.usage,
|
|
110
|
+
timestamp: new Date().toISOString(),
|
|
111
|
+
});
|
|
112
|
+
}, { priority: 99 });
|
|
113
|
+
|
|
114
|
+
// ── Subagent spawning ────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
api.on('subagent_spawning', (event: any) => {
|
|
117
|
+
void publishToSidecar('agent.events.subagent.spawning', {
|
|
118
|
+
childSessionKey: event.childSessionKey,
|
|
119
|
+
agentId: event.agentId,
|
|
120
|
+
label: event.label,
|
|
121
|
+
mode: event.mode,
|
|
122
|
+
timestamp: new Date().toISOString(),
|
|
123
|
+
});
|
|
124
|
+
return { status: 'ok' };
|
|
125
|
+
}, { priority: 99 });
|
|
126
|
+
|
|
81
127
|
// ── Agent Tools ─────────────────────────────────────────────────
|
|
82
128
|
|
|
83
129
|
const SIDECAR_URL = process.env.NATS_SIDECAR_URL || 'http://127.0.0.1:3104';
|
|
@@ -58,14 +58,9 @@ export class ConsumerController extends BaseController {
|
|
|
58
58
|
try {
|
|
59
59
|
const injectStart = performance.now();
|
|
60
60
|
await this.gatewayClient.inject({
|
|
61
|
-
|
|
61
|
+
to: route.target,
|
|
62
62
|
message: this.formatMessage(envelope),
|
|
63
|
-
|
|
64
|
-
source: 'nats',
|
|
65
|
-
eventId: envelope.id,
|
|
66
|
-
subject: envelope.subject,
|
|
67
|
-
priority: (ctx.enrichments['priority'] as number) ?? envelope.meta?.priority ?? 5,
|
|
68
|
-
},
|
|
63
|
+
eventId: envelope.id,
|
|
69
64
|
});
|
|
70
65
|
const lagMs = Math.round(performance.now() - injectStart);
|
|
71
66
|
await this.routerService.recordDelivery(route.id, envelope.subject, lagMs);
|
|
@@ -3,14 +3,11 @@ import path from 'node:path';
|
|
|
3
3
|
import { loadOrCreateIdentity, publicKeyToBase64Url, signChallenge, type DeviceIdentity } from './device-identity';
|
|
4
4
|
|
|
5
5
|
export interface GatewayInjectPayload {
|
|
6
|
-
|
|
6
|
+
/** OpenClaw session key or recipient address (maps to `to` in the send frame) */
|
|
7
|
+
to: string;
|
|
7
8
|
message: string;
|
|
8
|
-
metadata
|
|
9
|
-
|
|
10
|
-
eventId: string;
|
|
11
|
-
subject: string;
|
|
12
|
-
priority: number;
|
|
13
|
-
};
|
|
9
|
+
/** Internal tracking metadata — NOT sent to gateway (additionalProperties: false) */
|
|
10
|
+
eventId?: string;
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
export class GatewayRpcError extends Error {
|
|
@@ -266,10 +263,9 @@ export class GatewayClientService extends BaseService implements OnModuleInit, O
|
|
|
266
263
|
id,
|
|
267
264
|
method: 'send',
|
|
268
265
|
params: {
|
|
269
|
-
|
|
266
|
+
to: payload.to,
|
|
270
267
|
message: payload.message,
|
|
271
|
-
|
|
272
|
-
idempotencyKey: payload.metadata?.eventId ?? String(this.requestId),
|
|
268
|
+
idempotencyKey: payload.eventId ?? String(this.requestId),
|
|
273
269
|
},
|
|
274
270
|
});
|
|
275
271
|
return promise;
|
|
@@ -56,7 +56,7 @@ export class LogRepository extends BaseService {
|
|
|
56
56
|
.select({ count: sql<number>`count(*)` })
|
|
57
57
|
.from(executionLogs)
|
|
58
58
|
.where(this.buildWhereConditions(filters));
|
|
59
|
-
return result[0]?.count ?? 0;
|
|
59
|
+
return (result[0] as unknown as { count: number })?.count ?? 0;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
async findRecent(limit: number = 20): Promise<DbExecutionLog[]> {
|
|
@@ -58,7 +58,7 @@ export class LogService extends BaseService {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
async logError(entityType: 'route' | 'cron', entityId: string, subject: string, error: unknown): Promise<void> {
|
|
61
|
+
async logError(entityType: 'route' | 'cron' | 'timer', entityId: string, subject: string, error: unknown): Promise<void> {
|
|
62
62
|
try {
|
|
63
63
|
const detail = error instanceof Error
|
|
64
64
|
? JSON.stringify({ message: error.message, stack: error.stack })
|
|
@@ -49,7 +49,7 @@ export class PendingRepository extends BaseService {
|
|
|
49
49
|
.select({ count: sql<number>`count(*)` })
|
|
50
50
|
.from(pendingEvents)
|
|
51
51
|
.where(isNull(pendingEvents.deliveredAt));
|
|
52
|
-
return result[0]?.count ?? 0;
|
|
52
|
+
return (result[0] as unknown as { count: number })?.count ?? 0;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
async cleanup(ttlSeconds: number): Promise<number> {
|