feat(omo-codex): wire Codex SessionStart telemetry for DAU/WAU/MAU

Adds a new Codex plugin component `telemetry` that emits a single
omo_codex_daily_active event (reason: session_start) from every Codex
SessionStart hook, with the same UTC-day deduplication, hashed
installation identifier, and four-flag opt-out as the install-time
event. Previously omo-codex telemetry only fired on install_completed,
so DAU/WAU/MAU under-reported real Codex usage.

- New plugin component packages/omo-codex/plugin/components/telemetry/
  mirrors the rules/comment-checker/lsp pattern: own src/, tsc build,
  vitest tests, package.json (posthog-node dep), hooks/hooks.json.
- src/codex-hook.ts wraps createPluginPostHog().trackActive(..., "session_start")
  with safeShutdown so Codex session startup never blocks on telemetry.
- Plugin root hooks.json + workspaces register the new component
  alongside rules and ultrawork on SessionStart.
- Aggregate test expectations updated to include the telemetry directory.
- cross-package-equivalence.test.ts pins product-identity constants and
  shouldDisablePostHog behavior to stay byte-equivalent between the CLI
  installer (src/telemetry/) and the plugin runtime
  (plugin/components/telemetry/src/), so the two PostHog sources never
  drift on event name, distinct_id base, dedup file path, or opt-out
  flags.
- PostHogActivityReason union in the CLI-side posthog.ts gains
  "session_start" so future CLI paths can emit the same reason without
  a type break.
This commit is contained in:
YeonGyu-Kim
2026-05-26 15:30:41 +09:00
parent b886ca9ca4
commit bd867019f9
22 changed files with 1128 additions and 3 deletions
@@ -0,0 +1,69 @@
import { describe, expect, it } from "bun:test"
import * as cliIdentity from "./product-identity"
describe("cross-package telemetry identity equivalence", () => {
describe("#given the omo-codex CLI telemetry product-identity module and the Codex plugin component product-identity module", () => {
it("#when both are imported #then PRODUCT_NAME, PACKAGE_NAME, CACHE_DIR_NAME, EVENT_NAME, DEFAULT_POSTHOG_HOST, and DEFAULT_POSTHOG_API_KEY are identical", async () => {
const pluginIdentity = await import(
"../../plugin/components/telemetry/src/product-identity"
)
expect(pluginIdentity.PRODUCT_NAME).toBe(cliIdentity.PRODUCT_NAME)
expect(pluginIdentity.PACKAGE_NAME).toBe(cliIdentity.PACKAGE_NAME)
expect(pluginIdentity.CACHE_DIR_NAME).toBe(cliIdentity.CACHE_DIR_NAME)
expect(pluginIdentity.EVENT_NAME).toBe(cliIdentity.EVENT_NAME)
expect(pluginIdentity.DEFAULT_POSTHOG_HOST).toBe(cliIdentity.DEFAULT_POSTHOG_HOST)
expect(pluginIdentity.DEFAULT_POSTHOG_API_KEY).toBe(cliIdentity.DEFAULT_POSTHOG_API_KEY)
expect(pluginIdentity.LEGACY_PARENT_PACKAGE).toBe(cliIdentity.LEGACY_PARENT_PACKAGE)
})
})
describe("#given the omo-codex CLI env-flags module and the Codex plugin component env-flags module", () => {
it("#when shouldDisablePostHog is checked under each opt-out env var #then both modules disable on the same flags", async () => {
const cliEnv = await import("./env-flags")
const pluginEnv = await import(
"../../plugin/components/telemetry/src/env-flags"
)
const flags = [
"OMO_DISABLE_POSTHOG",
"OMO_SEND_ANONYMOUS_TELEMETRY",
"OMO_CODEX_DISABLE_POSTHOG",
"OMO_CODEX_SEND_ANONYMOUS_TELEMETRY",
] as const
const previousValues = new Map<string, string | undefined>()
for (const flag of flags) {
previousValues.set(flag, process.env[flag])
delete process.env[flag]
}
try {
expect(cliEnv.shouldDisablePostHog()).toBe(false)
expect(pluginEnv.shouldDisablePostHog()).toBe(false)
for (const optOutFlag of ["OMO_DISABLE_POSTHOG", "OMO_CODEX_DISABLE_POSTHOG"] as const) {
process.env[optOutFlag] = "1"
expect(cliEnv.shouldDisablePostHog()).toBe(true)
expect(pluginEnv.shouldDisablePostHog()).toBe(true)
delete process.env[optOutFlag]
}
for (const sendFlag of ["OMO_SEND_ANONYMOUS_TELEMETRY", "OMO_CODEX_SEND_ANONYMOUS_TELEMETRY"] as const) {
process.env[sendFlag] = "0"
expect(cliEnv.shouldDisablePostHog()).toBe(true)
expect(pluginEnv.shouldDisablePostHog()).toBe(true)
delete process.env[sendFlag]
}
} finally {
for (const flag of flags) {
const previous = previousValues.get(flag)
if (previous === undefined) {
delete process.env[flag]
} else {
process.env[flag] = previous
}
}
}
})
})
})
+1 -1
View File
@@ -17,7 +17,7 @@ import {
export { DEFAULT_POSTHOG_API_KEY, DEFAULT_POSTHOG_HOST }
export type PostHogSource = "cli" | "plugin" | "install"
export type PostHogActivityReason = "install_started" | "install_completed" | "cli_run"
export type PostHogActivityReason = "install_started" | "install_completed" | "cli_run" | "session_start"
export type PostHogClient = {
trackActive: (distinctId: string, reason: PostHogActivityReason) => void