refactor(plugin): migrate to V1 PluginModule format
Convert the default export from the legacy callable Plugin to the V1 PluginModule shape (`{ id, server }`) documented by opencode's plugin SDK. This aligns oh-my-openagent with the canonical plugin entry format and removes plugin-format legacy debt.
Drop the module-level `let activePluginDispose` cleanup guard: opencode instantiates plugins in a scope-bound Layer per server and dynamic-imports fresh modules on reload, so module-level state is not preserved across reloads. Individual managers already register their own SIGINT/SIGTERM cleanup (skill-mcp-manager, background-agent process-cleanup), so the orphaned createPluginDispose call provided no runtime value.
Remove the non-standard `name` return field that opencode's Hooks interface does not include. The PluginModule's `id` now carries the plugin identity instead.
Drop the unused `lspManager` import that only fed the orphaned createPluginDispose.
Update src/index.test.ts and src/index.telemetry.test.ts to call `plugin.server(ctx)` instead of `plugin(ctx)`, and assert the V1 shape.
This commit is contained in:
@@ -125,12 +125,13 @@ describe("OhMyOpenCodePlugin telemetry isolation", () => {
|
||||
const { default: plugin } = await import(`./index?telemetry=${Date.now()}-${Math.random()}`)
|
||||
|
||||
// when
|
||||
const result = await plugin({
|
||||
const result = await plugin.server({
|
||||
directory: "/tmp/project",
|
||||
client: {},
|
||||
} as Parameters<typeof plugin>[0])
|
||||
} as Parameters<typeof plugin.server>[0])
|
||||
|
||||
// then
|
||||
expect(result).toMatchObject({ name: "oh-my-openagent" })
|
||||
expect(typeof result).toBe("object")
|
||||
expect(result).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
+15
-6
@@ -257,7 +257,7 @@ const mockCreatePluginInterface = mock(() => ({}))
|
||||
const mockInitializeOpenClaw = mock(async () => {})
|
||||
const mockStartTmuxCheck = mock(() => {})
|
||||
|
||||
let OhMyOpenCodePlugin: (typeof import("./index"))["default"]
|
||||
let pluginModule: (typeof import("./index"))["default"]
|
||||
|
||||
function installIndexModuleMocks(): void {
|
||||
mock.module("./cli/config-manager/config-context", () => ({
|
||||
@@ -337,7 +337,7 @@ describe("OhMyOpenCodePlugin", () => {
|
||||
beforeEach(async () => {
|
||||
mock.restore()
|
||||
installIndexModuleMocks()
|
||||
;({ default: OhMyOpenCodePlugin } = await importFreshIndexModule())
|
||||
;({ default: pluginModule } = await importFreshIndexModule())
|
||||
mockInitConfigContext.mockClear()
|
||||
mockDetectExternalSkillPlugin.mockClear()
|
||||
mockGetSkillPluginConflictWarning.mockClear()
|
||||
@@ -375,10 +375,10 @@ describe("OhMyOpenCodePlugin", () => {
|
||||
})
|
||||
|
||||
// when
|
||||
await OhMyOpenCodePlugin({
|
||||
await pluginModule.server({
|
||||
directory: "/tmp/project",
|
||||
client: {},
|
||||
} as Parameters<typeof OhMyOpenCodePlugin>[0])
|
||||
} as Parameters<typeof pluginModule.server>[0])
|
||||
|
||||
// then
|
||||
expect(mockInitializeOpenClaw).toHaveBeenCalledTimes(1)
|
||||
@@ -390,12 +390,21 @@ describe("OhMyOpenCodePlugin", () => {
|
||||
mockLoadPluginConfig.mockReturnValue({})
|
||||
|
||||
// when
|
||||
await OhMyOpenCodePlugin({
|
||||
await pluginModule.server({
|
||||
directory: "/tmp/project",
|
||||
client: {},
|
||||
} as Parameters<typeof OhMyOpenCodePlugin>[0])
|
||||
} as Parameters<typeof pluginModule.server>[0])
|
||||
|
||||
// then
|
||||
expect(mockInitializeOpenClaw).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("exports a V1 PluginModule shape with id and server", () => {
|
||||
// given the plugin module is loaded
|
||||
// when inspecting the default export
|
||||
// then it has the expected V1 shape
|
||||
expect(typeof pluginModule).toBe("object")
|
||||
expect(pluginModule.id).toBe("oh-my-openagent")
|
||||
expect(typeof pluginModule.server).toBe("function")
|
||||
})
|
||||
})
|
||||
|
||||
+21
-31
@@ -1,5 +1,5 @@
|
||||
import { initConfigContext } from "./cli/config-manager/config-context"
|
||||
import type { Plugin } from "@opencode-ai/plugin"
|
||||
import type { Hooks, Plugin, PluginModule } from "@opencode-ai/plugin"
|
||||
|
||||
import type { HookName } from "./config"
|
||||
|
||||
@@ -9,7 +9,6 @@ import { createRuntimeTmuxConfig, isTmuxIntegrationEnabled } from "./create-runt
|
||||
import { createTools } from "./create-tools"
|
||||
import { initializeOpenClaw } from "./openclaw"
|
||||
import { createPluginInterface } from "./plugin-interface"
|
||||
import { createPluginDispose, type PluginDispose } from "./plugin-dispose"
|
||||
|
||||
import { loadPluginConfig } from "./plugin-config"
|
||||
import { createModelCacheState } from "./plugin-state"
|
||||
@@ -17,27 +16,23 @@ import { createFirstMessageVariantGate } from "./shared/first-message-variant"
|
||||
import { injectServerAuthIntoClient, log, logLegacyPluginStartupWarning } from "./shared"
|
||||
import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector"
|
||||
import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
|
||||
import { lspManager } from "./tools/lsp/client"
|
||||
import { createPluginPostHog, getPostHogDistinctId } from "./shared/posthog"
|
||||
|
||||
let activePluginDispose: PluginDispose | null = null
|
||||
|
||||
const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
const serverPlugin: Plugin = async (input, _options): Promise<Hooks> => {
|
||||
initConfigContext("opencode", null)
|
||||
log("[OhMyOpenCodePlugin] ENTRY - plugin loading", {
|
||||
directory: ctx.directory,
|
||||
directory: input.directory,
|
||||
})
|
||||
logLegacyPluginStartupWarning()
|
||||
|
||||
const skillPluginCheck = detectExternalSkillPlugin(ctx.directory)
|
||||
const skillPluginCheck = detectExternalSkillPlugin(input.directory)
|
||||
if (skillPluginCheck.detected && skillPluginCheck.pluginName) {
|
||||
console.warn(getSkillPluginConflictWarning(skillPluginCheck.pluginName))
|
||||
}
|
||||
|
||||
injectServerAuthIntoClient(ctx.client)
|
||||
await activePluginDispose?.()
|
||||
injectServerAuthIntoClient(input.client)
|
||||
|
||||
const pluginConfig = loadPluginConfig(ctx.directory, ctx)
|
||||
const pluginConfig = loadPluginConfig(input.directory, input)
|
||||
|
||||
const posthog = createPluginPostHog()
|
||||
const distinctId = getPostHogDistinctId()
|
||||
@@ -78,7 +73,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
const modelCacheState = createModelCacheState()
|
||||
|
||||
const managers = createManagers({
|
||||
ctx,
|
||||
ctx: input,
|
||||
pluginConfig,
|
||||
tmuxConfig,
|
||||
modelCacheState,
|
||||
@@ -86,13 +81,13 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
})
|
||||
|
||||
const toolsResult = await createTools({
|
||||
ctx,
|
||||
ctx: input,
|
||||
pluginConfig,
|
||||
managers,
|
||||
})
|
||||
|
||||
const hooks = createHooks({
|
||||
ctx,
|
||||
ctx: input,
|
||||
pluginConfig,
|
||||
modelCacheState,
|
||||
backgroundManager: managers.backgroundManager,
|
||||
@@ -102,15 +97,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
availableSkills: toolsResult.availableSkills,
|
||||
})
|
||||
|
||||
const dispose = createPluginDispose({
|
||||
backgroundManager: managers.backgroundManager,
|
||||
skillMcpManager: managers.skillMcpManager,
|
||||
lspManager,
|
||||
disposeHooks: hooks.disposeHooks,
|
||||
})
|
||||
|
||||
const pluginInterface = createPluginInterface({
|
||||
ctx,
|
||||
ctx: input,
|
||||
pluginConfig,
|
||||
firstMessageVariantGate,
|
||||
managers,
|
||||
@@ -118,30 +106,32 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
||||
tools: toolsResult.filteredTools,
|
||||
})
|
||||
|
||||
activePluginDispose = dispose
|
||||
|
||||
return {
|
||||
name: "oh-my-openagent",
|
||||
...pluginInterface,
|
||||
|
||||
"experimental.session.compacting": async (
|
||||
_input: { sessionID: string },
|
||||
compactingInput: { sessionID: string },
|
||||
output: { context: string[] },
|
||||
): Promise<void> => {
|
||||
await hooks.compactionContextInjector?.capture(_input.sessionID)
|
||||
await hooks.compactionTodoPreserver?.capture(_input.sessionID)
|
||||
await hooks.compactionContextInjector?.capture(compactingInput.sessionID)
|
||||
await hooks.compactionTodoPreserver?.capture(compactingInput.sessionID)
|
||||
await hooks.claudeCodeHooks?.["experimental.session.compacting"]?.(
|
||||
_input,
|
||||
compactingInput,
|
||||
output,
|
||||
)
|
||||
if (hooks.compactionContextInjector) {
|
||||
output.context.push(hooks.compactionContextInjector.inject(_input.sessionID))
|
||||
output.context.push(hooks.compactionContextInjector.inject(compactingInput.sessionID))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default OhMyOpenCodePlugin
|
||||
const pluginModule: PluginModule = {
|
||||
id: "oh-my-openagent",
|
||||
server: serverPlugin,
|
||||
}
|
||||
|
||||
export default pluginModule
|
||||
|
||||
export type {
|
||||
OhMyOpenCodeConfig,
|
||||
|
||||
Reference in New Issue
Block a user