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()}`)
|
const { default: plugin } = await import(`./index?telemetry=${Date.now()}-${Math.random()}`)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await plugin({
|
const result = await plugin.server({
|
||||||
directory: "/tmp/project",
|
directory: "/tmp/project",
|
||||||
client: {},
|
client: {},
|
||||||
} as Parameters<typeof plugin>[0])
|
} as Parameters<typeof plugin.server>[0])
|
||||||
|
|
||||||
// then
|
// 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 mockInitializeOpenClaw = mock(async () => {})
|
||||||
const mockStartTmuxCheck = mock(() => {})
|
const mockStartTmuxCheck = mock(() => {})
|
||||||
|
|
||||||
let OhMyOpenCodePlugin: (typeof import("./index"))["default"]
|
let pluginModule: (typeof import("./index"))["default"]
|
||||||
|
|
||||||
function installIndexModuleMocks(): void {
|
function installIndexModuleMocks(): void {
|
||||||
mock.module("./cli/config-manager/config-context", () => ({
|
mock.module("./cli/config-manager/config-context", () => ({
|
||||||
@@ -337,7 +337,7 @@ describe("OhMyOpenCodePlugin", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mock.restore()
|
mock.restore()
|
||||||
installIndexModuleMocks()
|
installIndexModuleMocks()
|
||||||
;({ default: OhMyOpenCodePlugin } = await importFreshIndexModule())
|
;({ default: pluginModule } = await importFreshIndexModule())
|
||||||
mockInitConfigContext.mockClear()
|
mockInitConfigContext.mockClear()
|
||||||
mockDetectExternalSkillPlugin.mockClear()
|
mockDetectExternalSkillPlugin.mockClear()
|
||||||
mockGetSkillPluginConflictWarning.mockClear()
|
mockGetSkillPluginConflictWarning.mockClear()
|
||||||
@@ -375,10 +375,10 @@ describe("OhMyOpenCodePlugin", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await OhMyOpenCodePlugin({
|
await pluginModule.server({
|
||||||
directory: "/tmp/project",
|
directory: "/tmp/project",
|
||||||
client: {},
|
client: {},
|
||||||
} as Parameters<typeof OhMyOpenCodePlugin>[0])
|
} as Parameters<typeof pluginModule.server>[0])
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(mockInitializeOpenClaw).toHaveBeenCalledTimes(1)
|
expect(mockInitializeOpenClaw).toHaveBeenCalledTimes(1)
|
||||||
@@ -390,12 +390,21 @@ describe("OhMyOpenCodePlugin", () => {
|
|||||||
mockLoadPluginConfig.mockReturnValue({})
|
mockLoadPluginConfig.mockReturnValue({})
|
||||||
|
|
||||||
// when
|
// when
|
||||||
await OhMyOpenCodePlugin({
|
await pluginModule.server({
|
||||||
directory: "/tmp/project",
|
directory: "/tmp/project",
|
||||||
client: {},
|
client: {},
|
||||||
} as Parameters<typeof OhMyOpenCodePlugin>[0])
|
} as Parameters<typeof pluginModule.server>[0])
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(mockInitializeOpenClaw).not.toHaveBeenCalled()
|
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 { 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"
|
import type { HookName } from "./config"
|
||||||
|
|
||||||
@@ -9,7 +9,6 @@ import { createRuntimeTmuxConfig, isTmuxIntegrationEnabled } from "./create-runt
|
|||||||
import { createTools } from "./create-tools"
|
import { createTools } from "./create-tools"
|
||||||
import { initializeOpenClaw } from "./openclaw"
|
import { initializeOpenClaw } from "./openclaw"
|
||||||
import { createPluginInterface } from "./plugin-interface"
|
import { createPluginInterface } from "./plugin-interface"
|
||||||
import { createPluginDispose, type PluginDispose } from "./plugin-dispose"
|
|
||||||
|
|
||||||
import { loadPluginConfig } from "./plugin-config"
|
import { loadPluginConfig } from "./plugin-config"
|
||||||
import { createModelCacheState } from "./plugin-state"
|
import { createModelCacheState } from "./plugin-state"
|
||||||
@@ -17,27 +16,23 @@ import { createFirstMessageVariantGate } from "./shared/first-message-variant"
|
|||||||
import { injectServerAuthIntoClient, log, logLegacyPluginStartupWarning } from "./shared"
|
import { injectServerAuthIntoClient, log, logLegacyPluginStartupWarning } from "./shared"
|
||||||
import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector"
|
import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector"
|
||||||
import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
|
import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
|
||||||
import { lspManager } from "./tools/lsp/client"
|
|
||||||
import { createPluginPostHog, getPostHogDistinctId } from "./shared/posthog"
|
import { createPluginPostHog, getPostHogDistinctId } from "./shared/posthog"
|
||||||
|
|
||||||
let activePluginDispose: PluginDispose | null = null
|
const serverPlugin: Plugin = async (input, _options): Promise<Hooks> => {
|
||||||
|
|
||||||
const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
|
||||||
initConfigContext("opencode", null)
|
initConfigContext("opencode", null)
|
||||||
log("[OhMyOpenCodePlugin] ENTRY - plugin loading", {
|
log("[OhMyOpenCodePlugin] ENTRY - plugin loading", {
|
||||||
directory: ctx.directory,
|
directory: input.directory,
|
||||||
})
|
})
|
||||||
logLegacyPluginStartupWarning()
|
logLegacyPluginStartupWarning()
|
||||||
|
|
||||||
const skillPluginCheck = detectExternalSkillPlugin(ctx.directory)
|
const skillPluginCheck = detectExternalSkillPlugin(input.directory)
|
||||||
if (skillPluginCheck.detected && skillPluginCheck.pluginName) {
|
if (skillPluginCheck.detected && skillPluginCheck.pluginName) {
|
||||||
console.warn(getSkillPluginConflictWarning(skillPluginCheck.pluginName))
|
console.warn(getSkillPluginConflictWarning(skillPluginCheck.pluginName))
|
||||||
}
|
}
|
||||||
|
|
||||||
injectServerAuthIntoClient(ctx.client)
|
injectServerAuthIntoClient(input.client)
|
||||||
await activePluginDispose?.()
|
|
||||||
|
|
||||||
const pluginConfig = loadPluginConfig(ctx.directory, ctx)
|
const pluginConfig = loadPluginConfig(input.directory, input)
|
||||||
|
|
||||||
const posthog = createPluginPostHog()
|
const posthog = createPluginPostHog()
|
||||||
const distinctId = getPostHogDistinctId()
|
const distinctId = getPostHogDistinctId()
|
||||||
@@ -78,7 +73,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
|||||||
const modelCacheState = createModelCacheState()
|
const modelCacheState = createModelCacheState()
|
||||||
|
|
||||||
const managers = createManagers({
|
const managers = createManagers({
|
||||||
ctx,
|
ctx: input,
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
tmuxConfig,
|
tmuxConfig,
|
||||||
modelCacheState,
|
modelCacheState,
|
||||||
@@ -86,13 +81,13 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const toolsResult = await createTools({
|
const toolsResult = await createTools({
|
||||||
ctx,
|
ctx: input,
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
managers,
|
managers,
|
||||||
})
|
})
|
||||||
|
|
||||||
const hooks = createHooks({
|
const hooks = createHooks({
|
||||||
ctx,
|
ctx: input,
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
modelCacheState,
|
modelCacheState,
|
||||||
backgroundManager: managers.backgroundManager,
|
backgroundManager: managers.backgroundManager,
|
||||||
@@ -102,15 +97,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
|||||||
availableSkills: toolsResult.availableSkills,
|
availableSkills: toolsResult.availableSkills,
|
||||||
})
|
})
|
||||||
|
|
||||||
const dispose = createPluginDispose({
|
|
||||||
backgroundManager: managers.backgroundManager,
|
|
||||||
skillMcpManager: managers.skillMcpManager,
|
|
||||||
lspManager,
|
|
||||||
disposeHooks: hooks.disposeHooks,
|
|
||||||
})
|
|
||||||
|
|
||||||
const pluginInterface = createPluginInterface({
|
const pluginInterface = createPluginInterface({
|
||||||
ctx,
|
ctx: input,
|
||||||
pluginConfig,
|
pluginConfig,
|
||||||
firstMessageVariantGate,
|
firstMessageVariantGate,
|
||||||
managers,
|
managers,
|
||||||
@@ -118,30 +106,32 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
|
|||||||
tools: toolsResult.filteredTools,
|
tools: toolsResult.filteredTools,
|
||||||
})
|
})
|
||||||
|
|
||||||
activePluginDispose = dispose
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "oh-my-openagent",
|
|
||||||
...pluginInterface,
|
...pluginInterface,
|
||||||
|
|
||||||
"experimental.session.compacting": async (
|
"experimental.session.compacting": async (
|
||||||
_input: { sessionID: string },
|
compactingInput: { sessionID: string },
|
||||||
output: { context: string[] },
|
output: { context: string[] },
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
await hooks.compactionContextInjector?.capture(_input.sessionID)
|
await hooks.compactionContextInjector?.capture(compactingInput.sessionID)
|
||||||
await hooks.compactionTodoPreserver?.capture(_input.sessionID)
|
await hooks.compactionTodoPreserver?.capture(compactingInput.sessionID)
|
||||||
await hooks.claudeCodeHooks?.["experimental.session.compacting"]?.(
|
await hooks.claudeCodeHooks?.["experimental.session.compacting"]?.(
|
||||||
_input,
|
compactingInput,
|
||||||
output,
|
output,
|
||||||
)
|
)
|
||||||
if (hooks.compactionContextInjector) {
|
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 {
|
export type {
|
||||||
OhMyOpenCodeConfig,
|
OhMyOpenCodeConfig,
|
||||||
|
|||||||
Reference in New Issue
Block a user