@latitude-data/openclaw-telemetry 0.0.9 → 0.1.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 +78 -218
- package/dist/plugin.d.ts +145 -61
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +2078 -601
- package/dist/plugin.js.map +1 -1
- package/openclaw.plugin.json +58 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,278 +1,138 @@
|
|
|
1
1
|
# @latitude-data/openclaw-telemetry
|
|
2
2
|
|
|
3
|
-
OpenClaw plugin that streams every agent run to [Latitude](https://latitude.so) as OTLP traces
|
|
3
|
+
OpenClaw plugin that streams every agent run to [Latitude](https://latitude.so) as OTLP traces: the user prompt and the sending user, the system prompt, every model call with its own tokens, cost and time to first token, the tools the agent was offered and the ones it called, memory reads and writes, subagents, cron runs and compactions, all grouped into one Latitude session per OpenClaw session.
|
|
4
|
+
|
|
5
|
+
This is the OpenClaw counterpart to the other harness integrations ([`latitude-telemetry-hermes`](../hermes), [`@latitude-data/claude-code-telemetry`](../claude-code), [`@latitude-data/pi-telemetry`](../pi)).
|
|
6
|
+
|
|
7
|
+
> OpenClaw also bundles a generic OpenTelemetry exporter (`@openclaw/diagnostics-otel`). It deliberately scrubs session, run and user ids ([openclaw/openclaw#91927](https://github.com/openclaw/openclaw/issues/91927)), exports no system prompt, no tool definitions and no memory, so it cannot produce Latitude sessions, users, tool rollups or the memory ledger. Use this plugin for full fidelity.
|
|
4
8
|
|
|
5
9
|
## Requirements
|
|
6
10
|
|
|
7
|
-
- **OpenClaw 2026.
|
|
11
|
+
- **OpenClaw 2026.8.1 or newer** on PATH.
|
|
8
12
|
- A **Latitude API key** from `https://console.latitude.so/projects/<your-slug>/settings/keys` and the matching **project slug**.
|
|
9
13
|
|
|
10
14
|
## Install
|
|
11
15
|
|
|
12
|
-
###
|
|
13
|
-
|
|
14
|
-
The companion CLI handles every step (install, config, validate, restart) in one command:
|
|
16
|
+
### One-shot CLI
|
|
15
17
|
|
|
16
18
|
```bash
|
|
17
|
-
npx -y @latitude-data/openclaw-telemetry-cli@0.0
|
|
19
|
+
npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 install
|
|
18
20
|
```
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
The installer prompts for the API key and project slug, runs `openclaw plugins install --accept-capabilities`, writes the plugin entry into `openclaw.json`, adds it to `plugins.allow`, validates the result and offers to restart the gateway. See the [CLI README](../openclaw-cli#readme) for flags, dry-run mode, custom config dir and CI usage.
|
|
21
23
|
|
|
22
24
|
### Manual install
|
|
23
25
|
|
|
24
|
-
If you'd rather not use the CLI, do exactly what it does, in four steps:
|
|
25
|
-
|
|
26
|
-
#### 1. Install the runtime
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
openclaw plugins install @latitude-data/openclaw-telemetry@0.0.9
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Pin to an exact version. OpenClaw's `security audit --deep` warns about unpinned install specs, so always include the `@<version>` suffix.
|
|
33
|
-
|
|
34
|
-
OpenClaw fetches from npm, runs its security scan, copies files into `~/.openclaw/extensions/<id>/`, and creates a (disabled) `plugins.entries["@latitude-data/openclaw-telemetry"]` entry in `~/.openclaw/openclaw.json`.
|
|
35
|
-
|
|
36
|
-
#### 2. Configure and enable
|
|
37
|
-
|
|
38
|
-
Run these `openclaw config set` commands (use bracket notation so the scoped package name parses correctly). Substitute your real API key and project slug:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].config.apiKey' "lat_xxx"
|
|
42
|
-
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].config.project' "my-project-slug"
|
|
43
|
-
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].config.allowConversationAccess' true
|
|
44
|
-
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].hooks.allowConversationAccess' true
|
|
45
|
-
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].enabled' true
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Both `allowConversationAccess` writes are required — see [The two flags](#the-two-flags).
|
|
49
|
-
|
|
50
|
-
#### 3. Add to `plugins.allow` (optional but recommended)
|
|
51
|
-
|
|
52
|
-
OpenClaw warns at every gateway restart about non-bundled plugins that auto-load without provenance via `plugins.allow`. Silence the warning:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# `config set` can't append to arrays — set the whole list. Include any other
|
|
56
|
-
# plugins you already have in `plugins.allow`.
|
|
57
|
-
openclaw config set 'plugins.allow' '["@latitude-data/openclaw-telemetry"]'
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
#### 4. Restart the gateway
|
|
61
|
-
|
|
62
26
|
```bash
|
|
27
|
+
openclaw plugins install @latitude-data/openclaw-telemetry@0.1.0 --accept-capabilities
|
|
28
|
+
|
|
29
|
+
P='plugins.entries["@latitude-data/openclaw-telemetry"]'
|
|
30
|
+
openclaw config set "$P.config.apiKey" "lat_xxx"
|
|
31
|
+
openclaw config set "$P.config.project" "my-project-slug"
|
|
32
|
+
openclaw config set "$P.config.allowConversationAccess" true
|
|
33
|
+
openclaw config set "$P.hooks.allowConversationAccess" true
|
|
34
|
+
openclaw config set "$P.enabled" true
|
|
63
35
|
openclaw gateway restart
|
|
64
36
|
```
|
|
65
37
|
|
|
66
|
-
|
|
38
|
+
`--accept-capabilities` records your consent to the plugin's declared surface; OpenClaw requires it for every non-bundled plugin. Both `allowConversationAccess` keys are required, see [The two flags](#the-two-flags). Optionally add the plugin id to `plugins.allow` to silence OpenClaw's provenance warning at startup (`config set` replaces the whole array, so include any ids already there).
|
|
39
|
+
|
|
40
|
+
Verify:
|
|
67
41
|
|
|
68
42
|
```bash
|
|
69
43
|
openclaw config validate --json
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
grep -E "blocked|plugin not found|latitude" /tmp/openclaw/openclaw-*.log | tail
|
|
73
|
-
# → ready (N plugins: ..., @latitude-data/openclaw-telemetry, ...)
|
|
74
|
-
# → no "blocked", no "plugin not found"
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Send a message to one of your OpenClaw agents — within seconds, traces appear at `https://console.latitude.so/projects/<your-slug>`.
|
|
78
|
-
|
|
79
|
-
#### Or: hand-edit `~/.openclaw/openclaw.json`
|
|
80
|
-
|
|
81
|
-
Equivalent to steps 2 + 3 in one paste:
|
|
82
|
-
|
|
83
|
-
```jsonc
|
|
84
|
-
{
|
|
85
|
-
"plugins": {
|
|
86
|
-
"allow": ["@latitude-data/openclaw-telemetry"],
|
|
87
|
-
"entries": {
|
|
88
|
-
"@latitude-data/openclaw-telemetry": {
|
|
89
|
-
"enabled": true,
|
|
90
|
-
"hooks": {
|
|
91
|
-
"allowConversationAccess": true
|
|
92
|
-
},
|
|
93
|
-
"config": {
|
|
94
|
-
"apiKey": "lat_xxx",
|
|
95
|
-
"project": "my-project-slug",
|
|
96
|
-
"allowConversationAccess": true
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
44
|
+
grep -E "latitude-openclaw|typed hook" /tmp/openclaw/openclaw-*.log | tail
|
|
102
45
|
```
|
|
103
46
|
|
|
104
|
-
|
|
47
|
+
With `config.debug` on, the gateway log shows `[latitude-openclaw] enabled v0.1.0 ...` at startup and one `exported N spans` line per run.
|
|
105
48
|
|
|
106
49
|
## Uninstall
|
|
107
50
|
|
|
108
|
-
If you installed via the CLI:
|
|
109
|
-
|
|
110
51
|
```bash
|
|
111
|
-
npx -y @latitude-data/openclaw-telemetry-cli@0.0
|
|
52
|
+
npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 uninstall
|
|
53
|
+
# or
|
|
54
|
+
openclaw plugins uninstall @latitude-data/openclaw-telemetry --force && openclaw gateway restart
|
|
112
55
|
```
|
|
113
56
|
|
|
114
|
-
Manual uninstall:
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
openclaw plugins uninstall @latitude-data/openclaw-telemetry --force
|
|
118
|
-
openclaw gateway restart
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
OpenClaw removes the extension files, install record, plugin entry, and the `plugins.allow` entry.
|
|
122
|
-
|
|
123
|
-
## Targeting staging or local dev
|
|
124
|
-
|
|
125
|
-
By default the plugin sends to production (`https://ingest.latitude.so`). Override `baseUrl` to point elsewhere:
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
# Staging
|
|
129
|
-
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].config.baseUrl' \
|
|
130
|
-
"https://staging-ingest.latitude.so"
|
|
131
|
-
|
|
132
|
-
# Local dev
|
|
133
|
-
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].config.baseUrl' \
|
|
134
|
-
"http://localhost:3002"
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
The CLI handles this with `--staging` / `--dev` flags.
|
|
138
|
-
|
|
139
|
-
## Structural-only telemetry (no content capture)
|
|
140
|
-
|
|
141
|
-
To get trace metadata (timings, token usage, model name, agent name, ids) without prompt/response content, keep `hooks.allowConversationAccess` at `true` so events still dispatch, and set only `config.allowConversationAccess` to `false`:
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].config.allowConversationAccess' false
|
|
145
|
-
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].hooks.allowConversationAccess' true
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Setting `hooks.allowConversationAccess=false` would block dispatch entirely — see [The two flags](#the-two-flags). With this config the plugin still emits the full span tree, just with content attributes (`gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.system_instructions`, tool args/results) scrubbed. Each span carries `latitude.captured.content: false` so the gate state is visible in the Latitude UI.
|
|
149
|
-
|
|
150
|
-
The CLI handles this with `--no-content`.
|
|
151
|
-
|
|
152
57
|
## What gets sent
|
|
153
58
|
|
|
154
|
-
|
|
59
|
+
One trace per agent run (a user turn, a cron run, a heartbeat, a subagent run):
|
|
155
60
|
|
|
156
61
|
```
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
│ └─ tool_call: ...
|
|
167
|
-
└─ model_call (final)
|
|
62
|
+
interaction invoke_agent prompt, final answer, outcome, sender
|
|
63
|
+
├── search_memory memory the snapshot injected at session start, once per session
|
|
64
|
+
├── llm_request chat tokens, cost, TTFT, system prompt, tool definitions, messages
|
|
65
|
+
├── tool_call:<name> execute_tool arguments, result, error
|
|
66
|
+
│ └── search_memory | upsert_memory memory memory tools and memory file writes
|
|
67
|
+
├── tool_call:sessions_spawn
|
|
68
|
+
│ └── subagent spawn → ended, with the child run's interaction nested inside
|
|
69
|
+
├── compaction
|
|
70
|
+
└── llm_request
|
|
168
71
|
```
|
|
169
72
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- **`agent`** — root of the run. Carries `openclaw.session.key`, `openclaw.agent.id`, `openclaw.agent.name`, aggregated token usage across all generations, run duration, success/error status, the first user prompt, and the full final message list. Attempt-aggregate `gen_ai.*` lands here.
|
|
173
|
-
- **`model_call`** — one per provider API call inside the run. Carries provider, request/response model, `openclaw.api`, `openclaw.transport`, per-call duration, outcome, error category, time-to-first-byte, request payload bytes, response stream bytes, upstream request id hash, and `gen_ai.input.messages` snapshotted at the moment that generation started. Per-call output messages and per-call token usage aren't surfaced by OpenClaw today (attempt-aggregate only); those stay on `agent`.
|
|
174
|
-
- **`tool_call:<name>`** — one per tool invocation. Canonical `gen_ai.tool.*` attributes: `name`, `call.id`, `call.arguments`, `call.result`. Sibling of `agent`, NOT child of `model_call` — tools run between generations, not during them.
|
|
175
|
-
- **`compaction`** — rare; fires when OpenClaw hits the message budget mid-run. Records before/after message counts and the compacted-out count.
|
|
176
|
-
- **`subagent`** — one per child run spawned by this agent. The child's entire `agent` subtree (its own `model_call`s, `tool_call`s, even further-nested `subagent`s) parents itself underneath via cross-runId trace propagation, so a spawn tree is one waterfall in one trace.
|
|
73
|
+
Every span carries the OpenClaw session id (`session.id`), the sender of a user turn (`user.id`), the agent (`gen_ai.agent.name`), derived tags and `openclaw.*` metadata. A subagent's spans join the parent's trace and session so one delegation reads as one conversation.
|
|
177
74
|
|
|
178
|
-
|
|
75
|
+
Per-call usage, cost, finish reason and output come from the run's transcript at `agent_end`, matched to each `model_call_started` / `model_call_ended` window by timestamp; OpenClaw's own cost is reported as the span's cost so a model missing from Latitude's catalog still shows a price. Tags are `openclaw`, the channel (`slack`, `telegram`, ...), the agent id, `cron:<job>` on cron runs and `subagent:<agent>` on a run that spawned one, plus whatever `config.tags` adds.
|
|
179
76
|
|
|
180
|
-
|
|
77
|
+
Design notes, hook traps and the full attribute tables live in [`dev-docs/openclaw-telemetry.md`](../../../dev-docs/openclaw-telemetry.md).
|
|
181
78
|
|
|
182
|
-
|
|
79
|
+
## Configuration
|
|
183
80
|
|
|
184
|
-
|
|
81
|
+
Everything lives under `plugins.entries["@latitude-data/openclaw-telemetry"]`.
|
|
185
82
|
|
|
186
|
-
|
|
83
|
+
### `.config` — read by the plugin
|
|
187
84
|
|
|
188
|
-
|
|
|
85
|
+
| Key | Default | Description |
|
|
189
86
|
| --- | --- | --- |
|
|
190
|
-
| `
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
|
209
|
-
|
|
|
210
|
-
| `apiKey` | yes | — | Bearer token for Latitude ingestion. |
|
|
211
|
-
| `project` | yes | — | Slug of the project to route traces into. |
|
|
212
|
-
| `baseUrl` | no | `https://ingest.latitude.so` | Override OTLP ingest origin. The CLI sets this only when `--staging` or `--dev` is passed. |
|
|
213
|
-
| `allowConversationAccess` | no | `false` | When `true`, attach raw prompts, assistant responses, system instructions, and tool I/O to spans. When `false`, emit only timing, token usage, model name, agent id, and structural ids — same span tree, scrubbed payloads. **Must match `hooks.allowConversationAccess` below — see [The two flags](#the-two-flags).** |
|
|
214
|
-
| `redact` | no | — | Custom local attribute redaction before export: `{ "attributes": ["/^gen_ai\\.(input|output)\\.messages$/"], "mask": "[]" }`. Patterns are exact strings, regex source strings, or `/pattern/flags` strings. |
|
|
215
|
-
| `enabled` | no | `true` | Set to `false` to pause emission without uninstalling. |
|
|
216
|
-
| `debug` | no | `false` | Log diagnostic lines to stderr (visible in the gateway log). |
|
|
217
|
-
|
|
218
|
-
### `.hooks` — read by OpenClaw's runtime
|
|
219
|
-
|
|
220
|
-
| Key | Required | Default | Description |
|
|
221
|
-
| --- | --- | --- | --- |
|
|
222
|
-
| `allowConversationAccess` | yes (on 2026.4.25+) | — | OpenClaw's hook dispatcher gates `llm_input` / `llm_output` / `before_tool_call` / `after_tool_call` / `agent_end` events on this. When `false` or absent, every typed hook is blocked and the plugin never sees an event — which means no traces, with the gateway log showing `[plugins] typed hook "..." blocked because non-bundled plugins must set plugins.entries.<id>.hooks.allowConversationAccess=true`. |
|
|
87
|
+
| `apiKey` | — | Latitude API key (required). |
|
|
88
|
+
| `project` | — | Project slug (required). |
|
|
89
|
+
| `baseUrl` | `https://ingest.latitude.so` | Ingest origin, without `/v1/traces`. |
|
|
90
|
+
| `allowConversationAccess` | `false` | Attach prompts, responses, system prompt, tool I/O and memory bodies. Must match `hooks.allowConversationAccess`. |
|
|
91
|
+
| `serviceName` | `openclaw` | OTLP `service.name`, the Service axis in Latitude. |
|
|
92
|
+
| `tags` | — | Extra tags, array or comma-separated string. |
|
|
93
|
+
| `metadata` | — | Extra metadata, string map. Keys starting with `openclaw.` are ignored. |
|
|
94
|
+
| `memory` | `true` | Emit memory spans. |
|
|
95
|
+
| `memoryContent` | `true` | Include memory bodies and queries on memory spans. |
|
|
96
|
+
| `toolDefinitions` | `true` | Attach the offered tool definitions to each model call. |
|
|
97
|
+
| `maxContentChars` | `262144` | Per-attribute content budget. Strings are truncated from the middle; message lists and tool definitions drop items from the middle so they still parse. |
|
|
98
|
+
| `redact` | — | `{ "attributes": ["exact key" or "/regex/flags"], "mask": "******" }`: mask selected attribute values before export, keeping the key. |
|
|
99
|
+
| `enabled` | `true` | Set to `false` to pause emission. |
|
|
100
|
+
| `debug` | `false` | Log diagnostics to the gateway log. |
|
|
101
|
+
|
|
102
|
+
### `.hooks` — read by OpenClaw
|
|
103
|
+
|
|
104
|
+
| Key | Description |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `allowConversationAccess` | OpenClaw's dispatch gate for `llm_input`, `llm_output` and `agent_end`. When absent or `false`, those hooks are never registered for this plugin and no traces are produced. |
|
|
223
107
|
|
|
224
108
|
### The two flags
|
|
225
109
|
|
|
226
|
-
`hooks.allowConversationAccess`
|
|
110
|
+
- `hooks.allowConversationAccess` is the **dispatch gate**: `false` means OpenClaw never forwards the conversation hooks, so nothing is exported.
|
|
111
|
+
- `config.allowConversationAccess` is the **content gate**: `false` means the full span tree still ships, with message, prompt, tool I/O and memory bodies removed and `latitude.captured.content=false` on every span.
|
|
227
112
|
|
|
228
|
-
-
|
|
229
|
-
- **`config.*`** is the **payload-content gate**. `false` → we emit spans normally but scrub message content from them. Structural-only telemetry.
|
|
113
|
+
Structural-only telemetry is therefore `hooks: true` plus `config: false` (the CLI's `--no-content`).
|
|
230
114
|
|
|
231
|
-
|
|
115
|
+
### Targeting staging or local dev
|
|
232
116
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
### Environment-variable fallbacks
|
|
238
|
-
|
|
239
|
-
If a `config.*` key isn't set, the runtime falls back to env vars on the gateway process: `LATITUDE_API_KEY`, `LATITUDE_PROJECT`, `LATITUDE_BASE_URL`, `LATITUDE_DEBUG`, `LATITUDE_OPENCLAW_ENABLED`. Useful for flipping `debug` without editing `openclaw.json`.
|
|
240
|
-
|
|
241
|
-
## Privacy
|
|
117
|
+
```bash
|
|
118
|
+
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].config.baseUrl' "https://staging-ingest.latitude.so"
|
|
119
|
+
openclaw config set 'plugins.entries["@latitude-data/openclaw-telemetry"].config.baseUrl' "http://localhost:3002"
|
|
120
|
+
```
|
|
242
121
|
|
|
243
|
-
The CLI
|
|
122
|
+
The CLI has `--staging` / `--dev` for the same.
|
|
244
123
|
|
|
245
|
-
|
|
124
|
+
## How it fails
|
|
246
125
|
|
|
247
|
-
|
|
126
|
+
Fail-open. An unreachable ingest, a bad key or a malformed hook payload is logged (with `debug: true`) and the agent run continues. Exports retry on `429`, `5xx` and network errors and never resend a span that was accepted.
|
|
248
127
|
|
|
249
|
-
|
|
128
|
+
## Development
|
|
250
129
|
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
"@latitude-data/openclaw-telemetry": {
|
|
256
|
-
"config": {
|
|
257
|
-
"redact": {
|
|
258
|
-
"attributes": ["/^gen_ai\\.(input|output)\\.messages$/"],
|
|
259
|
-
"mask": "[]"
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
130
|
+
```bash
|
|
131
|
+
pnpm --filter @latitude-data/openclaw-telemetry test
|
|
132
|
+
pnpm --filter @latitude-data/openclaw-telemetry build && (cd packages/telemetry/openclaw && npm pack)
|
|
133
|
+
openclaw plugins install npm-pack:/path/to/latitude-data-openclaw-telemetry-0.1.0.tgz --accept-capabilities --force
|
|
266
134
|
```
|
|
267
135
|
|
|
268
|
-
## Supported OpenClaw versions
|
|
269
|
-
|
|
270
|
-
Requires **2026.4.25 or newer**. Earlier versions either reject `hooks.allowConversationAccess` outright (≤ 2026.4.21) or have unverified dispatch gating (2026.4.22 – 2026.4.24). The CLI's version check aborts on older versions; manual installs run into validation errors. Run `npm install -g openclaw@latest` to upgrade.
|
|
271
|
-
|
|
272
|
-
## How it fails
|
|
273
|
-
|
|
274
|
-
Fail-open by design. If the API is unreachable, your key is wrong, or a hook payload is malformed, the plugin logs to stderr (when `debug: true`) and the agent run continues unaffected.
|
|
275
|
-
|
|
276
136
|
## License
|
|
277
137
|
|
|
278
138
|
MIT
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
interface OtlpAnyValue {
|
|
3
|
+
stringValue?: string;
|
|
4
|
+
intValue?: string;
|
|
5
|
+
boolValue?: boolean;
|
|
6
|
+
doubleValue?: number;
|
|
7
|
+
arrayValue?: {
|
|
8
|
+
values: OtlpAnyValue[];
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
interface OtlpKeyValue {
|
|
12
|
+
key: string;
|
|
13
|
+
value: OtlpAnyValue;
|
|
14
|
+
}
|
|
15
|
+
interface OtlpSpan {
|
|
16
|
+
traceId: string;
|
|
17
|
+
spanId: string;
|
|
18
|
+
parentSpanId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
kind: number;
|
|
21
|
+
startTimeUnixNano: string;
|
|
22
|
+
endTimeUnixNano: string;
|
|
23
|
+
attributes: OtlpKeyValue[];
|
|
24
|
+
status: {
|
|
25
|
+
code: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
interface OtlpResourceSpans {
|
|
29
|
+
resource: {
|
|
30
|
+
attributes: OtlpKeyValue[];
|
|
31
|
+
};
|
|
32
|
+
scopeSpans: Array<{
|
|
33
|
+
scope: {
|
|
34
|
+
name: string;
|
|
35
|
+
version: string;
|
|
36
|
+
};
|
|
37
|
+
spans: OtlpSpan[];
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
interface OtlpExportRequest {
|
|
41
|
+
resourceSpans: OtlpResourceSpans[];
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
1
44
|
//#region src/redaction.d.ts
|
|
2
45
|
interface RedactConfig {
|
|
3
46
|
attributes: string[];
|
|
@@ -14,11 +57,18 @@ interface Config {
|
|
|
14
57
|
/**
|
|
15
58
|
* When false, the plugin still emits one span per LLM call / tool / run, but
|
|
16
59
|
* scrubs raw conversation content (input/output messages, system prompt,
|
|
17
|
-
* tool args, tool results,
|
|
18
|
-
*
|
|
60
|
+
* tool args, tool results, memory bodies). Token counts, model names, agent
|
|
61
|
+
* ids, and timings are unaffected.
|
|
19
62
|
*/
|
|
20
63
|
allowConversationAccess: boolean;
|
|
21
64
|
redact?: RedactConfig | undefined;
|
|
65
|
+
serviceName: string;
|
|
66
|
+
tags: string[];
|
|
67
|
+
metadata: Record<string, string>;
|
|
68
|
+
memory: boolean;
|
|
69
|
+
memoryContent: boolean;
|
|
70
|
+
toolDefinitions: boolean;
|
|
71
|
+
maxContentChars: number;
|
|
22
72
|
}
|
|
23
73
|
//#endregion
|
|
24
74
|
//#region src/logger.d.ts
|
|
@@ -26,102 +76,136 @@ interface Logger {
|
|
|
26
76
|
debug: (msg: string) => void;
|
|
27
77
|
warn: (msg: string) => void;
|
|
28
78
|
}
|
|
79
|
+
/** Shape of OpenClaw's `api.logger`; lines written through it land in the gateway log. */
|
|
80
|
+
interface HostLogger {
|
|
81
|
+
debug?: (message: string) => void;
|
|
82
|
+
info: (message: string) => void;
|
|
83
|
+
warn: (message: string) => void;
|
|
84
|
+
error: (message: string) => void;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/usage.d.ts
|
|
88
|
+
type AttrValue = string | number | boolean | unknown[] | Record<string, unknown> | undefined;
|
|
29
89
|
//#endregion
|
|
30
90
|
//#region src/span-builder.d.ts
|
|
31
91
|
/**
|
|
32
|
-
* Builds
|
|
33
|
-
* paired hooks. Replaces the older `turn-builder.ts` model that collapsed the
|
|
34
|
-
* whole attempt into a single `llm_request` span — that shape was wrong on
|
|
35
|
-
* two counts: `llm_input` / `llm_output` fire ONCE per attempt (not per
|
|
36
|
-
* generation), and an attempt is a sequence of generations interleaved with
|
|
37
|
-
* tool executions.
|
|
38
|
-
*
|
|
39
|
-
* Span set this builder produces:
|
|
92
|
+
* Builds one trace per OpenClaw agent run from the typed plugin hooks:
|
|
40
93
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
94
|
+
* interaction (root, invoke_agent)
|
|
95
|
+
* ├── search_memory (once per session: the snapshot injected at session start)
|
|
96
|
+
* ├── llm_request (chat; one per provider call, per-call usage + cost + messages)
|
|
97
|
+
* ├── tool_call:<name> (execute_tool; sibling of llm_request, tools run between calls)
|
|
98
|
+
* │ └── search_memory / upsert_memory / delete_memory (memory tools and memory file writes)
|
|
99
|
+
* ├── tool_call:sessions_spawn
|
|
100
|
+
* │ └── subagent (spawn → ended; the child run's interaction nests underneath)
|
|
101
|
+
* ├── compaction
|
|
102
|
+
* └── llm_request
|
|
48
103
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* `llm_input` / `llm_output` are NOT span boundaries here. They're data-only
|
|
54
|
-
* feeds that enrich the parent `agent` span (full message history, output
|
|
55
|
-
* messages, aggregate token usage).
|
|
104
|
+
* No hook announces a run before `llm_input`, so the root opens lazily on the
|
|
105
|
+
* first event that carries a run id. `agent_end` carries the whole transcript,
|
|
106
|
+
* which is where per-call usage, cost and output content come from; it fires
|
|
107
|
+
* before `llm_output`, so finalization waits for the latter or a short grace.
|
|
56
108
|
*/
|
|
57
109
|
interface SpanRecord {
|
|
58
|
-
/** Stable id for the span (16 hex chars). */
|
|
59
110
|
spanId: string;
|
|
60
|
-
/** Span tree id (32 hex chars). */
|
|
61
111
|
traceId: string;
|
|
62
|
-
/** Empty string for root agent spans, parent's spanId otherwise. */
|
|
63
112
|
parentSpanId: string;
|
|
64
|
-
/** OpenClaw event noun (`agent` / `model_call` / `tool_call` / `compaction` / `subagent`). */
|
|
65
113
|
name: string;
|
|
114
|
+
/** OTel SpanKind: 1 INTERNAL, 3 CLIENT. */
|
|
115
|
+
kind: number;
|
|
66
116
|
startMs: number;
|
|
67
117
|
endMs: number | undefined;
|
|
68
|
-
/** Free-form attribute bag — flattened to OTLP key/value at emit time. */
|
|
69
118
|
attrs: Record<string, AttrValue>;
|
|
70
|
-
/** Status — set at close from the event payload's outcome/error. */
|
|
71
119
|
outcome?: "ok" | "error";
|
|
72
120
|
errorMessage?: string | undefined;
|
|
73
121
|
}
|
|
74
|
-
type AttrValue = string | number | boolean | unknown[] | Record<string, unknown> | undefined;
|
|
75
122
|
interface BuildResult {
|
|
76
|
-
/** Run id this batch belongs to. */
|
|
77
123
|
runId: string;
|
|
78
|
-
/** All spans ready to be exported (agent + everything beneath it). */
|
|
79
124
|
spans: SpanRecord[];
|
|
80
125
|
}
|
|
81
126
|
//#endregion
|
|
127
|
+
//#region src/transport.d.ts
|
|
128
|
+
interface TransportOptions {
|
|
129
|
+
baseUrl: string;
|
|
130
|
+
apiKey: string;
|
|
131
|
+
project: string;
|
|
132
|
+
logger: Logger;
|
|
133
|
+
timeoutMs?: number;
|
|
134
|
+
maxAttempts?: number;
|
|
135
|
+
fetchImpl?: typeof fetch;
|
|
136
|
+
sleep?: (ms: number) => Promise<void>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Ships OTLP requests sequentially with bounded retries. Every span is sent
|
|
140
|
+
* exactly once on success: Latitude's trace and session rollups add per
|
|
141
|
+
* insert, so a duplicate would inflate counts; a `4xx` other than `429` is
|
|
142
|
+
* therefore final, while `429`, `5xx` and network errors retry with backoff.
|
|
143
|
+
*/
|
|
144
|
+
declare class Transport {
|
|
145
|
+
private readonly url;
|
|
146
|
+
private readonly opts;
|
|
147
|
+
private chain;
|
|
148
|
+
private pending;
|
|
149
|
+
constructor(opts: TransportOptions);
|
|
150
|
+
enqueue(payload: OtlpExportRequest): void;
|
|
151
|
+
/** Resolves when everything queued so far has been sent or given up on, or the budget elapses. */
|
|
152
|
+
flush(budgetMs: number): Promise<void>;
|
|
153
|
+
private send;
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
82
156
|
//#region src/plugin.d.ts
|
|
83
157
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* and so we're robust to small signature changes across OpenClaw versions.
|
|
88
|
-
*
|
|
89
|
-
* `pluginConfig` is the user's `plugins.entries[id].config` block — that's
|
|
90
|
-
* the canonical place to read credentials and feature flags. The OpenClaw
|
|
91
|
-
* plugin SDK also exposes the same value as `api.pluginConfig` on the
|
|
92
|
-
* builder API; keep both names in sync if the upstream contract evolves.
|
|
158
|
+
* Structural type for the slice of OpenClaw's plugin API this plugin touches.
|
|
159
|
+
* Kept local so the package works without OpenClaw installed (tests, CLI) and
|
|
160
|
+
* tolerates small upstream signature changes.
|
|
93
161
|
*/
|
|
94
162
|
interface OpenClawPluginApiLike {
|
|
95
|
-
logger?:
|
|
163
|
+
logger?: HostLogger;
|
|
96
164
|
pluginConfig?: Record<string, unknown>;
|
|
165
|
+
/** The whole OpenClaw config; only the plugin's own `hooks` block is read. */
|
|
166
|
+
config?: {
|
|
167
|
+
plugins?: {
|
|
168
|
+
entries?: Record<string, {
|
|
169
|
+
hooks?: {
|
|
170
|
+
allowConversationAccess?: boolean;
|
|
171
|
+
};
|
|
172
|
+
}>;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
/** Host runtime helpers; the state dir and the agent event stream are used when present. */
|
|
176
|
+
runtime?: {
|
|
177
|
+
state?: {
|
|
178
|
+
resolveStateDir?: () => string;
|
|
179
|
+
};
|
|
180
|
+
events?: {
|
|
181
|
+
onAgentEvent?: (listener: (evt: AgentEventLike) => void) => unknown;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
97
184
|
on: <K extends string>(hookName: K, handler: (event: unknown, ctx: unknown) => unknown, opts?: {
|
|
98
185
|
priority?: number;
|
|
99
186
|
}) => void;
|
|
100
187
|
}
|
|
188
|
+
interface AgentEventLike {
|
|
189
|
+
runId?: string;
|
|
190
|
+
stream?: string;
|
|
191
|
+
ts?: number;
|
|
192
|
+
data?: Record<string, unknown>;
|
|
193
|
+
sessionKey?: string;
|
|
194
|
+
sessionId?: string;
|
|
195
|
+
agentId?: string;
|
|
196
|
+
}
|
|
101
197
|
interface RegisterOptions {
|
|
102
198
|
/** Override the config, mostly for tests. */
|
|
103
199
|
config?: Config;
|
|
104
|
-
/** Override the logger. */
|
|
105
200
|
logger?: Logger;
|
|
106
|
-
/**
|
|
107
|
-
* Hook to observe the emitted run right before it's posted. Used by tests;
|
|
108
|
-
* not a stable public API.
|
|
109
|
-
*/
|
|
201
|
+
/** Observe each finished batch right before export. Tests only. */
|
|
110
202
|
onEmit?: (result: BuildResult) => void;
|
|
203
|
+
/** Replace the network transport. Tests only. */
|
|
204
|
+
transport?: Pick<Transport, "enqueue" | "flush">;
|
|
205
|
+
now?: () => number;
|
|
206
|
+
schedule?: (fn: () => void, ms: number) => () => void;
|
|
111
207
|
}
|
|
112
|
-
/**
|
|
113
|
-
* Register the Latitude plugin against an OpenClaw plugin API. OpenClaw calls
|
|
114
|
-
* this once at plugin activation; we wire up the granular paired hooks
|
|
115
|
-
* (model_call_started/_ended, before_/after_tool_call, before_/after_compaction,
|
|
116
|
-
* subagent_spawned/_ended, before_agent_start/agent_end) plus the
|
|
117
|
-
* data-only feeds (llm_input/llm_output) that enrich the agent span.
|
|
118
|
-
*
|
|
119
|
-
* Every typed hook on OpenClaw's side fires fire-and-forget for non-modifying
|
|
120
|
-
* hooks; before_tool_call is a `runModifyingHook` where returning anything
|
|
121
|
-
* other than undefined blocks the tool call. Our handler returns nothing —
|
|
122
|
-
* keep it that way.
|
|
123
|
-
*/
|
|
124
208
|
declare function registerLatitudePlugin(api: OpenClawPluginApiLike, opts?: RegisterOptions): void;
|
|
125
209
|
//#endregion
|
|
126
|
-
export { OpenClawPluginApiLike, RegisterOptions, registerLatitudePlugin as default };
|
|
210
|
+
export { AgentEventLike, OpenClawPluginApiLike, RegisterOptions, registerLatitudePlugin as default };
|
|
127
211
|
//# sourceMappingURL=plugin.d.ts.map
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../src/redaction.ts","../src/config.ts","../src/logger.ts","../src/span-builder.ts","../src/plugin.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","names":[],"sources":["../src/types.ts","../src/redaction.ts","../src/config.ts","../src/logger.ts","../src/usage.ts","../src/span-builder.ts","../src/transport.ts","../src/plugin.ts"],"mappings":"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"}
|