👌 IMPROVE: feat(telemetry): add DenchClaw and OpenClaw version tracking

Integrate version tracking for DenchClaw and OpenClaw into the telemetry system. The versions are now read from the package.json and environment variables, and are included in the PostHog client initialization and telemetry events. This enhancement allows for better monitoring and analytics of the versions in use.
This commit is contained in:
kumarabhirup 2026-03-15 04:33:44 -07:00
parent 1c92aaf5d7
commit aa97489785
No known key found for this signature in database
GPG Key ID: DB7CA2289CAB0167
10 changed files with 118 additions and 10 deletions

View File

@ -7,6 +7,8 @@ import { usePathname, useSearchParams } from "next/navigation";
const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY || "";
const POSTHOG_HOST = "https://us.i.posthog.com";
const DENCHCLAW_VERSION = process.env.NEXT_PUBLIC_DENCHCLAW_VERSION || "";
const OPENCLAW_VERSION = process.env.NEXT_PUBLIC_OPENCLAW_VERSION || "";
let initialized = false;
@ -25,6 +27,12 @@ function initPostHog(anonymousId?: string) {
? { distinctID: anonymousId, isIdentifiedID: false }
: undefined,
});
const superProps: Record<string, string> = {};
if (DENCHCLAW_VERSION) superProps.denchclaw_version = DENCHCLAW_VERSION;
if (OPENCLAW_VERSION) superProps.openclaw_version = OPENCLAW_VERSION;
if (Object.keys(superProps).length > 0) posthog.register(superProps);
initialized = true;
}

View File

@ -6,6 +6,8 @@ import { PostHog } from "posthog-node";
const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY || "";
const POSTHOG_HOST = "https://us.i.posthog.com";
const DENCHCLAW_VERSION = process.env.NEXT_PUBLIC_DENCHCLAW_VERSION || "";
const OPENCLAW_VERSION = process.env.NEXT_PUBLIC_OPENCLAW_VERSION || "";
let client: PostHog | null = null;
@ -68,6 +70,8 @@ export function trackServer(
event,
properties: {
...properties,
denchclaw_version: DENCHCLAW_VERSION || undefined,
openclaw_version: OPENCLAW_VERSION || undefined,
$process_person_profile: false,
},
});

View File

