nemo-relay-openclaw 0.6.0 → 0.7.0-rc.2

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
@@ -8,8 +8,8 @@ SPDX-License-Identifier: Apache-2.0
8
8
  `nemo-relay-openclaw` is the NeMo Relay observability plugin package for
9
9
  OpenClaw. It converts supported OpenClaw hook events into NeMo Relay sessions,
10
10
  LLM spans, tool spans, and lifecycle marks that the generic NeMo Relay
11
- observability component can export as ATIF JSON, OpenTelemetry spans, and
12
- OpenInference/Phoenix spans. The same generic plugin config path can initialize
11
+ observability component can export as ATIF JSON and typed OpenTelemetry spans,
12
+ including the OpenInference projection for Phoenix. The same generic plugin config path can initialize
13
13
  Adaptive components for hook-backed telemetry learning.
14
14
 
15
15
  This public OpenClaw plugin package uses OpenClaw public hooks. It can run
@@ -33,8 +33,8 @@ through a public plugin hook.
33
33
  - Generic NeMo Relay plugin initialization through `config.plugins`.
34
34
  - ATIF JSON export through the built-in `observability` component.
35
35
  - Adaptive plugin initialization through `config.plugins`.
36
- - Optional OpenTelemetry OTLP export.
37
- - Optional OpenInference/Phoenix OTLP export.
36
+ - Optional typed OpenTelemetry OTLP endpoints using `full`, `gen_ai`, or
37
+ `openinference` projection.
38
38
  - Bounded LLM replay correlation across supported OpenClaw hooks.
39
39
  - Tool span replay with conservative privacy defaults.
40
40
  - Admin-scoped `nemoRelay.status` gateway health method.
@@ -82,7 +82,7 @@ OpenClaw plugin configuration under `plugins.entries["nemo-relay"].config`:
82
82
  "kind": "observability",
83
83
  "enabled": true,
84
84
  "config": {
85
- "version": 1,
85
+ "version": 3,
86
86
  "atif": {
87
87
  "enabled": true,
88
88
  "agent_name": "openclaw",
@@ -90,15 +90,20 @@ OpenClaw plugin configuration under `plugins.entries["nemo-relay"].config`:
90
90
  },
91
91
  "opentelemetry": {
92
92
  "enabled": false,
93
- "transport": "http_binary",
94
- "endpoint": "http://localhost:4318/v1/traces",
95
- "service_name": "openclaw-nemo-relay"
96
- },
97
- "openinference": {
98
- "enabled": false,
99
- "transport": "http_binary",
100
- "endpoint": "http://localhost:6006/v1/traces",
101
- "service_name": "openclaw-nemo-relay"
93
+ "endpoints": [
94
+ {
95
+ "type": "full",
96
+ "transport": "http_binary",
97
+ "endpoint": "http://localhost:4318/v1/traces",
98
+ "service_name": "openclaw-nemo-relay"
99
+ },
100
+ {
101
+ "type": "openinference",
102
+ "transport": "http_binary",
103
+ "endpoint": "http://localhost:6006/v1/traces",
104
+ "service_name": "openclaw-nemo-relay"
105
+ }
106
+ ]
102
107
  }
103
108
  }
104
109
  },
@@ -156,11 +161,10 @@ do not use, or set their `enabled` fields to `false`.
156
161
  `adaptive`.
157
162
  - `config.plugins.components[].config.atif` writes ATIF trajectory JSON files.
158
163
  Set `output_directory` to the directory where OpenClaw should write files.
159
- - `config.plugins.components[].config.opentelemetry` sends generic OTLP spans to
160
- an OpenTelemetry collector when `enabled` is `true`.
161
- - `config.plugins.components[].config.openinference` sends OpenInference OTLP
162
- spans to Phoenix or another OpenInference-compatible collector when `enabled`
163
- is `true`.
164
+ - `config.plugins.components[].config.opentelemetry.endpoints` sends typed OTLP
165
+ spans when the OpenTelemetry section is enabled. Use `full` for the complete
166
+ NeMo Relay projection, `gen_ai` for standardized GenAI conventions, or
167
+ `openinference` for Phoenix and other OpenInference-compatible collectors.
164
168
  - `config.plugins.components[]` entries with `kind: "adaptive"` initialize the
165
169
  Adaptive plugin. In hook-backed OpenClaw mode, adaptive telemetry can consume
166
170
  replayed NeMo Relay events, while request-rewrite features such as adaptive
@@ -191,9 +195,9 @@ sink:
191
195
 
192
196
  - ATIF: confirm JSON files appear in the configured
193
197
  `config.plugins.components[].config.atif.output_directory`.
194
- - OpenTelemetry: confirm spans arrive at the configured OTLP collector.
195
- - OpenInference: confirm spans arrive at the configured OpenInference/Phoenix
196
- endpoint.
198
+ - Typed OpenTelemetry: confirm spans arrive at every configured OTLP endpoint;
199
+ for an `openinference` endpoint, confirm the projected spans appear in
200
+ Phoenix or another OpenInference-compatible backend.
197
201
 
