Files
oh-my-opencode/src/index.ts
T
YeonGyu-Kim 669e0667be fix(telemetry): dedupe plugin_loaded event to once per UTC day
The plugin_loaded event was emitted on every plugin reload, generating
high event volume on PostHog (proportional to opencode restarts per
user per day). With MAU > 60K and active power users restarting
frequently, this drove unnecessary event spend.

Add a separate daily dedup state field (lastPluginLoadedDayUTC) so the
plugin_loaded capture only fires once per UTC day per machine. The
existing daily activity dedup (lastActiveDayUTC, used by
omo_daily_active) is preserved as an independent gate so the two
dimensions cannot overwrite each other in the activity state file.
2026-04-28 15:01:40 +09:00

159 lines
5.0 KiB
TypeScript

import { initConfigContext } from "./cli/config-manager/config-context"
import type { Hooks, Plugin, PluginModule } from "@opencode-ai/plugin"
import type { HookName } from "./config"
import { createHooks } from "./create-hooks"
import { createManagers } from "./create-managers"
import { createRuntimeTmuxConfig, isTmuxIntegrationEnabled } from "./create-runtime-tmux-config"
import { createTools } from "./create-tools"
import { initializeOpenClaw } from "./openclaw"
import { createPluginInterface } from "./plugin-interface"
import { loadPluginConfig } from "./plugin-config"
import { createModelCacheState } from "./plugin-state"
import { createFirstMessageVariantGate } from "./shared/first-message-variant"
import { injectServerAuthIntoClient, log, logLegacyPluginStartupWarning } from "./shared"
import { installAgentSortShim } from "./shared/agent-sort-shim"
import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector"
import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
import { createPluginPostHog, getPostHogDistinctId } from "./shared/posthog"
import { getPluginLoadedCaptureState } from "./shared/posthog-activity-state"
const serverPlugin: Plugin = async (input, _options): Promise<Hooks> => {
installAgentSortShim()
initConfigContext("opencode", null)
log("[oh-my-openagent] ENTRY - plugin loading", {
directory: input.directory,
})
logLegacyPluginStartupWarning()
const skillPluginCheck = detectExternalSkillPlugin(input.directory)
if (skillPluginCheck.detected && skillPluginCheck.pluginName) {
console.warn(getSkillPluginConflictWarning(skillPluginCheck.pluginName))
}
injectServerAuthIntoClient(input.client)
const pluginConfig = loadPluginConfig(input.directory, input)
const posthog = createPluginPostHog()
const distinctId = getPostHogDistinctId()
try {
posthog.trackActive(distinctId, "plugin_loaded")
} catch {
// telemetry failure is non-fatal, silently ignore
}
let pluginLoadedCaptureState: ReturnType<typeof getPluginLoadedCaptureState> | null = null
try {
pluginLoadedCaptureState = getPluginLoadedCaptureState()
} catch {
// telemetry failure is non-fatal, silently ignore
}
if (pluginLoadedCaptureState?.capturePluginLoaded) {
try {
posthog.capture({
distinctId,
event: "plugin_loaded",
properties: {
entry_point: "plugin",
has_openclaw: !!pluginConfig.openclaw,
tmux_enabled: isTmuxIntegrationEnabled(pluginConfig),
},
})
} catch {
// telemetry failure is non-fatal, silently ignore
}
}
if (pluginConfig.openclaw) {
await initializeOpenClaw(pluginConfig.openclaw)
}
const tmuxIntegrationEnabled = isTmuxIntegrationEnabled(pluginConfig)
if (tmuxIntegrationEnabled) {
startTmuxCheck()
}
const disabledHooks = new Set(pluginConfig.disabled_hooks ?? [])
const isHookEnabled = (hookName: HookName): boolean => !disabledHooks.has(hookName)
const safeHookEnabled = pluginConfig.experimental?.safe_hook_creation ?? true
const firstMessageVariantGate = createFirstMessageVariantGate()
const tmuxConfig = createRuntimeTmuxConfig(pluginConfig)
const modelCacheState = createModelCacheState()
const managers = createManagers({
ctx: input,
pluginConfig,
tmuxConfig,
modelCacheState,
backgroundNotificationHookEnabled: isHookEnabled("background-notification"),
})
const toolsResult = await createTools({
ctx: input,
pluginConfig,
managers,
})
const hooks = createHooks({
ctx: input,
pluginConfig,
modelCacheState,
backgroundManager: managers.backgroundManager,
modelFallbackControllerAccessor: managers.modelFallbackControllerAccessor,
isHookEnabled,
safeHookEnabled,
mergedSkills: toolsResult.mergedSkills,
availableSkills: toolsResult.availableSkills,
})
const pluginInterface = createPluginInterface({
ctx: input,
pluginConfig,
firstMessageVariantGate,
managers,
hooks,
tools: toolsResult.filteredTools,
})
return {
...pluginInterface,
"experimental.session.compacting": async (
compactingInput: { sessionID: string },
output: { context: string[] },
): Promise<void> => {
await hooks.compactionContextInjector?.capture(compactingInput.sessionID)
await hooks.compactionTodoPreserver?.capture(compactingInput.sessionID)
await hooks.claudeCodeHooks?.["experimental.session.compacting"]?.(
compactingInput,
output,
)
if (hooks.compactionContextInjector) {
output.context.push(hooks.compactionContextInjector.inject(compactingInput.sessionID))
}
},
}
}
const pluginModule: PluginModule = {
id: "oh-my-openagent",
server: serverPlugin,
}
export default pluginModule
export type {
OhMyOpenCodeConfig,
AgentName,
AgentOverrideConfig,
AgentOverrides,
McpName,
HookName,
BuiltinCommandName,
} from "./config"
export type { ConfigLoadError } from "./shared/config-errors"