kumarabhirup fdd89b4e6f
feat: add PostHog AI observability, feedback UI, and telemetry privacy mode
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.
2026-03-05 12:28:08 -08:00

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.
}
}