@latitude-data/openclaw-telemetry 0.0.3 → 0.0.5
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 +94 -40
- package/dist/cli.js +297 -111
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +55 -65
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +732 -444
- package/dist/plugin.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,22 +4,23 @@ OpenClaw plugin that streams every agent run to [Latitude](https://latitude.so)
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
+
Requires OpenClaw **2026.4.25 or newer** on PATH. Older versions are detected up-front and the installer aborts with an upgrade message.
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
10
|
npx -y @latitude-data/openclaw-telemetry install
|
|
11
|
+
openclaw gateway restart
|
|
9
12
|
```
|
|
10
13
|
|
|
11
14
|
The installer prompts for your Latitude API key and project slug, then:
|
|
12
15
|
|
|
13
|
-
1.
|
|
14
|
-
2.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
openclaw gateway restart
|
|
20
|
-
```
|
|
16
|
+
1. Verifies your OpenClaw version (aborts on `< 2026.4.25`).
|
|
17
|
+
2. Hands plugin placement to OpenClaw via `openclaw plugins install <package-path> --force`. OpenClaw copies files into `~/.openclaw/extensions/<id>/`, writes the install record to `~/.openclaw/plugins/installs.json`, and creates the `plugins.entries[id]` block.
|
|
18
|
+
3. Layers our config on top in `~/.openclaw/openclaw.json`:
|
|
19
|
+
- **`config.*`** — credentials, baseUrl, and `allowConversationAccess` (the payload-content gate the plugin's runtime reads).
|
|
20
|
+
- **`hooks.allowConversationAccess`** — the dispatch gate OpenClaw's runtime checks before forwarding LLM/tool/agent events to non-bundled plugins. Always mirrored to the same value as `config.allowConversationAccess`.
|
|
21
|
+
4. Adds the plugin id to `plugins.allow` (running `npx install` is the trust signal). Pass `--no-trust` to opt out.
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
Traces show up at `https://console.latitude.so/projects/<your-slug>` once the gateway restarts.
|
|
23
24
|
|
|
24
25
|
### Install flags
|
|
25
26
|
|
|
@@ -30,9 +31,10 @@ That's it. Traces show up at `https://console.latitude.so/projects/<your-slug>`.
|
|
|
30
31
|
| `--staging` | Target `https://staging.latitude.so` / `https://staging-ingest.latitude.so`. |
|
|
31
32
|
| `--dev` | Target `http://localhost:3000` / `http://localhost:3002`. |
|
|
32
33
|
| `--yes` / `--no-prompt` | Skip all prompts. Required for non-TTY / CI invocations. |
|
|
33
|
-
| `--no-content` | Skip raw prompt/response/tool I/O capture. Spans still emit with timing, token usage, model name, and ids. |
|
|
34
|
+
| `--no-content` | Skip raw prompt/response/tool I/O capture. Spans still emit with timing, token usage, model name, and ids. Mirrored into both `config.allowConversationAccess` and `hooks.allowConversationAccess`. |
|
|
35
|
+
| `--no-trust` | Skip auto-adding the plugin id to `plugins.allow`. OpenClaw will keep printing `plugins.allow is empty` warnings until you add it manually. |
|
|
34
36
|
|
|
35
|
-
Re-running `install` is idempotent —
|
|
37
|
+
Re-running `install` is idempotent — credentials/baseUrl are overwritten from prompts, but hand-edited `enabled`, `debug`, and `allowConversationAccess` values in `openclaw.json` are preserved unless you pass the corresponding flag.
|
|
36
38
|
|
|
37
39
|
## Uninstall
|
|
38
40
|
|
|
@@ -40,66 +42,115 @@ Re-running `install` is idempotent — existing values are preserved.
|
|
|
40
42
|
npx -y @latitude-data/openclaw-telemetry uninstall
|
|
41
43
|
```
|
|
42
44
|
|
|
43
|
-
Shows a plan, asks for confirmation, then removes the plugin entry
|
|
45
|
+
Shows a plan, asks for confirmation, then runs `openclaw plugins uninstall @latitude-data/openclaw-telemetry --force` (which removes files, the install record, the plugin entry, and the `plugins.allow` entry). Defensive cleanup follows for any leftover keys, with a backup at `openclaw.json.latitude-bak`.
|
|
44
46
|
|
|
45
47
|
## What gets sent
|
|
46
48
|
|
|
47
|
-
For each agent run, the plugin emits one trace
|
|
49
|
+
For each agent run, the plugin emits one trace shaped like the actual run:
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
```
|
|
52
|
+
agent (root, traceId = hash(runId))
|
|
53
|
+
├─ compaction (0..1, rare; budget-triggered)
|
|
54
|
+
├─ model_call (1..N, one per provider API call)
|
|
55
|
+
├─ tool_call: foo (between model_calls; sibling of agent)
|
|
56
|
+
├─ model_call
|
|
57
|
+
├─ tool_call: bar
|
|
58
|
+
├─ subagent (0..N — the child's full agent tree nests under here)
|
|
59
|
+
│ └─ agent
|
|
60
|
+
│ ├─ model_call
|
|
61
|
+
│ └─ tool_call: ...
|
|
62
|
+
└─ model_call (final)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Five span kinds:
|
|
66
|
+
|
|
67
|
+
- **`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. This is where attempt-aggregate `gen_ai.*` lands.
|
|
68
|
+
- **`model_call`** — one per actual 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`.
|
|
69
|
+
- **`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.
|
|
70
|
+
- **`compaction`** — rare; fires when OpenClaw hits the message budget mid-run. Records before/after message counts and the compacted-out count.
|
|
71
|
+
- **`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.
|
|
52
72
|
|
|
53
|
-
Every span carries `openclaw.agent.id` and `openclaw.agent.name`. Multi-agent
|
|
73
|
+
Every span carries `openclaw.agent.id` and `openclaw.agent.name`. Multi-agent setups produce spans tagged with the invoking agent's id, letting you filter and group by agent in the Latitude UI.
|
|
54
74
|
|
|
55
|
-
All spans share the run
|
|
75
|
+
All spans share the same `traceId` so they group as one trace per agent run (and one trace per spawn tree, by virtue of the subagent linkage).
|
|
76
|
+
|
|
77
|
+
### Backend caveat: Codex / Claude-Code-style providers
|
|
78
|
+
|
|
79
|
+
OpenClaw's `model_call_started` / `model_call_ended` hooks fire from its `selection` layer, which wraps the agent's `streamFn` invocation. For "agentic" backends (Codex, Claude Code) the inner generations happen inside the backend's own loop and don't surface as separate `model_call` events. Result: a Codex-backed run shows ONE `model_call` per attempt instead of N. Anthropic and OpenAI direct don't have this issue. The fix is upstream in OpenClaw — out of scope for this plugin.
|
|
56
80
|
|
|
57
81
|
## How it works
|
|
58
82
|
|
|
59
|
-
OpenClaw
|
|
83
|
+
We subscribe to OpenClaw's typed plugin hooks (`src/plugins/hook-types.ts` upstream). The model is "one span per paired before/after (or start/end) event":
|
|
84
|
+
|
|
85
|
+
| Span | Start hook | End hook |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| `agent` | `before_agent_start` | `agent_end` |
|
|
88
|
+
| `model_call` | `model_call_started` | `model_call_ended` |
|
|
89
|
+
| `tool_call` | `before_tool_call` | `after_tool_call` |
|
|
90
|
+
| `compaction` | `before_compaction` | `after_compaction` |
|
|
91
|
+
| `subagent` | `subagent_spawned` | `subagent_ended` |
|
|
60
92
|
|
|
61
|
-
|
|
62
|
-
- `llm_output` — assistant text, last assistant message, full token usage (input/output/cacheRead/cacheWrite/total), resolved provider/model ref.
|
|
63
|
-
- `before_tool_call` / `after_tool_call` — tool name, arguments, result, error, duration.
|
|
64
|
-
- `agent_end` — run completion signal. This is when we build the OTLP trace and POST it.
|
|
65
|
-
- `session_start` — currently a no-op; reserved for future session-level metadata.
|
|
93
|
+
Two more hooks (`llm_input`, `llm_output`) are subscribed to for **content only** — they don't open or close spans, they just enrich the `agent` span with attempt-aggregate data and seed the rolling history snapshot used by per-call `model_call.gen_ai.input.messages`.
|
|
66
94
|
|
|
67
|
-
|
|
95
|
+
The hook system runs handlers fire-and-forget (see [`src/plugins/hooks.ts`](https://github.com/openclaw/openclaw/blob/main/src/plugins/hooks.ts) upstream), so nothing we do here can slow the agent loop. The one exception is `before_tool_call`, which is a `runModifyingHook` — our handler returns `undefined` so OpenClaw dispatches the tool normally. Returning anything else (e.g. `{block: true}`) would block every tool call.
|
|
68
96
|
|
|
69
|
-
**No runtime wrapping.** Unlike
|
|
97
|
+
**No runtime wrapping.** Unlike third-party OpenClaw observability plugins that try to monkey-patch `@mariozechner/pi-ai` (and run into jiti's CJS/ESM module isolation), we stay inside the supported plugin API. The hooks give us everything, at lower risk of breaking on OpenClaw updates.
|
|
70
98
|
|
|
71
99
|
## Configuration reference
|
|
72
100
|
|
|
73
|
-
The installer writes
|
|
101
|
+
The installer writes two blocks to `plugins.entries["@latitude-data/openclaw-telemetry"]`:
|
|
74
102
|
|
|
75
|
-
###
|
|
103
|
+
### `.config` — read by the plugin's runtime
|
|
76
104
|
|
|
77
105
|
| Key | Required | Default | Description |
|
|
78
106
|
| --- | --- | --- | --- |
|
|
79
107
|
| `apiKey` | yes | — | Bearer token for Latitude ingestion. |
|
|
80
108
|
| `project` | yes | — | Slug of the project to route traces into. |
|
|
81
109
|
| `baseUrl` | no | `https://ingest.latitude.so` | Override OTLP ingest origin. Installer sets this only when you pass `--staging` or `--dev`. |
|
|
82
|
-
| `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. |
|
|
110
|
+
| `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".** |
|
|
83
111
|
| `enabled` | no | `true` | Set to `false` to pause emission without uninstalling. |
|
|
84
112
|
| `debug` | no | `false` | Log diagnostic lines to stderr (visible in the gateway log). |
|
|
85
113
|
|
|
114
|
+
### `.hooks` — read by OpenClaw's runtime
|
|
115
|
+
|
|
116
|
+
| Key | Required | Default | Description |
|
|
117
|
+
| --- | --- | --- | --- |
|
|
118
|
+
| `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`. |
|
|
119
|
+
|
|
120
|
+
### The two flags
|
|
121
|
+
|
|
122
|
+
`hooks.allowConversationAccess` and `config.allowConversationAccess` mean different things:
|
|
123
|
+
|
|
124
|
+
- **`hooks.*`** is the **dispatch gate**. `false` → OpenClaw never forwards events to us. No traces.
|
|
125
|
+
- **`config.*`** is the **payload-content gate**. `false` → we emit spans normally but scrub message content from them. Structural-only telemetry.
|
|
126
|
+
|
|
127
|
+
For *this* plugin, we always couple them — the installer writes both from the same source. If you hand-edit, set them to the same value:
|
|
128
|
+
|
|
129
|
+
- **Both `true`**: full content capture (the default).
|
|
130
|
+
- **Both `false`**: structural-only telemetry (set via `--no-content`).
|
|
131
|
+
- Anything else is incoherent: `hooks: false + config: true` means dispatch is off and content gating is moot; `hooks: true + config: false` works but is what you'd get with both `false` plus extra ceremony.
|
|
132
|
+
|
|
86
133
|
### Environment variable fallbacks
|
|
87
134
|
|
|
88
|
-
If a key isn't set
|
|
135
|
+
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`. The installer doesn't set them — pluginConfig is the canonical surface — but they're useful for flipping `debug` without editing `openclaw.json`.
|
|
89
136
|
|
|
90
137
|
### Manual installation
|
|
91
138
|
|
|
92
|
-
If
|
|
139
|
+
If you can't run the installer, do exactly what it does:
|
|
93
140
|
|
|
94
|
-
1. **
|
|
95
|
-
2. **
|
|
141
|
+
1. **Hand placement to OpenClaw** with `openclaw plugins install <path-to-extracted-package> --force`. This is what populates `~/.openclaw/extensions/`, writes the install record, and creates the (initially empty) `plugins.entries[id]` block. Don't hand-place files into the extensions directory — the persisted plugin index won't see them.
|
|
142
|
+
2. **Add the config + hooks block** to `~/.openclaw/openclaw.json`:
|
|
96
143
|
|
|
97
144
|
```jsonc
|
|
98
145
|
{
|
|
99
146
|
"plugins": {
|
|
147
|
+
"allow": ["@latitude-data/openclaw-telemetry"],
|
|
100
148
|
"entries": {
|
|
101
149
|
"@latitude-data/openclaw-telemetry": {
|
|
102
150
|
"enabled": true,
|
|
151
|
+
"hooks": {
|
|
152
|
+
"allowConversationAccess": true
|
|
153
|
+
},
|
|
103
154
|
"config": {
|
|
104
155
|
"apiKey": "lat_xxx",
|
|
105
156
|
"project": "my-openclaw-project",
|
|
@@ -111,21 +162,24 @@ If the installer doesn't fit your setup, you need two things:
|
|
|
111
162
|
}
|
|
112
163
|
```
|
|
113
164
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
After editing, run `openclaw config validate` — it should print `valid: true`. Then `openclaw gateway restart`.
|
|
165
|
+
Run `openclaw config validate` — should print `valid: true`. Then `openclaw gateway restart`.
|
|
117
166
|
|
|
118
167
|
## Privacy
|
|
119
168
|
|
|
120
|
-
|
|
169
|
+
There are two defaults at play — keep them straight:
|
|
170
|
+
|
|
171
|
+
- **Installer default**: when you run `npx -y @latitude-data/openclaw-telemetry install` without `--no-content`, the installer writes `allowConversationAccess: true` to both the `hooks` and `config` blocks. **You get full content capture** — prompts, assistant responses, system instructions, tool I/O — shipped to Latitude alongside structural telemetry.
|
|
172
|
+
- **Runtime default for absent keys**: if you hand-write `openclaw.json` and leave `allowConversationAccess` out entirely, `config.allowConversationAccess` falls back to `false` at the plugin's runtime (privacy-preserving) and `hooks.allowConversationAccess` falls back to OpenClaw's default (also `false`, which means dispatch is blocked and you get nothing). **Manual installs without those keys produce no traces, not "structural-only traces".**
|
|
173
|
+
|
|
174
|
+
The installer always writes both keys to the same value, so the installer-driven path is unambiguous. The runtime-default trap only matters for hand-rolled configs.
|
|
121
175
|
|
|
122
|
-
When
|
|
176
|
+
When you pass `--no-content` (or hand-edit both flags to `false`), we emit the same span tree but scrub the content attributes (`gen_ai.input.messages`, `gen_ai.output.messages`, `gen_ai.system_instructions`, `gen_ai.tool.call.arguments`/`result`, the interaction's `user_prompt`). Timings, token usage, model name, agent name, ids, and the `latitude.captured.content: false` boolean still flow.
|
|
123
177
|
|
|
124
|
-
To pause emission entirely without uninstalling, set `LATITUDE_OPENCLAW_ENABLED=0` in the gateway environment.
|
|
178
|
+
To pause emission entirely without uninstalling, set `LATITUDE_OPENCLAW_ENABLED=0` in the gateway environment, or set `enabled: false` on the plugin entry in `openclaw.json`.
|
|
125
179
|
|
|
126
180
|
## Supported OpenClaw versions
|
|
127
181
|
|
|
128
|
-
Requires OpenClaw **2026.
|
|
182
|
+
Requires OpenClaw **2026.4.25 or newer**. The installer detects the version up-front via `openclaw --version` and aborts with an upgrade message on older versions — supporting the full range with portability shims would mean shipping known-broken behaviour (≤ 2026.4.21 reject `hooks.allowConversationAccess` outright; 2026.4.22 – 2026.4.24 have unverified dispatch gating). Run `npm install -g openclaw@latest` to upgrade.
|
|
129
183
|
|
|
130
184
|
## How it fails
|
|
131
185
|
|