198
202
  The plugin also registers the `operator.admin` scoped gateway method
199
203
  `nemoRelay.status`. If your CLI is already paired with admin-capable gateway
@@ -229,9 +233,9 @@ session emits the relevant LLM, message-write, and tool hooks.
229
233
 
230
234
  If no export output appears, verify
231
235
  `config.plugins.components[].config.atif.output_directory`,
232
- `config.plugins.components[].config.opentelemetry.endpoint`, or
233
- `config.plugins.components[].config.openinference.endpoint`, then confirm the
234
- configured collector or output directory is reachable.
236
+ or each
237
+ `config.plugins.components[].config.opentelemetry.endpoints[].endpoint`, then
238
+ confirm the configured collector or output directory is reachable.
235
239
 
236
240
  ## Development
237
241
 
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import { registerNemoRelayPlugin } from './src/runtime-state.js';
12
12
  const nemoRelayPluginEntry = definePluginEntry({
13
13
  id: 'nemo-relay',
14
14
  name: 'NeMo Relay Observability',
15
- description: 'ATIF, OpenInference, and OpenTelemetry telemetry through NeMo Relay',
15
+ description: 'ATIF and typed OpenTelemetry telemetry through NeMo Relay',
16
16
  configSchema: nemoRelayConfigSchema,
17
17
  register(api) {
18
18
  registerNemoRelayPlugin(api);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "nemo-relay",
3
3
  "name": "NeMo Relay Observability",
4
- "description": "ATIF, OpenInference, and OpenTelemetry telemetry through NeMo Relay.",
4
+ "description": "ATIF and typed OpenTelemetry telemetry through NeMo Relay.",
5
5
  "activation": {
6
6
  "onStartup": true
7
7
  },
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "plugins": {
27
27
  "$ref": "#/$defs/nemoRelayPluginConfig",
28
- "description": "Generic NeMo Relay plugin configuration document. Use the observability component to configure ATIF, OpenTelemetry, and OpenInference exporters."
28
+ "description": "Generic NeMo Relay plugin configuration document. Use the observability component to configure ATIF and typed OpenTelemetry exporters."
29
29
  },
30
30
  "capture": {
31
31
  "type": "object",
@@ -119,7 +119,7 @@ function rejectRemovedFields(raw) {
119
119
  throw new Error('atif was removed; configure plugins.components[].config.atif on the observability component');
120
120
  }
121
121
  if (raw.telemetry !== undefined) {
122
- throw new Error('telemetry was removed; configure plugins.components[].config.opentelemetry or openinference on the observability component');
122
+ throw new Error('telemetry was removed; configure plugins.components[].config.opentelemetry.endpoints on the observability component');
123
123
  }
124
124
  }
125
125
  /** Keep parser behavior aligned with the manifest's additionalProperties=false contract. */
@@ -36,8 +36,13 @@ function configuredObservabilityOutputs(config) {
36
36
  }
37
37
  const componentConfig = asRecord(record.config);
38
38
  outputs.atif ||= sectionEnabled(componentConfig?.atif);
39
- outputs.otel ||= sectionEnabled(componentConfig?.opentelemetry);
40
- outputs.openInference ||= sectionEnabled(componentConfig?.openinference);
39
+ const opentelemetry = asRecord(componentConfig?.opentelemetry);
40
+ const opentelemetryEnabled = sectionEnabled(opentelemetry);
41
+ outputs.otel ||= opentelemetryEnabled;
42
+ outputs.openInference ||=
43
+ opentelemetryEnabled &&
44
+ Array.isArray(opentelemetry?.endpoints) &&
45
+ opentelemetry.endpoints.some((endpoint) => asRecord(endpoint)?.type === 'openinference');
41
46
  }
42
47
  return outputs;
43
48
  }
@@ -281,7 +281,7 @@ export class HookReplayBackend {
281
281
  this.materializeDeferredSessionRoot(session);
282
282
  drainSession(this.sessionManager(), session);
283
283
  closeSessionRoot(this.sessionManager(), session, summary, session.finalOutput ?? summary, metadata);
284
- this.flushSubscriberDelivery('session_close');
284
+ await this.flushSubscriberDelivery('session_close');
285
285
  this.forgetPendingSubagentLineage(session);
286
286
  deleteSession(this.stateValue, session);
287
287
  }
@@ -316,9 +316,9 @@ export class HookReplayBackend {
316
316
  }
317
317
  }
318
318
  /** Wait for native subscriber/exporter delivery after a replay closure boundary. */
