@runtypelabs/openclaw-adapter 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Runtype, Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Runtype telemetry for OpenClaw
2
+
3
+ `@runtypelabs/openclaw-adapter` is a native OpenClaw plugin. It observes agent,
4
+ model, and tool hooks and sends one OTLP/JSON trace per completed OpenClaw turn to
5
+ Runtype. It does not host or change the agent.
6
+
7
+ ## Install
8
+
9
+ Set these environment variables on the OpenClaw Gateway process:
10
+
11
+ ```sh
12
+ RUNTYPE_API_KEY=rt_... # Runtype key with TELEMETRY:WRITE
13
+ RUNTYPE_AGENT_ID=agent_... # Saved Runtype agent that owns the run
14
+ RUNTYPE_CAPTURE_CONTENT=true # Optional; needed for eval case capture
15
+ ```
16
+
17
+ `RUNTYPE_OTLP_URL` optionally overrides the default
18
+ `https://api.runtype.com/v1/otel/v1/traces`. Use it only for a trusted OTLP
19
+ receiver; the adapter sends the telemetry key to that URL.
20
+
21
+ ```sh
22
+ openclaw plugins install @runtypelabs/openclaw-adapter
23
+ openclaw plugins enable runtype
24
+ openclaw config set plugins.entries.runtype.hooks.allowConversationAccess true --strict-json
25
+ openclaw plugins inspect runtype --runtime
26
+ ```
27
+
28
+ Reload the plugin or restart the Gateway if it was running before these changes.
29
+ In an interactive terminal, review OpenClaw's package-source and capability
30
+ prompts before accepting them. Noninteractive installation requires `--force`
31
+ and `--accept-capabilities` after independent review; do not add those flags to
32
+ an unattended setup that has not been reviewed.
33
+ The `allowConversationAccess` grant is required for a non-bundled plugin to
34
+ receive `llm_input`, `llm_output`, and `agent_end` hooks. Review the plugin before
35
+ granting it access to conversations. See [OpenClaw's hook documentation](https://docs.openclaw.ai/plugins/hooks).
36
+
37
+ For direct `OPENAI_API_KEY` use, pin the model's OpenClaw runtime rather than
38
+ letting the host select its Codex runtime. For example, under
39
+ `agents.defaults.models["openai/gpt-4.1-mini"]`, set
40
+ `"agentRuntime": { "id": "openclaw" }`. This controls OpenClaw model routing;
41
+ it is not an adapter setting.
42
+
43
+ ## What is captured
44
+
45
+ The adapter emits `invoke_agent` and `chat` spans using GenAI semantic
46
+ conventions, and `execute_tool` spans for completed tool hooks. OpenClaw's
47
+ `llm_input` and `llm_output` hooks describe an entire attempt, not each physical
48
+ provider call, so a `chat` span carries attempt-level model and usage data rather
49
+ than per-call timing or token attribution. Missing attempt usage is not silently
50
+ replaced with zero in the run total. If native provider-call metadata shows a
51
+ fallback across different models, the adapter omits aggregate usage rather
52
+ than pricing it all against one model. Runtype attributes each trace to
53
+ `RUNTYPE_AGENT_ID` and identifies its source as
54
+ `@runtypelabs/openclaw-adapter`.
55
+
56
+ Prompt, answer, system-instruction, and tool payloads are **off by default**.
57
+ Set `RUNTYPE_CAPTURE_CONTENT=true` to include bounded input/output messages
58
+ for eval case capture. The adapter removes values of environment variables
59
+ whose names indicate API keys, tokens, secrets, passwords, or credentials, and
60
+ omits any content value over 32 KiB after redaction. Review what your agent
61
+ handles before enabling content; this is not a general-purpose DLP system.
62
+ OpenClaw may suppress conversation hooks for privacy-sensitive sessions; the
63
+ plugin does not reconstruct that content from other hooks. Such runs can have
64
+ a structural trace without an eval-capturable transcript.
65
+
66
+ ## Delivery limits
67
+
68
+ The `agent_end` hook waits up to one second for a pending model or tool
69
+ observation, because OpenClaw can deliver `llm_output` just after `agent_end`.
70
+ It flushes immediately when the pending observation arrives and does not
71
+ invent an output if the grace expires. It then makes one HTTP submission, with
72
+ a 10-second timeout. Telemetry failure does not fail the agent turn. OpenClaw can dispatch
73
+ channel-backed `agent_end` hooks without waiting for them, and this adapter has
74
+ no durable retry queue; process exit or network failure can lose a trace.
75
+ This is observational telemetry, not a control or approval mechanism. Runtype
76
+ cannot re-run telemetry-only agents without a callable external-agent endpoint.
@@ -0,0 +1,32 @@
1
+ //#region src/index.d.ts
2
+ type HookEvent = Record<string, unknown>;
3
+ type HookContext = Record<string, unknown>;
4
+ type HookHandler = (event: HookEvent, context: HookContext) => void | Promise<void>;
5
+ export interface OpenClawHookApi {
6
+ on(name: string, handler: HookHandler): void;
7
+ logger?: {
8
+ warn(message: string): void;
9
+ };
10
+ }
11
+ export interface OpenClawAdapterConfig {
12
+ agentId: string;
13
+ apiKey: string;
14
+ otlpUrl: string;
15
+ captureContent?: boolean;
16
+ onDiagnostic?: (code: string) => void;
17
+ }
18
+ export declare function createRuntypeOpenClawPlugin(config: OpenClawAdapterConfig): {
19
+ id: string;
20
+ name: string;
21
+ description: string;
22
+ register(api: OpenClawHookApi): void;
23
+ };
24
+ declare const environmentPlugin: {
25
+ id: string;
26
+ name: string;
27
+ description: string;
28
+ register(api: OpenClawHookApi): void;
29
+ };
30
+ //#endregion
31
+ export { environmentPlugin as default };
32
+ export {}
package/dist/index.mjs ADDED
@@ -0,0 +1,327 @@
1
+ import { createHash } from "node:crypto";
2
+ //#region package.json
3
+ var version = "0.1.0";
4
+ //#endregion
5
+ //#region src/index.ts
6
+ const MAX_CONTENT_BYTES = 32768;
7
+ const ADAPTER_VERSION = version;
8
+ function string(value) {
9
+ return typeof value === "string" && value.length > 0 ? value : void 0;
10
+ }
11
+ function nativeId(event, context) {
12
+ return string(event.runId) ?? string(context.runId);
13
+ }
14
+ function id(value, bytes) {
15
+ return createHash("sha256").update(value).digest("hex").slice(0, bytes * 2);
16
+ }
17
+ function nanos(milliseconds) {
18
+ return (BigInt(Math.trunc(milliseconds)) * 1000000n).toString();
19
+ }
20
+ function number(value) {
21
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
22
+ }
23
+ function otlpAttributes(values) {
24
+ return Object.entries(values).map(([key, value]) => ({
25
+ key,
26
+ value: typeof value === "string" ? { stringValue: value } : typeof value === "boolean" ? { boolValue: value } : Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value }
27
+ }));
28
+ }
29
+ function configuredSecrets(apiKey) {
30
+ const secrets = Object.entries(process.env).filter(([name, value]) => /(?:API_KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)/i.test(name) && value && value.length >= 8).map(([, value]) => value);
31
+ secrets.push(apiKey);
32
+ return [...new Set(secrets)].sort((a, b) => b.length - a.length);
33
+ }
34
+ function capture(value, secrets, enabled) {
35
+ if (!enabled || value === void 0 || value === null) return void 0;
36
+ let serialized;
37
+ try {
38
+ serialized = JSON.stringify(value);
39
+ } catch {
40
+ return;
41
+ }
42
+ for (const secret of secrets) serialized = serialized.replaceAll(JSON.stringify(secret).slice(1, -1), "[REDACTED]");
43
+ return Buffer.byteLength(serialized, "utf8") <= MAX_CONTENT_BYTES ? serialized : void 0;
44
+ }
45
+ function message(role, value, secrets, enabled) {
46
+ return typeof value === "string" && value.length > 0 ? capture([{
47
+ role,
48
+ content: value
49
+ }], secrets, enabled) : void 0;
50
+ }
51
+ function assistantFailed(assistant) {
52
+ if (!assistant || typeof assistant !== "object") return false;
53
+ const final = assistant;
54
+ return final.stopReason === "error" || final.isError === true;
55
+ }
56
+ function finalAssistantFailed(messages) {
57
+ if (!Array.isArray(messages)) return false;
58
+ return assistantFailed([...messages].reverse().find((item) => item && typeof item === "object" && item.role === "assistant"));
59
+ }
60
+ function makeSpan(nativeRunId, operation, name, start, end, attributes, statusCode) {
61
+ return {
62
+ traceId: id(`openclaw:${nativeRunId}`, 16),
63
+ spanId: id(`openclaw:${nativeRunId}:${operation}`, 8),
64
+ parentSpanId: id(`openclaw:${nativeRunId}:root`, 8),
65
+ name,
66
+ startTimeUnixNano: nanos(start),
67
+ endTimeUnixNano: nanos(Math.max(start, end)),
68
+ attributes,
69
+ status: { code: statusCode }
70
+ };
71
+ }
72
+ function encode(spans, agentId) {
73
+ return JSON.stringify({ resourceSpans: [{
74
+ resource: { attributes: otlpAttributes({
75
+ "runtype.agent.id": agentId,
76
+ "runtype.adapter.name": "@runtypelabs/openclaw-adapter",
77
+ "runtype.adapter.version": ADAPTER_VERSION
78
+ }) },
79
+ scopeSpans: [{
80
+ scope: {
81
+ name: "@runtypelabs/openclaw-adapter",
82
+ version: ADAPTER_VERSION
83
+ },
84
+ spans: spans.map((span) => ({
85
+ traceId: span.traceId,
86
+ spanId: span.spanId,
87
+ ...span.parentSpanId ? { parentSpanId: span.parentSpanId } : {},
88
+ name: span.name,
89
+ startTimeUnixNano: span.startTimeUnixNano,
90
+ endTimeUnixNano: span.endTimeUnixNano,
91
+ attributes: otlpAttributes(span.attributes),
92
+ status: span.status
93
+ }))
94
+ }]
95
+ }] });
96
+ }
97
+ function createRuntypeOpenClawPlugin(config) {
98
+ const active = /* @__PURE__ */ new Map();
99
+ const completed = /* @__PURE__ */ new Set();
100
+ const finishing = /* @__PURE__ */ new Set();
101
+ const secrets = configuredSecrets(config.apiKey);
102
+ const content = config.captureContent === true;
103
+ function run(nativeRunId) {
104
+ let current = active.get(nativeRunId);
105
+ if (current) return current;
106
+ if (active.size >= 1024) active.delete(active.keys().next().value);
107
+ current = {
108
+ startedAt: Date.now(),
109
+ inputs: [],
110
+ tools: /* @__PURE__ */ new Map(),
111
+ spans: [],
112
+ modelRefs: /* @__PURE__ */ new Set(),
113
+ inputTokens: 0,
114
+ outputTokens: 0,
115
+ measuredModels: 0,
116
+ cacheReadTokens: 0,
117
+ cacheWriteTokens: 0,
118
+ cacheReadModels: 0,
119
+ cacheWriteModels: 0
120
+ };
121
+ active.set(nativeRunId, current);
122
+ return current;
123
+ }
124
+ function diagnostic(code) {
125
+ try {
126
+ config.onDiagnostic?.(code);
127
+ } catch {}
128
+ }
129
+ function settlePending(current) {
130
+ if (current.inputs.length === 0 && current.tools.size === 0) current.onSettled?.();
131
+ }
132
+ async function deliver(payload) {
133
+ try {
134
+ if (!(await fetch(config.otlpUrl, {
135
+ method: "POST",
136
+ redirect: "manual",
137
+ signal: AbortSignal.timeout(1e4),
138
+ headers: {
139
+ authorization: `Bearer ${config.apiKey}`,
140
+ "content-type": "application/json",
141
+ "x-runtype-agent-id": config.agentId
142
+ },
143
+ body: payload
144
+ })).ok) diagnostic("delivery_failed");
145
+ } catch {
146
+ diagnostic("delivery_failed");
147
+ }
148
+ }
149
+ return {
150
+ id: "runtype",
151
+ name: "Runtype Telemetry",
152
+ description: "Exports OpenClaw agent turns to Runtype",
153
+ register(api) {
154
+ api.on("llm_input", (event, context) => {
155
+ const key = nativeId(event, context);
156
+ if (!key || completed.has(key) || finishing.has(key)) return;
157
+ run(key).inputs.push({
158
+ at: Date.now(),
159
+ event
160
+ });
161
+ });
162
+ api.on("model_call_started", (event, context) => {
163
+ const key = nativeId(event, context);
164
+ const provider = string(event.provider);
165
+ const model = string(event.model);
166
+ if (!key || !provider || !model || completed.has(key)) return;
167
+ run(key).modelRefs.add(`${provider}/${model}`);
168
+ });
169
+ api.on("llm_output", (event, context) => {
170
+ const key = nativeId(event, context);
171
+ if (!key || completed.has(key)) return;
172
+ const current = run(key);
173
+ const input = current.inputs.shift();
174
+ const provider = string(event.provider) ?? string(input?.event.provider) ?? "unknown";
175
+ const model = string(event.model) ?? string(input?.event.model) ?? "unknown";
176
+ const texts = Array.isArray(event.assistantTexts) ? event.assistantTexts.filter((item) => typeof item === "string") : [];
177
+ const inputMessages = message("user", input?.event.prompt, secrets, content);
178
+ const outputMessages = message("assistant", texts.join("\n"), secrets, content);
179
+ const system = capture(input?.event.systemPrompt, secrets, content);
180
+ const usage = event.usage && typeof event.usage === "object" ? event.usage : {};
181
+ const inputTokens = number(usage.input);
182
+ const outputTokens = number(usage.output);
183
+ const cacheReadTokens = number(usage.cacheRead);
184
+ const cacheWriteTokens = number(usage.cacheWrite);
185
+ const inclusiveInputTokens = inputTokens === void 0 && cacheReadTokens === void 0 && cacheWriteTokens === void 0 ? void 0 : (inputTokens ?? 0) + (cacheReadTokens ?? 0) + (cacheWriteTokens ?? 0);
186
+ const index = current.spans.filter((span) => span.attributes["gen_ai.operation.name"] === "chat").length;
187
+ current.spans.push(makeSpan(key, `chat:${index}`, `chat ${model}`, input?.at ?? Date.now(), Date.now(), {
188
+ "gen_ai.operation.name": "chat",
189
+ "gen_ai.provider.name": provider,
190
+ "gen_ai.request.model": model,
191
+ "runtype.iteration": index,
192
+ ...inclusiveInputTokens === void 0 ? {} : { "gen_ai.usage.input_tokens": inclusiveInputTokens },
193
+ ...outputTokens === void 0 ? {} : { "gen_ai.usage.output_tokens": outputTokens },
194
+ ...cacheReadTokens === void 0 ? {} : { "gen_ai.usage.cache_read.input_tokens": cacheReadTokens },
195
+ ...cacheWriteTokens === void 0 ? {} : { "gen_ai.usage.cache_creation.input_tokens": cacheWriteTokens },
196
+ ...inputMessages === void 0 ? {} : { "gen_ai.input.messages": inputMessages },
197
+ ...outputMessages === void 0 ? {} : { "gen_ai.output.messages": outputMessages },
198
+ ...system === void 0 ? {} : { "gen_ai.system_instructions": system }
199
+ }, assistantFailed(event.lastAssistant) ? 2 : 1));
200
+ current.firstInput ??= inputMessages;
201
+ current.lastOutput = outputMessages;
202
+ current.lastModel = model;
203
+ current.lastProvider = provider;
204
+ if (inclusiveInputTokens !== void 0 && outputTokens !== void 0) {
205
+ current.inputTokens += inclusiveInputTokens;
206
+ current.outputTokens += outputTokens;
207
+ current.measuredModels += 1;
208
+ }
209
+ if (cacheReadTokens !== void 0) {
210
+ current.cacheReadTokens += cacheReadTokens;
211
+ current.cacheReadModels += 1;
212
+ }
213
+ if (cacheWriteTokens !== void 0) {
214
+ current.cacheWriteTokens += cacheWriteTokens;
215
+ current.cacheWriteModels += 1;
216
+ }
217
+ settlePending(current);
218
+ });
219
+ api.on("before_tool_call", (event, context) => {
220
+ const key = nativeId(event, context);
221
+ const toolKey = string(event.toolCallId) ?? string(event.toolName);
222
+ if (!key || !toolKey || completed.has(key) || finishing.has(key)) return;
223
+ run(key).tools.set(toolKey, {
224
+ at: Date.now(),
225
+ event
226
+ });
227
+ });
228
+ api.on("after_tool_call", (event, context) => {
229
+ const key = nativeId(event, context);
230
+ if (!key || completed.has(key)) return;
231
+ const current = run(key);
232
+ const toolKey = string(event.toolCallId) ?? string(event.toolName);
233
+ const before = toolKey ? current.tools.get(toolKey) : void 0;
234
+ if (toolKey) current.tools.delete(toolKey);
235
+ const name = string(event.toolName) ?? string(before?.event.toolName) ?? "unknown";
236
+ const callId = string(event.toolCallId) ?? string(before?.event.toolCallId);
237
+ const args = capture(event.params, secrets, content);
238
+ const result = capture(event.result, secrets, content);
239
+ const index = current.spans.filter((span) => span.attributes["gen_ai.operation.name"] === "execute_tool").length + 1;
240
+ const now = Date.now();
241
+ current.spans.push(makeSpan(key, `tool:${index}`, `execute_tool ${name}`, before?.at ?? Math.max(1, now - (number(event.durationMs) ?? 0)), now, {
242
+ "gen_ai.operation.name": "execute_tool",
243
+ "gen_ai.tool.name": name,
244
+ ...callId === void 0 ? {} : { "gen_ai.tool.call.id": callId },
245
+ ...args === void 0 ? {} : { "gen_ai.tool.call.arguments": args },
246
+ ...result === void 0 ? {} : { "gen_ai.tool.call.result": result }
247
+ }, string(event.error) === void 0 ? 1 : 2));
248
+ settlePending(current);
249
+ });
250
+ api.on("agent_end", async (event, context) => {
251
+ const key = nativeId(event, context);
252
+ if (!key || completed.has(key) || finishing.has(key)) return;
253
+ finishing.add(key);
254
+ const current = run(key);
255
+ const terminalAt = Date.now();
256
+ if (current.inputs.length > 0 || current.tools.size > 0) await new Promise((resolve) => {
257
+ const timer = setTimeout(() => {
258
+ current.onSettled = void 0;
259
+ resolve();
260
+ }, 1e3);
261
+ current.onSettled = () => {
262
+ clearTimeout(timer);
263
+ current.onSettled = void 0;
264
+ resolve();
265
+ };
266
+ });
267
+ active.delete(key);
268
+ completed.add(key);
269
+ finishing.delete(key);
270
+ if (completed.size > 2048) completed.delete(completed.values().next().value);
271
+ const chatSpans = current.spans.filter((span) => span.attributes["gen_ai.operation.name"] === "chat");
272
+ const usageComplete = current.measuredModels === chatSpans.length && current.modelRefs.size <= 1;
273
+ if (!usageComplete) for (const span of chatSpans) {
274
+ delete span.attributes["gen_ai.usage.input_tokens"];
275
+ delete span.attributes["gen_ai.usage.output_tokens"];
276
+ delete span.attributes["gen_ai.usage.cache_read.input_tokens"];
277
+ delete span.attributes["gen_ai.usage.cache_creation.input_tokens"];
278
+ }
279
+ const now = Math.max(terminalAt, ...current.spans.map((span) => Number(BigInt(span.endTimeUnixNano) / 1000000n)));
280
+ const duration = number(event.durationMs);
281
+ const startedAt = duration === void 0 ? current.startedAt : Math.min(current.startedAt, Math.max(1, terminalAt - duration));
282
+ const success = event.success === true && !finalAssistantFailed(event.messages);
283
+ const finalChat = chatSpans.at(-1);
284
+ if (!success && finalChat?.status?.code === 1 && finalChat.attributes["gen_ai.output.messages"] === void 0 && finalChat.attributes["gen_ai.usage.input_tokens"] === void 0 && finalChat.attributes["gen_ai.usage.output_tokens"] === void 0) finalChat.status = { code: 2 };
285
+ const root = makeSpan(key, "root", "invoke_agent", startedAt, now, {
286
+ "gen_ai.operation.name": "invoke_agent",
287
+ "runtype.stop_reason": success ? "stop" : "error",
288
+ ...current.firstInput === void 0 ? {} : { "gen_ai.input.messages": current.firstInput },
289
+ ...current.lastOutput === void 0 ? {} : { "gen_ai.output.messages": current.lastOutput },
290
+ ...current.lastModel === void 0 ? {} : { "gen_ai.request.model": current.lastModel },
291
+ ...current.lastProvider === void 0 ? {} : { "gen_ai.provider.name": current.lastProvider },
292
+ ...current.measuredModels === 0 || !usageComplete ? {} : {
293
+ "gen_ai.usage.input_tokens": current.inputTokens,
294
+ "gen_ai.usage.output_tokens": current.outputTokens,
295
+ ...current.cacheReadModels === chatSpans.length ? { "gen_ai.usage.cache_read.input_tokens": current.cacheReadTokens } : {},
296
+ ...current.cacheWriteModels === chatSpans.length ? { "gen_ai.usage.cache_creation.input_tokens": current.cacheWriteTokens } : {}
297
+ }
298
+ }, success ? 1 : 2);
299
+ delete root.parentSpanId;
300
+ await deliver(encode([...current.spans, root], config.agentId));
301
+ });
302
+ }
303
+ };
304
+ }
305
+ const environmentPlugin = {
306
+ id: "runtype",
307
+ name: "Runtype Telemetry",
308
+ description: "Exports OpenClaw agent turns to Runtype",
309
+ register(api) {
310
+ const apiKey = process.env.RUNTYPE_API_KEY;
311
+ const agentId = process.env.RUNTYPE_AGENT_ID;
312
+ if (!apiKey || !agentId) {
313
+ api.logger?.warn("Runtype telemetry disabled: RUNTYPE_API_KEY and RUNTYPE_AGENT_ID are required");
314
+ return;
315
+ }
316
+ createRuntypeOpenClawPlugin({
317
+ apiKey,
318
+ agentId,
319
+ otlpUrl: process.env.RUNTYPE_OTLP_URL ?? "https://api.runtype.com/v1/otel/v1/traces",
320
+ captureContent: process.env.RUNTYPE_CAPTURE_CONTENT === "true",
321
+ onDiagnostic: (code) => api.logger?.warn(`Runtype telemetry ${code}`)
322
+ }).register(api);
323
+ }
324
+ };
325
+ //#endregion
326
+ export { createRuntypeOpenClawPlugin, environmentPlugin as default };
327
+
@@ -0,0 +1,7 @@
1
+ {
2
+ "id": "runtype",
3
+ "name": "Runtype Telemetry",
4
+ "description": "Export OpenClaw agent turns to Runtype",
5
+ "activation": { "onStartup": true },
6
+ "configSchema": { "type": "object", "additionalProperties": false }
7
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@runtypelabs/openclaw-adapter",
3
+ "version": "0.1.0",
4
+ "description": "OpenClaw run telemetry for Runtype",
5
+ "type": "module",
6
+ "main": "./dist/index.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.mjs"
11
+ }
12
+ },
13
+ "openclaw": {
14
+ "extensions": [
15
+ "./dist/index.mjs"
16
+ ]
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "openclaw.plugin.json",
21
+ "README.md"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^25.3.3",
28
+ "tsdown": "^0.23.0",
29
+ "typescript": "^6.0.3",
30
+ "vitest": "^5.0.2"
31
+ },
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/runtypelabs/core",
36
+ "directory": "packages/openclaw-adapter"
37
+ },
38
+ "scripts": {
39
+ "build": "tsdown",
40
+ "test": "vitest run",
41
+ "typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.tests.json",
42
+ "lint": "eslint . --max-warnings 0"
43
+ }
44
+ }