Integrate PostHog LLM Analytics via a bundled OpenClaw plugin that captures $ai_generation, $ai_span, and $ai_trace events with configurable privacy mode (content redaction on by default). Add like/dislike feedback buttons to the web chat UI backed by a /api/feedback route. Extend the CLI with `telemetry privacy on|off` subcommands and fix command delegation so telemetry subcommands aren't forwarded to OpenClaw. Harden the web runtime installer to auto-flatten pnpm standalone deps and dereference dangling symlinks, preventing "Cannot find module 'next'" crashes in dev. Move plugin installation before onboard in bootstrap so the gateway starts with plugins.allow already configured.
82 lines
1.9 KiB
TypeScript
82 lines
1.9 KiB
TypeScript
const DEFAULT_HOST = "https://us.i.posthog.com";
|
|
const FLUSH_INTERVAL_MS = 15_000;
|
|
const FLUSH_AT = 10;
|
|
|
|
export interface CaptureEvent {
|
|
distinctId: string;
|
|
event: string;
|
|
properties?: Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Minimal PostHog client using the HTTP capture API directly.
|
|
* Zero npm dependencies -- uses built-in fetch (Node 18+).
|
|
*/
|
|
export class PostHogClient {
|
|
private apiKey: string;
|
|
private host: string;
|
|
private queue: Array<Record<string, unknown>> = [];
|
|
private timer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
constructor(apiKey: string, host?: string) {
|
|
this.apiKey = apiKey;
|
|
this.host = (host || DEFAULT_HOST).replace(/\/$/, "");
|
|
this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
|
|
if (this.timer.unref) this.timer.unref();
|
|
}
|
|
|
|
capture(event: CaptureEvent): void {
|
|
this.queue.push({
|
|
event: event.event,
|
|
distinct_id: event.distinctId,
|
|
properties: {
|
|
...event.properties,
|
|
$lib: "denchclaw-posthog-plugin",
|
|
},
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
|
|
if (this.queue.length >= FLUSH_AT) {
|
|
this.flush();
|
|
}
|
|
}
|
|
|
|
flush(): void {
|
|
if (this.queue.length === 0) return;
|
|
|
|
const batch = this.queue.splice(0);
|
|
const body = JSON.stringify({
|
|
api_key: this.apiKey,
|
|
batch,
|
|
});
|
|
|
|
fetch(`${this.host}/batch/`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body,
|
|
}).catch(() => {
|
|
// Fail silently -- telemetry should never block the gateway.
|
|
});
|
|
}
|
|
|
|
async shutdown(): Promise<void> {
|
|
if (this.timer) {
|
|
clearInterval(this.timer);
|
|
this.timer = null;
|
|
}
|
|
this.flush();
|
|
}
|
|
}
|
|
|
|
export function createPostHogClient(apiKey: string, host?: string): PostHogClient {
|
|
return new PostHogClient(apiKey, host);
|
|
}
|
|
|
|
export async function shutdownPostHogClient(client: PostHogClient): Promise<void> {
|
|
try {
|
|
await client.shutdown();
|
|
} catch {
|
|
// Non-fatal.
|
|
}
|
|
}
|