Files
oh-my-openagent/src/index.ts
2025-12-05 09:29:08 +09:00

204 lines
6.0 KiB
TypeScript

import type { Plugin } from "@opencode-ai/plugin"
import { createBuiltinAgents } from "./agents"
import { createTodoContinuationEnforcer, createContextWindowMonitorHook, createSessionRecoveryHook, createCommentCheckerHooks } from "./hooks"
import { updateTerminalTitle } from "./features/terminal"
import { builtinTools } from "./tools"
import { createBuiltinMcps } from "./mcp"
import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig } from "./config"
import * as fs from "fs"
import * as path from "path"
function loadPluginConfig(directory: string): OhMyOpenCodeConfig {
const configPaths = [
path.join(directory, "oh-my-opencode.json"),
path.join(directory, ".oh-my-opencode.json"),
]
for (const configPath of configPaths) {
try {
if (fs.existsSync(configPath)) {
const content = fs.readFileSync(configPath, "utf-8")
const rawConfig = JSON.parse(content)
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig)
if (!result.success) {
console.error(`[oh-my-opencode] Config validation error in ${configPath}:`)
for (const issue of result.error.issues) {
console.error(` - ${issue.path.join(".")}: ${issue.message}`)
}
return {}
}
return result.data
}
} catch {
// Ignore parse errors, use defaults
}
}
return {}
}
const OhMyOpenCodePlugin: Plugin = async (ctx) => {
const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx)
const contextWindowMonitor = createContextWindowMonitorHook(ctx)
const sessionRecovery = createSessionRecoveryHook(ctx)
const commentChecker = createCommentCheckerHooks()
updateTerminalTitle({ sessionId: "main" })
const pluginConfig = loadPluginConfig(ctx.directory)
let mainSessionID: string | undefined
let currentSessionID: string | undefined
let currentSessionTitle: string | undefined
return {
tool: builtinTools,
config: async (config) => {
const agents = createBuiltinAgents(
pluginConfig.disabled_agents,
pluginConfig.agents
)
config.agent = {
...config.agent,
...agents,
}
config.tools = {
...config.tools,
grep: false,
}
config.mcp = {
...config.mcp,
...createBuiltinMcps(pluginConfig.disabled_mcps),
}
},
event: async (input) => {
await todoContinuationEnforcer(input)
await contextWindowMonitor.event(input)
const { event } = input
const props = event.properties as Record<string, unknown> | undefined
if (event.type === "session.created") {
const sessionInfo = props?.info as { id?: string; title?: string; parentID?: string } | undefined
if (!sessionInfo?.parentID) {
mainSessionID = sessionInfo?.id
currentSessionID = sessionInfo?.id
currentSessionTitle = sessionInfo?.title
updateTerminalTitle({
sessionId: currentSessionID || "main",
status: "idle",
directory: ctx.directory,
sessionTitle: currentSessionTitle,
})
}
}
if (event.type === "session.updated") {
const sessionInfo = props?.info as { id?: string; title?: string; parentID?: string } | undefined
if (!sessionInfo?.parentID) {
currentSessionID = sessionInfo?.id
currentSessionTitle = sessionInfo?.title
updateTerminalTitle({
sessionId: currentSessionID || "main",
status: "processing",
directory: ctx.directory,
sessionTitle: currentSessionTitle,
})
}
}
if (event.type === "session.deleted") {
const sessionInfo = props?.info as { id?: string } | undefined
if (sessionInfo?.id === mainSessionID) {
mainSessionID = undefined
currentSessionID = undefined
currentSessionTitle = undefined
updateTerminalTitle({
sessionId: "main",
status: "idle",
})
}
}
if (event.type === "session.error") {
const sessionID = props?.sessionID as string | undefined
const error = props?.error
if (sessionRecovery.isRecoverableError(error)) {
const messageInfo = {
id: props?.messageID as string | undefined,
role: "assistant" as const,
sessionID,
error,
}
await sessionRecovery.handleSessionRecovery(messageInfo)
}
if (sessionID && sessionID === mainSessionID) {
updateTerminalTitle({
sessionId: sessionID,
status: "error",
directory: ctx.directory,
sessionTitle: currentSessionTitle,
})
}
}
if (event.type === "session.idle") {
const sessionID = props?.sessionID as string | undefined
if (sessionID && sessionID === mainSessionID) {
updateTerminalTitle({
sessionId: sessionID,
status: "idle",
directory: ctx.directory,
sessionTitle: currentSessionTitle,
})
}
}
},
"tool.execute.before": async (input, output) => {
await commentChecker["tool.execute.before"](input, output)
if (input.sessionID === mainSessionID) {
updateTerminalTitle({
sessionId: input.sessionID,
status: "tool",
currentTool: input.tool,
directory: ctx.directory,
sessionTitle: currentSessionTitle,
})
}
},
"tool.execute.after": async (input, output) => {
await contextWindowMonitor["tool.execute.after"](input, output)
await commentChecker["tool.execute.after"](input, output)
if (input.sessionID === mainSessionID) {
updateTerminalTitle({
sessionId: input.sessionID,
status: "idle",
directory: ctx.directory,
sessionTitle: currentSessionTitle,
})
}
},
}
}
export default OhMyOpenCodePlugin
export type {
OhMyOpenCodeConfig,
AgentName,
AgentOverrideConfig,
AgentOverrides,
McpName,
} from "./config"