319
- flushSubscriberDelivery(label) {
319
+ async flushSubscriberDelivery(label) {
320
320
  try {
321
- this.nf.flushSubscribers?.();
321
+ await this.nf.flushSubscribers?.();
322
322
  }
323
323
  catch (error) {
324
324
  this.logBoundedWarn(`flush-subscribers:${label}`, `nemo-relay subscriber flush failed: label=${label} error=${toMessage(error)}`);
package/index.ts CHANGED
@@ -17,7 +17,7 @@ type NemoRelayPluginEntry = ReturnType<typeof definePluginEntry>;
17
17
  const nemoRelayPluginEntry: NemoRelayPluginEntry = definePluginEntry({
18
18
  id: 'nemo-relay',
19
19
  name: 'NeMo Relay Observability',
20
- description: 'ATIF, OpenInference, and OpenTelemetry telemetry through NeMo Relay',
20
+ description: 'ATIF and typed OpenTelemetry telemetry through NeMo Relay',
21
21
  configSchema: nemoRelayConfigSchema,
22
22
  register(api: OpenClawPluginApi) {
23
23
  registerNemoRelayPlugin(api);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "nemo-relay",
3
3
  "name": "NeMo Relay Observability",
4
- "description": "ATIF, OpenInference, and OpenTelemetry telemetry through NeMo Relay.",
4
+ "description": "ATIF and typed OpenTelemetry telemetry through NeMo Relay.",
5
5
  "activation": {
6
6
  "onStartup": true
7
7
  },
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "plugins": {
27
27
  "$ref": "#/$defs/nemoRelayPluginConfig",
28
- "description": "Generic NeMo Relay plugin configuration document. Use the observability component to configure ATIF, OpenTelemetry, and OpenInference exporters."
28
+ "description": "Generic NeMo Relay plugin configuration document. Use the observability component to configure ATIF and typed OpenTelemetry exporters."
29
29
  },
30
30
  "capture": {
31
31
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nemo-relay-openclaw",
3
- "version": "0.6.0",
3
+ "version": "0.7.0-rc.2",
4
4
  "description": "NeMo Relay-authored observability plugin for OpenClaw.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -63,7 +63,7 @@
63
63
  "openclaw": "^2026.7.1"
64
64
  },
65
65
  "dependencies": {
66
- "nemo-relay-node": "0.6.0"
66
+ "nemo-relay-node": "0.7.0-rc.2"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/node": "^24.0.0",
package/src/config.ts CHANGED
@@ -178,7 +178,7 @@ function rejectRemovedFields(raw: Record<string, unknown>): void {
178
178
  }
179
179
  if (raw.telemetry !== undefined) {
180
180
  throw new Error(
181
- 'telemetry was removed; configure plugins.components[].config.opentelemetry or openinference on the observability component',
181
+ 'telemetry was removed; configure plugins.components[].config.opentelemetry.endpoints on the observability component',
182
182
  );
183
183
  }
184
184
  }
package/src/health.ts CHANGED
@@ -85,8 +85,13 @@ function configuredObservabilityOutputs(config: NemoRelayHookBackendConfig): {
85
85
 
86
86
  const componentConfig = asRecord(record.config);
87
87
  outputs.atif ||= sectionEnabled(componentConfig?.atif);
88
- outputs.otel ||= sectionEnabled(componentConfig?.opentelemetry);
89
- outputs.openInference ||= sectionEnabled(componentConfig?.openinference);
88
+ const opentelemetry = asRecord(componentConfig?.opentelemetry);
89
+ const opentelemetryEnabled = sectionEnabled(opentelemetry);
90
+ outputs.otel ||= opentelemetryEnabled;
91
+ outputs.openInference ||=
92
+ opentelemetryEnabled &&
93
+ Array.isArray(opentelemetry?.endpoints) &&
94
+ opentelemetry.endpoints.some((endpoint) => asRecord(endpoint)?.type === 'openinference');
90
95
  }
91
96
 
92
97
  return outputs;
@@ -426,7 +426,7 @@ export class HookReplayBackend {
426
426
  this.materializeDeferredSessionRoot(session);
427
427
  drainSession(this.sessionManager(), session);
428
428
  closeSessionRoot(this.sessionManager(), session, summary, session.finalOutput ?? summary, metadata);
429
- this.flushSubscriberDelivery('session_close');
429
+ await this.flushSubscriberDelivery('session_close');
430
430
  this.forgetPendingSubagentLineage(session);
431
431
  deleteSession(this.stateValue, session);
432
432
  }
@@ -466,9 +466,9 @@ export class HookReplayBackend {
466
466
  }
467
467
 
468
468
  /** Wait for native subscriber/exporter delivery after a replay closure boundary. */
469
- private flushSubscriberDelivery(label: string): void {
469
+ private async flushSubscriberDelivery(label: string): Promise<void> {
470
470
  try {
471
- this.nf.flushSubscribers?.();
471
+ await this.nf.flushSubscribers?.();
472
472
  } catch (error) {
473
473
  this.logBoundedWarn(
474
474
  `flush-subscribers:${label}`,