Files
OpenViking/examples/opencode-plugin/index.mjs
T
ValorandValor fdb4b571ca feat(opencode): add hook-only installation mode (#3734)
* feat(opencode): add hook-only installation mode

* test(opencode): cover hook-only plugin configuration

---------

Co-authored-by: Valor <210239105+zrh805@users.noreply.github.com>
2026-08-07 17:11:45 +08:00

100 lines
3.6 KiB
JavaScript

import { dirname } from "path"
import { fileURLToPath } from "url"
import { initializeRuntime } from "./lib/runtime.mjs"
import { createRepoContext } from "./lib/repo-context.mjs"
import { createMemorySessionManager } from "./lib/memory-session.mjs"
import { createMemoryRecall } from "./lib/memory-recall.mjs"
import { createSessionInject } from "./lib/session-inject.mjs"
import { createVikingUriGuard } from "./lib/viking-uri-guard.mjs"
import { injectOpenVikingMcpConfig } from "./lib/mcp-config.mjs"
import { loadConfig, resolveDataDir } from "./lib/config.mjs"
import { initLogger, log, makeToast } from "./lib/utils.mjs"
const pluginRoot = dirname(fileURLToPath(import.meta.url))
export async function OpenVikingPlugin({ client, directory }) {
const config = loadConfig(pluginRoot, directory)
const dataDir = resolveDataDir(pluginRoot, config)
initLogger(dataDir)
if (!config.enabled) {
log("INFO", "plugin", "OpenViking plugin is disabled in configuration")
return {}
}
const repoContext = createRepoContext({ config })
const sessionManager = createMemorySessionManager({ config, pluginRoot: dataDir })
const recall = createMemoryRecall({ config, sessionManager })
const sessionInject = createSessionInject({ config, sessionManager })
const vikingUriGuard = createVikingUriGuard()
await sessionManager.init()
const toast = makeToast(client)
if (config.legacyCredentialsUsed) {
log("WARN", "config", "Legacy OpenCode credential fields are still in use; run node scripts/setup.mjs to migrate to ovcli.conf", {
configPath: config.configPath,
})
await toast("OpenViking credentials in openviking-config.json are deprecated. Run node scripts/setup.mjs to migrate.", "warning")
}
Promise.resolve().then(async () => {
const ready = await initializeRuntime(config, client)
if (ready) await repoContext.refreshRepos({ force: true })
})
return {
config: async (opencodeConfig) => {
const injected = injectOpenVikingMcpConfig(opencodeConfig, pluginRoot, config.mcp.enabled)
const hookOnly = !config.mcp.enabled
log(
injected || hookOnly ? "INFO" : "WARN",
"mcp",
injected ? "Registered OpenViking MCP server" :
hookOnly ? "Skipped bundled MCP registration in hook-only mode" :
"OpenViking MCP server was not registered",
)
},
event: async ({ event }) => {
await sessionManager.handleEvent(event)
if (event?.type === "session.created") {
await repoContext.refreshRepos({ force: true })
}
},
"tool.execute.before": vikingUriGuard,
"experimental.chat.system.transform": (_input, output) => {
const prompt = repoContext.getRepoSystemPrompt()
if (prompt) output.system.push(prompt)
},
"chat.message": async (input, output) => {
try {
await sessionInject.injectSessionContext(input, output)
if (!config.autoRecall?.enabled) return
await recall.injectRelevantMemories(input, output)
} catch (error) {
log("WARN", "recall", "Auto recall failed", { error: error?.message ?? String(error) })
}
},
"experimental.session.compacting": async (input) => {
log("INFO", "compaction", "OpenCode session compacting", {
opencode_session: input.sessionID,
})
await sessionManager.flushSession(input.sessionID, {
commit: true,
reason: "experimental.session.compacting",
})
},
dispose: async () => {
await sessionManager.flushAll({ commit: true })
log("INFO", "plugin", "OpenViking plugin disposed")
},
}
}
export default OpenVikingPlugin