@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 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 full system prompt, message history, assistant output, token usage, tool I/O, and the running agent's name on every span.
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.4.25 or newer** on PATH.
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
- ### Recommended — one-shot CLI
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.9 install
19
+ npx -y @latitude-data/openclaw-telemetry-cli@0.1.0 install
18
20
  ```
19
21
 
20
- It prompts for your API key and project slug, runs `openclaw plugins install` for you, writes the plugin entry into `openclaw.json`, adds the plugin to `plugins.allow`, validates the result, and (on TTY) offers to restart the gateway. See the [CLI README](https://github.com/latitude-dev/latitude-llm/tree/main/packages/telemetry/openclaw-cli#readme) for the full flag matrix, dry-run mode, custom config dir, and CI usage.
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
- Verify everything's wired:
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
- # {"valid": true, ...}
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
- Merge with whatever else is in `openclaw.json`. Then run `openclaw config validate` and `openclaw gateway restart`.
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.9 uninstall
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
- For each agent run, the plugin emits one trace shaped like the actual run:
59
+ One trace per agent run (a user turn, a cron run, a heartbeat, a subagent run):
155
60
 
156
61
  ```
157
- agent (root, traceId = hash(runId))
158
- ├─ compaction (0..1, rare; budget-triggered)
159
- ├─ model_call (1..N, one per provider API call)
160
- ├─ tool_call: foo (between model_calls; sibling of agent)
161
- ├─ model_call
162
- ├─ tool_call: bar
163
- ├─ subagent (0..N the child's full agent tree nests under here)
164
- │ └─ agent
165
- │ ├─ model_call
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
- Five span kinds:
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
- 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. 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).
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
- ### Backend caveat: Codex / Claude-Code-style providers
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
- 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.
79
+ ## Configuration
183
80
 
184
- ## How it works
81
+ Everything lives under `plugins.entries["@latitude-data/openclaw-telemetry"]`.
185
82
 
186
- 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":
83
+ ### `.config` read by the plugin
187
84
 
188
- | Span | Start hook | End hook |
85
+ | Key | Default | Description |
189
86
  | --- | --- | --- |
190
- | `agent` | `before_agent_start` | `agent_end` |
191
- | `model_call` | `model_call_started` | `model_call_ended` |
192
- | `tool_call` | `before_tool_call` | `after_tool_call` |
193
- | `compaction` | `before_compaction` | `after_compaction` |
194
- | `subagent` | `subagent_spawned` | `subagent_ended` |
195
-
196
- 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`.
197
-
198
- The hook system runs handlers fire-and-forget, 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.
199
-
200
- **No runtime wrapping.** We stay inside the supported plugin API rather than monkey-patching `@mariozechner/pi-ai`. The hooks give us everything, at lower risk of breaking on OpenClaw updates.
201
-
202
- ## Configuration reference
203
-
204
- Two blocks live under `plugins.entries["@latitude-data/openclaw-telemetry"]`:
205
-
206
- ### `.config` — read by the plugin's runtime
207
-
208
- | Key | Required | Default | Description |
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` and `config.allowConversationAccess` mean different things:
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
- - **`hooks.*`** is the **dispatch gate**. `false` OpenClaw never forwards events to us. No traces.
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
- For *this* plugin we always couple them — the CLI writes both from the same source. If you hand-edit:
115
+ ### Targeting staging or local dev
232
116
 
233
- - **Both `true`**: full content capture (the default).
234
- - `hooks: true` + `config: false`: structural-only telemetry (set via `--no-content`).
235
- - `hooks: false` + anything: no traces. Don't.
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's first-install default writes `allowConversationAccess: true` to both blocks → full content capture. Pass `--no-content` for structural-only telemetry.
122
+ The CLI has `--staging` / `--dev` for the same.
244
123
 
245
- For hand-edited configs, leaving `allowConversationAccess` out entirely produces **no traces** (not "structural-only traces") because `hooks.allowConversationAccess` defaults to `false` at OpenClaw's level and dispatch is blocked. Always set both keys explicitly.
124
+ ## How it fails
246
125
 
247
- To pause emission without uninstalling, set `enabled: false` on the plugin entry, or `LATITUDE_OPENCLAW_ENABLED=0` in the gateway environment.
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
- For field-level PII controls while keeping content capture enabled, add `config.redact`. For example, to send empty message arrays for prompts/responses before anything leaves the gateway:
128
+ ## Development
250
129
 
251
- ```jsonc
252
- {
253
- "plugins": {
254
- "entries": {
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, the surfaced first-prompt). Token counts, model
18
- * names, agent ids, and timings are unaffected.
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 the per-trace span tree for an OpenClaw agent run from the granular
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
- * agent (root)
42
- * ├─ compaction (0..1, rare)
43
- * ├─ model_call (1..N, one per provider API call)
44
- * ├─ tool_call: ... (interleaved between model_calls; siblings of agent)
45
- * ├─ subagent (0..N; child agent runs nest INSIDE these via
46
- * │ └─ agent ... cross-runId trace propagation)
47
- * └─ model_call (final)
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
- * Tool spans are siblings of `agent`, not children of `model_call`, because
50
- * tools run BETWEEN generations not during them. Nesting under model_call
51
- * would falsely imply concurrency.
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
- * Minimal structural type for OpenClaw's plugin API only the fields we
85
- * touch. We avoid importing from `openclaw/plugin-sdk` so the package stays
86
- * usable when OpenClaw isn't installed (the CLI and tests don't need it),
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?: 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
@@ -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":";UAEiB,YAAA;EACf,UAAA;EACA,IAAA;AAAA;;;UCFe,MAAA;EACf,MAAA;EACA,OAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;;;AALF;;;;EAYE,uBAAA;EACA,MAAA,GAAS,YAAA;AAAA;;;UCbM,MAAA;EACf,KAAA,GAAQ,GAAA;EACR,IAAA,GAAO,GAAA;AAAA;;;AFFT;;;;;;;;ACAA;;;;;;;;;;;;;;;;;ACAA;AFAA,UGoDiB,UAAA;;EAEf,MAAA;EDrDA;ECuDA,OAAA;EDtDA;ECwDA,YAAA;EDxDkB;EC0DlB,IAAA;EACA,OAAA;EACA,KAAA;;EAEA,KAAA,EAAO,MAAA,SAAe,SAAA;EAZG;EAczB,OAAA;EACA,YAAA;AAAA;AAAA,KAGU,SAAA,2CAAoD,MAAA;AAAA,UAoD/C,WAAA;EA9Df;EAgEA,KAAA;EA9DA;EAgEA,KAAA,EAAO,UAAA;AAAA;;;;;;;;;AF9HT;;;;;UG8BiB,qBAAA;EACf,MAAA,GAAS,MAAA;EACT,YAAA,GAAe,MAAA;EACf,EAAA,qBACE,QAAA,EAAU,CAAA,EACV,OAAA,GAAU,KAAA,WAAgB,GAAA,uBAC1B,IAAA;IAAS,QAAA;EAAA;AAAA;AAAA,UAII,eAAA;EH3BM;EG6BrB,MAAA,GAAS,MAAA;;EAET,MAAA,GAAS,MAAA;EF5CM;;;;EEiDf,MAAA,IAAU,MAAA,EAAQ,WAAA;AAAA;;;;;;;;ADGpB;;;;;iBCYwB,sBAAA,CAAuB,GAAA,EAAK,qBAAA,EAAuB,IAAA,GAAM,eAAA"}
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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"}