agentmetrics-openclaw 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,9 @@
1
1
  # agentmetrics-openclaw
2
2
 
3
- [![npm](https://img.shields.io/npm/v/agentmetrics-openclaw?color=009E80&label=npm&logo=npm&logoColor=white)](https://www.npmjs.com/package/agentmetrics-openclaw)
4
- [![License: MIT](https://img.shields.io/badge/license-MIT-009E80)](../LICENSE)
3
+ [![npm](https://img.shields.io/npm/v/agentmetrics-openclaw?color=6366f1&label=npm&logo=npm&logoColor=white)](https://www.npmjs.com/package/agentmetrics-openclaw)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-6366f1)](../LICENSE)
5
5
 
6
- Zero-code observability for [OpenClaw](https://openclaw.dev) agents. Install the plugin, set your API key, and every agent session is tracked automatically tokens, cost, tools, subagents, context health, and reliability.
7
-
8
- → **[agentmetrics.dev/docs/integrations/openclaw](https://agentmetrics.dev/docs/integrations/openclaw)**
6
+ AgentMetrics integration for [OpenClaw](https://openclaw.dev). Install the plugin, point it at your server, and every agent session reports back to your dashboard automatically showing tokens, cost, tools, subagents, context health, and reliability, all without changing your agent code.
9
7
 
10
8
  ---
11
9
 
@@ -13,7 +11,7 @@ Zero-code observability for [OpenClaw](https://openclaw.dev) agents. Install the
13
11
 
14
12
  - OpenClaw 2026.3.2 or later
15
13
  - Node.js 22 or later
16
- - An AgentMetrics API key [get one at agentmetrics.dev](https://agentmetrics.dev/signup)
14
+ - A running AgentMetrics server (see the [main README](../../../README.md) for setup)
17
15
 
18
16
  ---
19
17
 
@@ -27,35 +25,60 @@ openclaw plugins install agentmetrics-openclaw
27
25
 
28
26
  ## Setup
29
27
 
30
- **1. Set your API key**
28
+ **1. Start AgentMetrics** (if not already running)
29
+
30
+ ```bash
31
+ # Docker
32
+ docker compose up
33
+
34
+ # Or Python CLI
35
+ pip install agentmetrics
36
+ agentmetrics dashboard
37
+ ```
38
+
39
+ **2. Set the server URL** (if not running on localhost)
31
40
 
32
41
  ```bash
33
- # macOS / Linux
34
- echo 'export AGENTMETRICS_API_KEY=am_live_...' >> ~/.bashrc && source ~/.bashrc
42
+ # macOS / Linux, permanent
43
+ echo 'export AGENTMETRICS_BASE_URL=http://your-server:8099' >> ~/.bashrc && source ~/.bashrc
35
44
 
36
45
  # Windows (PowerShell)
37
- $Env:AGENTMETRICS_API_KEY = "am_live_..."
46
+ $Env:AGENTMETRICS_BASE_URL = "http://your-server:8099"
38
47
  ```
39
48
 
40
- **2. Trust the plugin** (silences the security scan advisory)
49
+ Omit this step if your server runs on `http://localhost:8099` (the default).
50
+
51
+ **3. Trust the plugin** (silences the security scan advisory)
41
52
 
42
53
  ```bash
43
54
  openclaw config set plugins.allow '["agentmetrics"]'
44
55
  ```
45
56
 
46
- **3. Restart the gateway**
57
+ **4. Restart the gateway**
47
58
 
48
59
  ```bash
49
60
  openclaw gateway restart
50
61
  ```
51
62
 
52
- **4. Verify**
63
+ **5. Verify**
53
64
 
54
65
  ```bash
55
66
  openclaw plugins list
56
67
  # agentmetrics loaded
57
68
  ```
58
69
 
70
+ **6. Set your agent name** (recommended)
71
+
72
+ In your agent's `openclaw.json`:
73
+
74
+ ```json
75
+ {
76
+ "name": "my-agent"
77
+ }
78
+ ```
79
+
80
+ The `name` field becomes the agent ID in your dashboard. Give each agent a distinct name.
81
+
59
82
  ---
60
83
 
61
84
  ## What gets tracked
@@ -76,15 +99,15 @@ Every agent session reports automatically:
76
99
 
77
100
  ## Troubleshooting
78
101
 
79
- **"dangerous code patterns" warning on install**
80
- Safe to ignore. The plugin reads `AGENTMETRICS_API_KEY` and sends it as a Bearer token to the AgentMetrics API. Add `agentmetrics` to `plugins.allow` to suppress it permanently.
102
+ **"dangerous code patterns" warning on install**
103
+ Safe to ignore. The plugin reads `AGENTMETRICS_BASE_URL` and makes network calls to the AgentMetrics API. Add `agentmetrics` to `plugins.allow` to suppress it permanently.
81
104
 
82
- **"manifest id does not match package name" warning**
105
+ **"manifest id does not match package name" warning**
83
106
  Not an error. The plugin's internal manifest id is `agentmetrics`; the npm package name is `agentmetrics-openclaw`. Use `agentmetrics` (not the npm name) in `plugins.allow`.
84
107
 
85
- **Runs not appearing in the dashboard**
86
- 1. Check the key is set: `echo $AGENTMETRICS_API_KEY`
87
- 2. Verify the plugin loads: `openclaw plugins list`
108
+ **Runs not appearing in the dashboard**
109
+ 1. Verify the plugin loads: `openclaw plugins list`
110
+ 2. Check the server is reachable: `openclaw agentmetrics test`
88
111
  3. Restart the gateway after any env var change
89
112
  4. Confirm your `openclaw.json` has a `name` field
90
113
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "agentmetrics",
3
3
  "name": "AgentMetrics",
4
- "description": "360-degree observability for every OpenClaw agent tokens, tools, latency, cost, subagents, context health, and reliability.",
4
+ "description": "360-degree observability for every OpenClaw agent - tokens, tools, latency, cost, subagents, context health, and reliability.",
5
5
  "configSchema": {
6
6
  "type": "object",
7
7
  "properties": {
@@ -11,7 +11,55 @@
11
11
  },
12
12
  "endpoint": {
13
13
  "type": "string",
14
- "description": "Custom API endpoint (default: https://api.agentmetrics.dev)"
14
+ "description": "Custom API endpoint (default: http://localhost:8099)"
15
+ },
16
+ "enabled": {
17
+ "type": "boolean",
18
+ "description": "Master toggle - set to false to disable all telemetry without uninstalling (default: true)",
19
+ "default": true
20
+ },
21
+ "flushIntervalSeconds": {
22
+ "type": "integer",
23
+ "description": "How often to flush the event batch to the API in seconds (default: 10)",
24
+ "default": 10
25
+ },
26
+ "maxBatchSize": {
27
+ "type": "integer",
28
+ "description": "Maximum number of events per batch request (default: 100)",
29
+ "default": 100
30
+ },
31
+ "maxQueueSize": {
32
+ "type": "integer",
33
+ "description": "Maximum number of events to hold in the local queue before dropping (FIFO, default: 10000)",
34
+ "default": 10000
35
+ },
36
+ "retryMaxAttempts": {
37
+ "type": "integer",
38
+ "description": "Maximum retry attempts per event batch before moving to dead-letter queue (default: 5)",
39
+ "default": 5
40
+ },
41
+ "redactionMode": {
42
+ "type": "string",
43
+ "enum": ["strict", "moderate", "debug"],
44
+ "description": "Redaction policy: strict = all sensitive fields scrubbed (default); moderate = errors and secrets only; debug = opt-in full detail, auto-expires after 1h",
45
+ "default": "strict"
46
+ },
47
+ "toolNameExport": {
48
+ "type": "string",
49
+ "enum": ["allowlist", "blocklist", "hash", "off"],
50
+ "description": "How tool names are exported: blocklist = export all except redactToolNames (default); allowlist = export only listed; hash = pseudonymise; off = never export",
51
+ "default": "blocklist"
52
+ },
53
+ "redactToolNames": {
54
+ "type": "array",
55
+ "items": { "type": "string" },
56
+ "description": "Tool names to always redact regardless of toolNameExport mode (default: [])",
57
+ "default": []
58
+ },
59
+ "compressPayloads": {
60
+ "type": "boolean",
61
+ "description": "Gzip-compress batch payloads before sending (requires server-side Content-Encoding: gzip support, default: false)",
62
+ "default": false
15
63
  }
16
64
  },
17
65
  "additionalProperties": false
package/package.json CHANGED
@@ -1,26 +1,53 @@
1
1
  {
2
2
  "name": "agentmetrics-openclaw",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "AgentMetrics observability plugin for OpenClaw agents",
6
6
  "license": "MIT",
7
- "homepage": "https://agentmetrics.dev",
7
+ "homepage": "https://github.com/andalabx/agentmetrics",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "git+https://github.com/andalabx/agentmetrics-sdk.git"
10
+ "url": "git+https://github.com/andalabx/agentmetrics.git"
11
11
  },
12
- "keywords": ["openclaw", "agentmetrics", "observability", "ai-agents", "monitoring"],
13
- "peerDependencies": {
14
- "openclaw": ">=2026.3.2"
12
+ "keywords": [
13
+ "openclaw",
14
+ "agentmetrics",
15
+ "observability",
16
+ "ai-agents",
17
+ "monitoring"
18
+ ],
19
+ "exports": {
20
+ ".": "./dist/index.js"
15
21
  },
16
22
  "openclaw": {
17
- "extensions": ["./index.ts"]
23
+ "extensions": [
24
+ "./dist/index.js"
25
+ ]
18
26
  },
19
27
  "files": [
20
- "index.ts",
28
+ "dist",
21
29
  "openclaw.plugin.json",
22
30
  "README.md"
23
31
  ],
32
+ "scripts": {
33
+ "build": "tsup",
34
+ "prepublishOnly": "npm run build"
35
+ },
36
+ "peerDependencies": {
37
+ "openclaw": ">=2026.3.2"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "openclaw": {
41
+ "optional": true
42
+ }
43
+ },
44
+ "devDependencies": {
45
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
46
+ "@typescript-eslint/parser": "^8.0.0",
47
+ "eslint": "^9.0.0",
48
+ "tsup": "^8.0.0",
49
+ "typescript": "^5.4.0"
50
+ },
24
51
  "engines": {
25
52
  "node": ">=22"
26
53
  }
package/index.ts DELETED
@@ -1,604 +0,0 @@
1
- import { randomUUID } from "crypto";
2
-
3
- // pluginConfig overrides env vars — set once in register()
4
- let API_KEY: string | undefined;
5
- let BASE_URL: string;
6
-
7
- interface PluginApi {
8
- config: Record<string, unknown>;
9
- pluginConfig?: Record<string, unknown>;
10
- registerAutoEnableProbe?: (probe: () => boolean) => void;
11
- registerCli?: (registrar: {
12
- name: string;
13
- description: string;
14
- commands: Array<{ name: string; description: string; handler: () => void }>;
15
- }) => void;
16
- on: (hookName: string, handler: (...args: unknown[]) => void) => void;
17
- }
18
-
19
- // ─── Types (sourced from openclaw/src/plugins/hook-types.ts) ──────────────────
20
-
21
- type AgentContext = {
22
- runId?: string;
23
- agentId?: string;
24
- sessionKey?: string;
25
- sessionId?: string;
26
- modelId?: string;
27
- modelProviderId?: string;
28
- };
29
-
30
- type SessionContext = {
31
- sessionId: string;
32
- sessionKey?: string;
33
- agentId?: string;
34
- };
35
-
36
- type SessionStartEvent = {
37
- sessionId: string;
38
- sessionKey?: string;
39
- resumedFrom?: string;
40
- };
41
-
42
- type SessionEndEvent = {
43
- sessionId: string;
44
- sessionKey?: string;
45
- durationMs?: number;
46
- messageCount: number;
47
- reason?: string;
48
- transcriptArchived?: boolean;
49
- };
50
-
51
- type LlmInputEvent = {
52
- runId: string;
53
- sessionId: string;
54
- provider: string;
55
- model: string;
56
- systemPrompt?: string;
57
- prompt: string;
58
- historyMessages: unknown[];
59
- imagesCount: number;
60
- };
61
-
62
- type LlmOutputEvent = {
63
- runId: string;
64
- sessionId: string;
65
- provider: string;
66
- model: string;
67
- assistantTexts: string[];
68
- usage?: {
69
- input?: number;
70
- output?: number;
71
- cacheRead?: number;
72
- cacheWrite?: number;
73
- total?: number;
74
- };
75
- };
76
-
77
- type BeforeToolCallEvent = {
78
- toolName: string;
79
- params: Record<string, unknown>;
80
- runId?: string;
81
- toolCallId?: string;
82
- };
83
-
84
- type AfterToolCallEvent = {
85
- toolName: string;
86
- params: Record<string, unknown>;
87
- runId?: string;
88
- toolCallId?: string;
89
- result?: unknown;
90
- error?: string;
91
- durationMs?: number;
92
- };
93
-
94
- type ToolContext = {
95
- agentId?: string;
96
- sessionKey?: string;
97
- sessionId?: string;
98
- runId?: string;
99
- toolName: string;
100
- toolCallId?: string;
101
- };
102
-
103
- type AgentEndEvent = {
104
- messages: unknown[];
105
- success: boolean;
106
- error?: string;
107
- durationMs?: number;
108
- };
109
-
110
- type BeforeAgentStartEvent = {
111
- agentId?: string;
112
- sessionKey?: string;
113
- sessionId?: string;
114
- };
115
-
116
- type SubagentSpawningEvent = {
117
- childSessionKey: string;
118
- agentId: string;
119
- label?: string;
120
- mode: "run" | "session";
121
- threadRequested: boolean;
122
- };
123
-
124
- type SubagentEndedEvent = {
125
- targetSessionKey: string;
126
- reason: string;
127
- runId?: string;
128
- outcome?: "ok" | "error" | "timeout" | "killed" | "reset" | "deleted";
129
- error?: string;
130
- };
131
-
132
- type SubagentContext = {
133
- runId?: string;
134
- childSessionKey?: string;
135
- requesterSessionKey?: string;
136
- };
137
-
138
- type CompactionEvent = {
139
- messageCount: number;
140
- compactingCount?: number;
141
- tokenCount?: number;
142
- sessionFile?: string;
143
- };
144
-
145
- type ResetEvent = {
146
- sessionFile?: string;
147
- reason?: string;
148
- };
149
-
150
- type GatewayStartEvent = { port: number };
151
- type GatewayStopEvent = { reason?: string };
152
- type GatewayContext = { port?: number };
153
-
154
- // ─── Per-session and per-run state ────────────────────────────────────────────
155
-
156
- interface SessionMeta {
157
- traceId: string;
158
- agentId: string;
159
- startedAt: number;
160
- compactions: number;
161
- resets: number;
162
- }
163
-
164
- interface RunMeta {
165
- inputTokens: number;
166
- outputTokens: number;
167
- cacheReadTokens: number;
168
- cacheWriteTokens: number;
169
- llmCalls: number;
170
- imagesCount: number;
171
- toolCalls: number;
172
- toolErrors: number;
173
- toolNames: Set<string>;
174
- subagentsSpawned: number;
175
- subagentErrors: number;
176
- model?: string;
177
- provider?: string;
178
- sessionKey?: string;
179
- startedAt: number; // run-level start time for accurate duration
180
- }
181
-
182
- const sessions = new Map<string, SessionMeta>();
183
- const runs = new Map<string, RunMeta>();
184
-
185
- // ─── HTTP helpers ─────────────────────────────────────────────────────────────
186
-
187
- /** Send a completed run summary to /v1/events (persisted to DB). */
188
- async function send(payload: Record<string, unknown>): Promise<void> {
189
- if (!API_KEY) return;
190
- try {
191
- await fetch(`${BASE_URL}/v1/events`, {
192
- method: "POST",
193
- headers: {
194
- "Content-Type": "application/json",
195
- "Authorization": `Bearer ${API_KEY}`,
196
- },
197
- body: JSON.stringify(payload),
198
- });
199
- } catch {
200
- // Never crash the agent on observability failure
201
- }
202
- }
203
-
204
- /**
205
- * Send a real-time intermediate event to /v1/activity (in-memory, streamed
206
- * to dashboard via SSE). Fire-and-forget — no retry, no crash on failure.
207
- */
208
- function sendActivity(
209
- type: string,
210
- agentId: string,
211
- sessionKey: string | undefined,
212
- runId: string | undefined,
213
- data?: Record<string, unknown>,
214
- ): void {
215
- if (!API_KEY) return;
216
- const payload = JSON.stringify({
217
- type,
218
- agent_id: agentId,
219
- session_key: sessionKey,
220
- run_id: runId,
221
- ts: Date.now(),
222
- data: data ?? null,
223
- });
224
- fetch(`${BASE_URL}/v1/activity`, {
225
- method: "POST",
226
- headers: {
227
- "Content-Type": "application/json",
228
- "Authorization": `Bearer ${API_KEY}`,
229
- },
230
- body: payload,
231
- }).catch(() => {});
232
- }
233
-
234
- function emptyRun(sessionKey?: string): RunMeta {
235
- return {
236
- inputTokens: 0, outputTokens: 0,
237
- cacheReadTokens: 0, cacheWriteTokens: 0,
238
- llmCalls: 0, imagesCount: 0,
239
- toolCalls: 0, toolErrors: 0, toolNames: new Set(),
240
- subagentsSpawned: 0, subagentErrors: 0,
241
- sessionKey,
242
- startedAt: Date.now(),
243
- };
244
- }
245
-
246
- // ─── Plugin ───────────────────────────────────────────────────────────────────
247
-
248
- const plugin = {
249
- id: "agentmetrics",
250
- name: "AgentMetrics",
251
- description: "360-degree observability for every OpenClaw agent — real-time streaming, tokens, tools, latency, cost, subagents, and reliability.",
252
- configSchema: {
253
- type: "object",
254
- properties: {
255
- apiKey: {
256
- type: "string",
257
- description: "AgentMetrics API key (overrides AGENTMETRICS_API_KEY env var)",
258
- },
259
- endpoint: {
260
- type: "string",
261
- description: "Custom API endpoint (default: https://api.agentmetrics.dev)",
262
- },
263
- },
264
- additionalProperties: false,
265
- } as const,
266
-
267
- register(api: PluginApi) {
268
- // Config: pluginConfig > env vars
269
- API_KEY = (api.pluginConfig?.apiKey as string | undefined) ?? process.env.AGENTMETRICS_API_KEY;
270
- BASE_URL = (
271
- (api.pluginConfig?.endpoint as string | undefined) ??
272
- process.env.AGENTMETRICS_URL ??
273
- "https://api.agentmetrics.dev"
274
- ).replace(/\/$/, "");
275
-
276
- // Auto-enable when a key is available (env var or plugin config)
277
- if (typeof api.registerAutoEnableProbe === "function") {
278
- api.registerAutoEnableProbe(() => !!API_KEY);
279
- }
280
-
281
- if (!API_KEY) {
282
- console.log(
283
- "\n AgentMetrics: no API key found.\n" +
284
- " Your agent runs are not being tracked.\n" +
285
- " Get an API key at https://agentmetrics.dev and set AGENTMETRICS_API_KEY.\n"
286
- );
287
- return;
288
- }
289
-
290
- console.log(
291
- `\n AgentMetrics active — sending data to ${BASE_URL}\n` +
292
- ` View your dashboard → https://agentmetrics.dev\n`
293
- );
294
-
295
- // CLI: openclaw agentmetrics status
296
- if (typeof api.registerCli === "function") {
297
- api.registerCli({
298
- name: "agentmetrics",
299
- description: "AgentMetrics observability commands",
300
- commands: [
301
- {
302
- name: "status",
303
- description: "Show current AgentMetrics plugin status",
304
- handler() {
305
- const keyPreview = API_KEY
306
- ? `${API_KEY.slice(0, 8)}...${API_KEY.slice(-4)}`
307
- : "(not set)";
308
- console.log("AgentMetrics — active");
309
- console.log(` API key : ${keyPreview}`);
310
- console.log(` Endpoint : ${BASE_URL}`);
311
- console.log(` Sessions : ${sessions.size} tracked`);
312
- console.log(` Runs : ${runs.size} in flight`);
313
- },
314
- },
315
- ],
316
- });
317
- }
318
-
319
- // ── gateway_start ─────────────────────────────────────────────────────
320
- // Activity only — gateway events are infrastructure, not agent runs.
321
- // We do NOT call send() here to avoid polluting the agents list.
322
- api.on("gateway_start", (event: GatewayStartEvent, _ctx: GatewayContext) => {
323
- sendActivity("gateway_start", "openclaw-gateway", undefined, undefined, {
324
- port: event.port,
325
- });
326
- });
327
-
328
- // ── gateway_stop ──────────────────────────────────────────────────────
329
- api.on("gateway_stop", (event: GatewayStopEvent, _ctx: GatewayContext) => {
330
- sendActivity("gateway_stop", "openclaw-gateway", undefined, undefined, {
331
- reason: event.reason,
332
- });
333
- });
334
-
335
- // ── session_start ─────────────────────────────────────────────────────
336
- api.on("session_start", (event: SessionStartEvent, ctx: SessionContext) => {
337
- const key = event.sessionKey ?? event.sessionId;
338
- if (!key || sessions.has(key)) return;
339
-
340
- sessions.set(key, {
341
- traceId: randomUUID(),
342
- agentId: ctx.agentId ?? "openclaw-agent",
343
- startedAt: Date.now(),
344
- compactions: 0,
345
- resets: 0,
346
- });
347
- });
348
-
349
- // ── before_agent_start ────────────────────────────────────────────────
350
- // Fires at the start of each run. Sends run_start for real-time UI.
351
- api.on("before_agent_start", (event: BeforeAgentStartEvent, ctx: AgentContext) => {
352
- const sessionKey = ctx.sessionKey ?? ctx.sessionId;
353
- const agentId = ctx.agentId ?? "openclaw-agent";
354
- const runId = ctx.runId;
355
-
356
- if (!runId && !sessionKey) return;
357
-
358
- sendActivity("run_start", agentId, sessionKey, runId, {
359
- model: ctx.modelId,
360
- provider: ctx.modelProviderId,
361
- });
362
- });
363
-
364
- // ── llm_input ─────────────────────────────────────────────────────────
365
- // Fires before each LLM call. Sends llm_start for real-time "thinking"
366
- // indicator. Also accumulates LLM call count and image count.
367
- api.on("llm_input", (event: LlmInputEvent, ctx: AgentContext) => {
368
- const { runId } = event;
369
- if (!runId) return;
370
-
371
- const sessionKey = ctx.sessionKey ?? ctx.sessionId ?? event.sessionId;
372
- const agentId = ctx.agentId ?? sessions.get(sessionKey ?? "")?.agentId ?? "openclaw-agent";
373
- const run = runs.get(runId) ?? emptyRun(sessionKey);
374
-
375
- run.llmCalls += 1;
376
- run.imagesCount += event.imagesCount ?? 0;
377
- if (!run.model && event.model) run.model = event.model;
378
- if (!run.provider && event.provider) run.provider = event.provider;
379
- if (!run.sessionKey) run.sessionKey = sessionKey;
380
- runs.set(runId, run);
381
-
382
- sendActivity("llm_start", agentId, sessionKey, runId, {
383
- model: event.model,
384
- provider: event.provider,
385
- images: event.imagesCount,
386
- history: event.historyMessages?.length ?? 0,
387
- });
388
- });
389
-
390
- // ── llm_output ────────────────────────────────────────────────────────
391
- // Fires after each LLM response. Accumulates tokens and sends llm_end
392
- // so the dashboard can show token counts updating in real time.
393
- api.on("llm_output", (event: LlmOutputEvent, ctx: AgentContext) => {
394
- const { runId } = event;
395
- if (!runId) return;
396
-
397
- const sessionKey = ctx.sessionKey ?? ctx.sessionId ?? event.sessionId;
398
- const agentId = ctx.agentId ?? sessions.get(sessionKey ?? "")?.agentId ?? "openclaw-agent";
399
- const run = runs.get(runId) ?? emptyRun(sessionKey);
400
-
401
- if (event.usage) {
402
- run.inputTokens += event.usage.input ?? 0;
403
- run.outputTokens += event.usage.output ?? 0;
404
- run.cacheReadTokens += event.usage.cacheRead ?? 0;
405
- run.cacheWriteTokens += event.usage.cacheWrite ?? 0;
406
- }
407
- if (event.model) run.model = event.model;
408
- if (event.provider) run.provider = event.provider;
409
- if (!run.sessionKey) run.sessionKey = sessionKey;
410
- runs.set(runId, run);
411
-
412
- sendActivity("llm_end", agentId, sessionKey, runId, {
413
- model: event.model,
414
- provider: event.provider,
415
- input_tokens: event.usage?.input,
416
- output_tokens: event.usage?.output,
417
- cache_read: event.usage?.cacheRead,
418
- cache_write: event.usage?.cacheWrite,
419
- total_input: run.inputTokens,
420
- total_output: run.outputTokens,
421
- });
422
- });
423
-
424
- // ── before_tool_call ──────────────────────────────────────────────────
425
- // Fires before each tool execution. Sends tool_start immediately so
426
- // the live visualizer shows tool activity in real time.
427
- api.on("before_tool_call", (event: BeforeToolCallEvent, ctx: ToolContext) => {
428
- const runId = event.runId ?? ctx.runId;
429
- const sessionKey = ctx.sessionKey ?? ctx.sessionId;
430
- const agentId = ctx.agentId ?? sessions.get(sessionKey ?? "")?.agentId ?? "openclaw-agent";
431
-
432
- sendActivity("tool_start", agentId, sessionKey, runId, {
433
- tool_name: event.toolName,
434
- });
435
- });
436
-
437
- // ── after_tool_call ───────────────────────────────────────────────────
438
- // Fires after every tool execution. Counts calls/errors and sends
439
- // tool_end with outcome and duration.
440
- api.on("after_tool_call", (event: AfterToolCallEvent, ctx: ToolContext) => {
441
- const runId = event.runId ?? ctx.runId;
442
- const sessionKey = ctx.sessionKey ?? ctx.sessionId;
443
- const agentId = ctx.agentId ?? sessions.get(sessionKey ?? "")?.agentId ?? "openclaw-agent";
444
-
445
- if (runId) {
446
- const run = runs.get(runId) ?? emptyRun(sessionKey);
447
- run.toolCalls += 1;
448
- if (event.error) run.toolErrors += 1;
449
- run.toolNames.add(event.toolName);
450
- if (!run.sessionKey) run.sessionKey = sessionKey;
451
- runs.set(runId, run);
452
- }
453
-
454
- sendActivity("tool_end", agentId, sessionKey, runId, {
455
- tool_name: event.toolName,
456
- duration_ms: event.durationMs,
457
- error: event.error?.slice(0, 200),
458
- });
459
- });
460
-
461
- // ── subagent_spawning ─────────────────────────────────────────────────
462
- api.on("subagent_spawning", (event: SubagentSpawningEvent, ctx: SubagentContext) => {
463
- const runId = ctx.runId;
464
- const sessionKey = ctx.requesterSessionKey;
465
- const agentId = sessions.get(sessionKey ?? "")?.agentId ?? "openclaw-agent";
466
-
467
- if (runId) {
468
- const run = runs.get(runId);
469
- if (run) {
470
- run.subagentsSpawned += 1;
471
- runs.set(runId, run);
472
- }
473
- }
474
-
475
- sendActivity("subagent_start", agentId, sessionKey, runId, {
476
- child_agent_id: event.agentId,
477
- child_session_key: event.childSessionKey,
478
- mode: event.mode,
479
- label: event.label,
480
- });
481
- });
482
-
483
- // ── subagent_ended ────────────────────────────────────────────────────
484
- api.on("subagent_ended", (event: SubagentEndedEvent, ctx: SubagentContext) => {
485
- const runId = event.runId ?? ctx.runId;
486
- const sessionKey = ctx.requesterSessionKey;
487
- const agentId = sessions.get(sessionKey ?? "")?.agentId ?? "openclaw-agent";
488
-
489
- if (runId) {
490
- const run = runs.get(runId);
491
- if (run && event.outcome && event.outcome !== "ok") {
492
- run.subagentErrors += 1;
493
- runs.set(runId, run);
494
- }
495
- }
496
-
497
- sendActivity("subagent_end", agentId, sessionKey, runId, {
498
- child_session_key: event.targetSessionKey,
499
- outcome: event.outcome,
500
- error: event.error?.slice(0, 200),
501
- });
502
- });
503
-
504
- // ── before_compaction ─────────────────────────────────────────────────
505
- api.on("before_compaction", (_event: CompactionEvent, ctx: AgentContext) => {
506
- const key = ctx.sessionKey ?? ctx.sessionId;
507
- const agentId = ctx.agentId ?? sessions.get(key ?? "")?.agentId ?? "openclaw-agent";
508
- if (!key) return;
509
-
510
- const session = sessions.get(key);
511
- if (session) {
512
- session.compactions += 1;
513
- sessions.set(key, session);
514
- }
515
-
516
- sendActivity("compaction", agentId, key, ctx.runId);
517
- });
518
-
519
- // ── before_reset ──────────────────────────────────────────────────────
520
- api.on("before_reset", (event: ResetEvent, ctx: AgentContext) => {
521
- const key = ctx.sessionKey ?? ctx.sessionId;
522
- const agentId = ctx.agentId ?? sessions.get(key ?? "")?.agentId ?? "openclaw-agent";
523
- if (!key) return;
524
-
525
- const session = sessions.get(key);
526
- if (session) {
527
- session.resets += 1;
528
- sessions.set(key, session);
529
- }
530
-
531
- sendActivity("reset", agentId, key, ctx.runId, { reason: event.reason });
532
- });
533
-
534
- // ── agent_end ─────────────────────────────────────────────────────────
535
- // Fires when the agent finishes a run. Sends run_end for real-time UI,
536
- // then the full persisted event to /v1/events with all accumulated data.
537
- api.on("agent_end", (event: AgentEndEvent, ctx: AgentContext) => {
538
- const sessionKey = ctx.sessionKey ?? ctx.sessionId;
539
- if (!sessionKey) return;
540
-
541
- const session = sessions.get(sessionKey);
542
- const run = ctx.runId ? runs.get(ctx.runId) : undefined;
543
- const agentId = ctx.agentId ?? session?.agentId ?? "openclaw-agent";
544
-
545
- if (session) session.agentId = agentId;
546
-
547
- const totalTokens =
548
- (run?.inputTokens ?? 0) +
549
- (run?.outputTokens ?? 0) +
550
- (run?.cacheReadTokens ?? 0) +
551
- (run?.cacheWriteTokens ?? 0);
552
-
553
- // Use run-level duration (accurate) — fall back to session duration only if unavailable
554
- const durationMs = event.durationMs ?? (run ? Date.now() - run.startedAt : undefined);
555
-
556
- // Real-time: notify dashboard the run finished
557
- sendActivity("run_end", agentId, sessionKey, ctx.runId, {
558
- status: event.success ? "success" : "failed",
559
- duration_ms: durationMs,
560
- total_tokens: totalTokens || undefined,
561
- tool_calls: run?.toolCalls,
562
- error: event.error?.slice(0, 200),
563
- });
564
-
565
- // Persistent: full summary stored in DB
566
- send({
567
- trace_id: session?.traceId ?? randomUUID(),
568
- agent_id: agentId,
569
- status: event.success ? "success" : "failed",
570
- duration_ms: durationMs,
571
- model: run?.model,
572
- model_provider: run?.provider,
573
- input_tokens: run?.inputTokens ?? 0,
574
- output_tokens: run?.outputTokens ?? 0,
575
- cache_read_tokens: run?.cacheReadTokens ?? 0,
576
- cache_write_tokens: run?.cacheWriteTokens ?? 0,
577
- total_tokens: totalTokens || undefined,
578
- tool_calls: run?.toolCalls ?? 0,
579
- tool_errors: run?.toolErrors ?? 0,
580
- step_count: event.messages?.length,
581
- ...(event.error ? { error: event.error.slice(0, 500) } : {}),
582
- metadata: {
583
- llm_calls: run?.llmCalls ?? 0,
584
- images_count: run?.imagesCount ?? 0,
585
- subagents_spawned: run?.subagentsSpawned ?? 0,
586
- subagent_errors: run?.subagentErrors ?? 0,
587
- compactions: session?.compactions ?? 0,
588
- resets: session?.resets ?? 0,
589
- tool_names: run ? [...run.toolNames] : [],
590
- },
591
- });
592
-
593
- if (ctx.runId) runs.delete(ctx.runId);
594
- });
595
-
596
- // ── session_end ───────────────────────────────────────────────────────
597
- api.on("session_end", (event: SessionEndEvent, _ctx: SessionContext) => {
598
- const key = event.sessionKey ?? event.sessionId;
599
- sessions.delete(key);
600
- });
601
- },
602
- };
603
-
604
- export default plugin;