@ -1,8 +1,26 @@
import type { NextConfig } from "next";
import { readFileSync } from "node:fs";
import path from "node:path";
import { homedir } from "node:os";
import { createRequire } from "node:module";
const rootPkg = JSON.parse(
readFileSync(path.join(import.meta.dirname, "..", "..", "package.json"), "utf-8"),
) as { version?: string };
let openclawVersion = "";
try {
const req = createRequire(import.meta.url);
const oclPkg = req("openclaw/package.json") as { version?: string };
openclawVersion = oclPkg.version ?? "";
} catch { /* openclaw not resolvable at build time */ }
const nextConfig: NextConfig = {
env: {
NEXT_PUBLIC_DENCHCLAW_VERSION: rootPkg.version ?? "",
NEXT_PUBLIC_OPENCLAW_VERSION: openclawVersion,
},
// Produce a self-contained standalone build so npm global installs
// can run the web app with `node server.js` — no npm install or
// next build required at runtime.

View File

@ -2,7 +2,11 @@ import { createPostHogClient, shutdownPostHogClient } from "./lib/posthog-client
import { TraceContextManager, resolveSessionKey } from "./lib/trace-context.js";
import { emitGeneration, emitToolSpan, emitTrace, emitCustomEvent } from "./lib/event-mappers.js";
import { readPrivacyMode } from "./lib/privacy.js";
import { POSTHOG_KEY as BUILT_IN_KEY } from "./lib/build-env.js";
import {
POSTHOG_KEY as BUILT_IN_KEY,
DENCHCLAW_VERSION,
OPENCLAW_VERSION,
} from "./lib/build-env.js";
import type { PluginConfig } from "./lib/types.js";
export const id = "posthog-analytics";
@ -29,7 +33,13 @@ export default function register(api: any) {
return;
}
const ph = createPostHogClient(apiKey, config?.host);
const versionProps: Record<string, unknown> = {};
const dcv = DENCHCLAW_VERSION || process.env.npm_package_version;
if (dcv) versionProps.denchclaw_version = dcv;
const ocv = OPENCLAW_VERSION || process.env.OPENCLAW_VERSION || process.env.OPENCLAW_SERVICE_VERSION;
if (ocv) versionProps.openclaw_version = ocv;
const ph = createPostHogClient(apiKey, config?.host, versionProps);
const traceCtx = new TraceContextManager();
const getPrivacyMode = () => readPrivacyMode(api.config);

View File

@ -1 +1,3 @@
export const POSTHOG_KEY = "";
export const DENCHCLAW_VERSION = "";
export const OPENCLAW_VERSION = "";

View File

@ -15,12 +15,14 @@ export interface CaptureEvent {
export class PostHogClient {
private apiKey: string;
private host: string;
private globalProperties: Record<string, unknown>;
private queue: Array<Record<string, unknown>> = [];
private timer: ReturnType<typeof setInterval> | null = null;
constructor(apiKey: string, host?: string) {
constructor(apiKey: string, host?: string, globalProperties?: Record<string, unknown>) {
this.apiKey = apiKey;
this.host = (host || DEFAULT_HOST).replace(/\/$/, "");
this.globalProperties = globalProperties ?? {};
this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
if (this.timer.unref) this.timer.unref();
}
@ -30,6 +32,7 @@ export class PostHogClient {
event: event.event,
distinct_id: event.distinctId,
properties: {
...this.globalProperties,
...event.properties,
$lib: "denchclaw-posthog-plugin",
},
@ -68,8 +71,12 @@ export class PostHogClient {
}
}
export function createPostHogClient(apiKey: string, host?: string): PostHogClient {
return new PostHogClient(apiKey, host);
export function createPostHogClient(
apiKey: string,
host?: string,
globalProperties?: Record<string, unknown>,
): PostHogClient {
return new PostHogClient(apiKey, host, globalProperties);
}
export async function shutdownPostHogClient(client: PostHogClient): Promise<void> {

View File

@ -1,7 +1,24 @@
import { writeFileSync } from "node:fs";
import { readFileSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
const key = process.env.POSTHOG_KEY || "";
const rootPkg = JSON.parse(readFileSync("package.json", "utf-8"));
const denchclawVersion = rootPkg.version || "";
let openclawVersion = "";
try {
const req = createRequire(import.meta.url);
const oclPkg = req("openclaw/package.json");
openclawVersion = oclPkg.version || "";
} catch { /* openclaw not resolvable at build time */ }
writeFileSync(
"extensions/posthog-analytics/lib/build-env.js",
`export const POSTHOG_KEY = ${JSON.stringify(key)};\n`,
[
`export const POSTHOG_KEY = ${JSON.stringify(key)};`,
`export const DENCHCLAW_VERSION = ${JSON.stringify(denchclawVersion)};`,
`export const OPENCLAW_VERSION = ${JSON.stringify(openclawVersion)};`,
"",
].join("\n"),
);

View File

@ -44,6 +44,8 @@ describe("posthog-analytics plugin key fallback", () => {
it("uses api.config.apiKey when provided", async () => {
vi.doMock("../../extensions/posthog-analytics/lib/build-env.js", () => ({
POSTHOG_KEY: "built-in-key",
DENCHCLAW_VERSION: "",
OPENCLAW_VERSION: "",
}));
const { default: register } = await import(
@ -52,12 +54,18 @@ describe("posthog-analytics plugin key fallback", () => {
const api = createMockApi({ apiKey: "config-key", enabled: true });
register(api);
expect(mockCreatePostHogClient).toHaveBeenCalledWith("config-key", undefined);
expect(mockCreatePostHogClient).toHaveBeenCalledWith(
"config-key",
undefined,
expect.any(Object),
);
});
it("falls back to built-in key when api.config has no apiKey", async () => {
vi.doMock("../../extensions/posthog-analytics/lib/build-env.js", () => ({
POSTHOG_KEY: "built-in-key",
DENCHCLAW_VERSION: "",
OPENCLAW_VERSION: "",
}));
const { default: register } = await import(
@ -66,12 +74,18 @@ describe("posthog-analytics plugin key fallback", () => {
const api = createMockApi();
register(api);
expect(mockCreatePostHogClient).toHaveBeenCalledWith("built-in-key", undefined);
expect(mockCreatePostHogClient).toHaveBeenCalledWith(
"built-in-key",
undefined,
expect.any(Object),
);
});
it("does not initialize when neither config nor built-in key is available", async () => {
vi.doMock("../../extensions/posthog-analytics/lib/build-env.js", () => ({
POSTHOG_KEY: "",
DENCHCLAW_VERSION: "",
OPENCLAW_VERSION: "",
}));
const { default: register } = await import(
@ -87,6 +101,8 @@ describe("posthog-analytics plugin key fallback", () => {
it("registers lifecycle hooks when built-in key is used", async () => {
vi.doMock("../../extensions/posthog-analytics/lib/build-env.js", () => ({
POSTHOG_KEY: "built-in-key",
DENCHCLAW_VERSION: "",
OPENCLAW_VERSION: "",
}));
const { default: register } = await import(

View File

@ -1,5 +1,6 @@
import { PostHog } from "posthog-node";
import { readTelemetryConfig, getOrCreateAnonymousId } from "./config.js";
import { VERSION, resolveOpenClawVersion } from "../version.js";
const POSTHOG_KEY = process.env.POSTHOG_KEY || "";
const POSTHOG_HOST = "https://us.i.posthog.com";
@ -27,6 +28,8 @@ function getMachineContext(): Record<string, unknown> {
os: process.platform,
arch: process.arch,
node_version: process.version,
denchclaw_version: VERSION,
openclaw_version: resolveOpenClawVersion(),
};
}

View File

@ -88,7 +88,7 @@ export function resolveRuntimeServiceVersion(
);
}
// Single source of truth for the current OpenClaw version.
// Single source of truth for the current DenchClaw version.
// - Embedded/bundled builds: injected define or env var.
// - Dev/npm builds: package.json.
export const VERSION =
@ -96,3 +96,26 @@ export const VERSION =
process.env.OPENCLAW_BUNDLED_VERSION ||
resolveVersionFromModuleUrl(import.meta.url) ||
"0.0.0";
let _cachedOpenClawVersion: string | undefined;
export function resolveOpenClawVersion(): string | undefined {
if (_cachedOpenClawVersion !== undefined) return _cachedOpenClawVersion || undefined;
const envVersion = (process.env.OPENCLAW_VERSION || process.env.OPENCLAW_SERVICE_VERSION)?.trim();
if (envVersion) {
_cachedOpenClawVersion = envVersion;
return envVersion;
}
try {
const req = createRequire(import.meta.url);
const pkg = req("openclaw/package.json") as { version?: string };
const v = pkg.version?.trim();
_cachedOpenClawVersion = v || "";
return v || undefined;
} catch {
_cachedOpenClawVersion = "";
return undefined;
}
}