From c067b0fc063b4a340a1f7f9bf8a33a81189dde1a Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 16 May 2026 00:49:03 +0900 Subject: [PATCH] refactor(plugin-entry): move createPluginModule to testing module createPluginModule and PluginModuleDeps were exposed at package entry as a test seam. Their export creates accidental public TS API obligations for internal manager/tool/hook constructor types. Move to src/testing/create-plugin-module.ts so only tests reach them. Closes HIGH-8 Co-authored-by: api-surface (deep / gpt-5.3-codex high) --- src/index.telemetry.test.ts | 2 +- src/index.test.ts | 2 +- src/index.ts | 188 +--------------------------- src/testing/create-plugin-module.ts | 178 ++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 185 deletions(-) create mode 100644 src/testing/create-plugin-module.ts diff --git a/src/index.telemetry.test.ts b/src/index.telemetry.test.ts index a10f0f028..5d736b164 100644 --- a/src/index.telemetry.test.ts +++ b/src/index.telemetry.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, mock } from "bun:test" -import { createPluginModule } from "./index" +import { createPluginModule } from "./testing/create-plugin-module" const mockInitConfigContext = mock(() => {}) const mockInjectServerAuthIntoClient = mock(() => {}) diff --git a/src/index.test.ts b/src/index.test.ts index 7b5f4512e..8089321cc 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, mock } from "bun:test" -import { createPluginModule } from "./index" +import { createPluginModule } from "./testing/create-plugin-module" const mockInitConfigContext = mock(() => {}) const mockDetectExternalSkillPlugin = mock(() => ({ detected: false, pluginName: null })) diff --git a/src/index.ts b/src/index.ts index 52478b4ef..76d93212c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,196 +1,18 @@ -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 { - createCompactionAutocontinueHandler, - createSessionCompactingHandler, - type CompactionAutocontinueHook, -} from "./plugin/session-compacting" - -import { loadPluginConfig } from "./plugin-config" -import { createModelCacheState } from "./plugin-state" -import { createFirstMessageVariantGate } from "./shared/first-message-variant" -import { log } from "./shared/logger" -import { logLegacyPluginStartupWarning } from "./shared/log-legacy-plugin-startup-warning" -import { injectServerAuthIntoClient } from "./shared/opencode-server-auth" -import { installAgentSortShim, setAgentSortOrder } from "./shared/agent-sort-shim" -import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector" -import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash" - -type HooksWithCompactionAutocontinue = Hooks & { - "experimental.compaction.autocontinue"?: CompactionAutocontinueHook -} - -type PluginModuleDeps = { - initConfigContext: typeof initConfigContext - installAgentSortShim: typeof installAgentSortShim - setAgentSortOrder: typeof setAgentSortOrder - log: typeof log - logLegacyPluginStartupWarning: typeof logLegacyPluginStartupWarning - detectExternalSkillPlugin: typeof detectExternalSkillPlugin - getSkillPluginConflictWarning: typeof getSkillPluginConflictWarning - injectServerAuthIntoClient: typeof injectServerAuthIntoClient - loadPluginConfig: typeof loadPluginConfig - initializeOpenClaw: typeof initializeOpenClaw - isTmuxIntegrationEnabled: typeof isTmuxIntegrationEnabled - startTmuxCheck: typeof startTmuxCheck - createFirstMessageVariantGate: typeof createFirstMessageVariantGate - createRuntimeTmuxConfig: typeof createRuntimeTmuxConfig - createModelCacheState: typeof createModelCacheState - createManagers: typeof createManagers - createTools: typeof createTools - createHooks: typeof createHooks - createPluginInterface: typeof createPluginInterface -} - -const defaultPluginModuleDeps: PluginModuleDeps = { - initConfigContext, - installAgentSortShim, - setAgentSortOrder, - log, - logLegacyPluginStartupWarning, - detectExternalSkillPlugin, - getSkillPluginConflictWarning, - injectServerAuthIntoClient, - loadPluginConfig, - initializeOpenClaw, - isTmuxIntegrationEnabled, - startTmuxCheck, - createFirstMessageVariantGate, - createRuntimeTmuxConfig, - createModelCacheState, - createManagers, - createTools, - createHooks, - createPluginInterface, -} - -export function createPluginModule(overrides: Partial = {}): PluginModule { - const deps = { ...defaultPluginModuleDeps, ...overrides } - const serverPlugin: Plugin = async (input, _options): Promise => { - deps.installAgentSortShim() - deps.initConfigContext("opencode", null) - deps.log("[oh-my-openagent] ENTRY - plugin loading", { - directory: input.directory, - }) - deps.logLegacyPluginStartupWarning() - - const skillPluginCheck = deps.detectExternalSkillPlugin(input.directory) - if (skillPluginCheck.detected && skillPluginCheck.pluginName) { - console.warn(deps.getSkillPluginConflictWarning(skillPluginCheck.pluginName)) - } - - deps.injectServerAuthIntoClient(input.client) - - const pluginConfig = deps.loadPluginConfig(input.directory, input) - deps.setAgentSortOrder(pluginConfig.agent_order) - - if (pluginConfig.openclaw) { - await deps.initializeOpenClaw(pluginConfig.openclaw) - } - if (pluginConfig.team_mode?.enabled) { - const teamModeConfig = pluginConfig.team_mode - try { - const { ensureBaseDirs, resolveBaseDir } = await import("./features/team-mode/team-registry/paths") - const { checkTeamModeDependencies } = await import("./features/team-mode/deps") - await checkTeamModeDependencies(teamModeConfig) - await ensureBaseDirs(resolveBaseDir(teamModeConfig)) - if (pluginConfig.disabled_skills?.includes("team-mode")) { - console.warn( - "[team-mode] enabled=true but team-mode skill is disabled; skill docs hidden but tools still registered (D-29)", - ) - } - } catch (err) { - console.warn("[team-mode] init failed:", err) - } - } - const tmuxIntegrationEnabled = deps.isTmuxIntegrationEnabled(pluginConfig) - if (tmuxIntegrationEnabled) { - deps.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 = deps.createFirstMessageVariantGate() - - const tmuxConfig = deps.createRuntimeTmuxConfig(pluginConfig) - - const modelCacheState = deps.createModelCacheState() - - const managers = deps.createManagers({ - ctx: input, - pluginConfig, - tmuxConfig, - modelCacheState, - backgroundNotificationHookEnabled: isHookEnabled("background-notification"), - }) - - const toolsResult = await deps.createTools({ - ctx: input, - pluginConfig, - managers, - }) - - const hooks = deps.createHooks({ - ctx: input, - pluginConfig, - modelCacheState, - backgroundManager: managers.backgroundManager, - modelFallbackControllerAccessor: managers.modelFallbackControllerAccessor, - isHookEnabled, - safeHookEnabled, - mergedSkills: toolsResult.mergedSkills, - availableSkills: toolsResult.availableSkills, - }) - - const pluginInterface = deps.createPluginInterface({ - ctx: input, - pluginConfig, - firstMessageVariantGate, - managers, - hooks, - tools: toolsResult.filteredTools, - }) - - const pluginHooks: HooksWithCompactionAutocontinue = { - ...pluginInterface, - - "experimental.session.compacting": createSessionCompactingHandler(hooks), - - "experimental.compaction.autocontinue": createCompactionAutocontinueHandler(hooks), - } - - return pluginHooks - } - - return { - id: "oh-my-openagent", - server: serverPlugin, - } -} +import type { PluginModule } from "@opencode-ai/plugin" +import { createPluginModule } from "./testing/create-plugin-module" const pluginModule: PluginModule = createPluginModule() export default pluginModule export type { - OhMyOpenCodeConfig, AgentName, AgentOverrideConfig, AgentOverrides, - McpName, - HookName, BuiltinCommandName, + HookName, + McpName, + OhMyOpenCodeConfig, } from "./config" export type { ConfigLoadError } from "./shared/config-errors" diff --git a/src/testing/create-plugin-module.ts b/src/testing/create-plugin-module.ts new file mode 100644 index 000000000..36029d2fa --- /dev/null +++ b/src/testing/create-plugin-module.ts @@ -0,0 +1,178 @@ +import type { Hooks, Plugin, PluginModule } from "@opencode-ai/plugin" +import type { HookName } from "../config" +import { initConfigContext } from "../cli/config-manager/config-context" + +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 { + createCompactionAutocontinueHandler, + createSessionCompactingHandler, + type CompactionAutocontinueHook, +} from "../plugin/session-compacting" +import { installAgentSortShim, setAgentSortOrder } from "../shared/agent-sort-shim" +import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "../shared/external-plugin-detector" +import { createFirstMessageVariantGate } from "../shared/first-message-variant" +import { log } from "../shared/logger" +import { logLegacyPluginStartupWarning } from "../shared/log-legacy-plugin-startup-warning" +import { injectServerAuthIntoClient } from "../shared/opencode-server-auth" +import { startBackgroundCheck as startTmuxCheck } from "../tools/interactive-bash" + +type HooksWithCompactionAutocontinue = Hooks & { + "experimental.compaction.autocontinue"?: CompactionAutocontinueHook +} + +export type PluginModuleDeps = { + initConfigContext: typeof initConfigContext + installAgentSortShim: typeof installAgentSortShim + setAgentSortOrder: typeof setAgentSortOrder + log: typeof log + logLegacyPluginStartupWarning: typeof logLegacyPluginStartupWarning + detectExternalSkillPlugin: typeof detectExternalSkillPlugin + getSkillPluginConflictWarning: typeof getSkillPluginConflictWarning + injectServerAuthIntoClient: typeof injectServerAuthIntoClient + loadPluginConfig: typeof loadPluginConfig + initializeOpenClaw: typeof initializeOpenClaw + isTmuxIntegrationEnabled: typeof isTmuxIntegrationEnabled + startTmuxCheck: typeof startTmuxCheck + createFirstMessageVariantGate: typeof createFirstMessageVariantGate + createRuntimeTmuxConfig: typeof createRuntimeTmuxConfig + createModelCacheState: typeof createModelCacheState + createManagers: typeof createManagers + createTools: typeof createTools + createHooks: typeof createHooks + createPluginInterface: typeof createPluginInterface +} + +const defaultPluginModuleDeps: PluginModuleDeps = { + initConfigContext, + installAgentSortShim, + setAgentSortOrder, + log, + logLegacyPluginStartupWarning, + detectExternalSkillPlugin, + getSkillPluginConflictWarning, + injectServerAuthIntoClient, + loadPluginConfig, + initializeOpenClaw, + isTmuxIntegrationEnabled, + startTmuxCheck, + createFirstMessageVariantGate, + createRuntimeTmuxConfig, + createModelCacheState, + createManagers, + createTools, + createHooks, + createPluginInterface, +} + +export function createPluginModule(overrides: Partial = {}): PluginModule { + const deps = { ...defaultPluginModuleDeps, ...overrides } + const serverPlugin: Plugin = async (input, _options): Promise => { + deps.installAgentSortShim() + deps.initConfigContext("opencode", null) + deps.log("[oh-my-openagent] ENTRY - plugin loading", { + directory: input.directory, + }) + deps.logLegacyPluginStartupWarning() + + const skillPluginCheck = deps.detectExternalSkillPlugin(input.directory) + if (skillPluginCheck.detected && skillPluginCheck.pluginName) { + console.warn(deps.getSkillPluginConflictWarning(skillPluginCheck.pluginName)) + } + + deps.injectServerAuthIntoClient(input.client) + + const pluginConfig = deps.loadPluginConfig(input.directory, input) + deps.setAgentSortOrder(pluginConfig.agent_order) + + if (pluginConfig.openclaw) { + await deps.initializeOpenClaw(pluginConfig.openclaw) + } + if (pluginConfig.team_mode?.enabled) { + const teamModeConfig = pluginConfig.team_mode + try { + const { ensureBaseDirs, resolveBaseDir } = await import("../features/team-mode/team-registry/paths") + const { checkTeamModeDependencies } = await import("../features/team-mode/deps") + await checkTeamModeDependencies(teamModeConfig) + await ensureBaseDirs(resolveBaseDir(teamModeConfig)) + if (pluginConfig.disabled_skills?.includes("team-mode")) { + console.warn( + "[team-mode] enabled=true but team-mode skill is disabled; skill docs hidden but tools still registered (D-29)", + ) + } + } catch (err) { + console.warn("[team-mode] init failed:", err) + } + } + const tmuxIntegrationEnabled = deps.isTmuxIntegrationEnabled(pluginConfig) + if (tmuxIntegrationEnabled) { + deps.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 = deps.createFirstMessageVariantGate() + + const tmuxConfig = deps.createRuntimeTmuxConfig(pluginConfig) + + const modelCacheState = deps.createModelCacheState() + + const managers = deps.createManagers({ + ctx: input, + pluginConfig, + tmuxConfig, + modelCacheState, + backgroundNotificationHookEnabled: isHookEnabled("background-notification"), + }) + + const toolsResult = await deps.createTools({ + ctx: input, + pluginConfig, + managers, + }) + + const hooks = deps.createHooks({ + ctx: input, + pluginConfig, + modelCacheState, + backgroundManager: managers.backgroundManager, + modelFallbackControllerAccessor: managers.modelFallbackControllerAccessor, + isHookEnabled, + safeHookEnabled, + mergedSkills: toolsResult.mergedSkills, + availableSkills: toolsResult.availableSkills, + }) + + const pluginInterface = deps.createPluginInterface({ + ctx: input, + pluginConfig, + firstMessageVariantGate, + managers, + hooks, + tools: toolsResult.filteredTools, + }) + + const pluginHooks: HooksWithCompactionAutocontinue = { + ...pluginInterface, + + "experimental.session.compacting": createSessionCompactingHandler(hooks), + + "experimental.compaction.autocontinue": createCompactionAutocontinueHandler(hooks), + } + + return pluginHooks + } + + return { + id: "oh-my-openagent", + server: serverPlugin, + } +}