diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 49960d3..0180d8c 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -6,3 +6,4 @@ export { createCommentCheckerHooks } from "./comment-checker"; export { createGrepOutputTruncatorHook } from "./grep-output-truncator"; export { createPulseMonitorHook } from "./pulse-monitor"; export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector"; +export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detector"; diff --git a/src/index.ts b/src/index.ts index a0cad0f..88e9005 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,59 +1,70 @@ -import type { Plugin } from "@opencode-ai/plugin" -import { createBuiltinAgents } from "./agents" -import { createTodoContinuationEnforcer, createContextWindowMonitorHook, createSessionRecoveryHook, createCommentCheckerHooks, createGrepOutputTruncatorHook, createPulseMonitorHook } 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" +import type { Plugin } from "@opencode-ai/plugin"; +import { createBuiltinAgents } from "./agents"; +import { + createTodoContinuationEnforcer, + createContextWindowMonitorHook, + createSessionRecoveryHook, + createCommentCheckerHooks, + createGrepOutputTruncatorHook, + createPulseMonitorHook, + createDirectoryAgentsInjectorHook, +} 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) + 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}:`) + console.error( + `[oh-my-opencode] Config validation error in ${configPath}:`, + ); for (const issue of result.error.issues) { - console.error(` - ${issue.path.join(".")}: ${issue.message}`) + console.error(` - ${issue.path.join(".")}: ${issue.message}`); } - return {} + return {}; } - return result.data + return result.data; } } catch { // Ignore parse errors, use defaults } } - return {} + return {}; } const OhMyOpenCodePlugin: Plugin = async (ctx) => { - const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx) - const contextWindowMonitor = createContextWindowMonitorHook(ctx) - const sessionRecovery = createSessionRecoveryHook(ctx) - const pulseMonitor = createPulseMonitorHook(ctx) - const commentChecker = createCommentCheckerHooks() - const grepOutputTruncator = createGrepOutputTruncatorHook(ctx) + const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx); + const contextWindowMonitor = createContextWindowMonitorHook(ctx); + const sessionRecovery = createSessionRecoveryHook(ctx); + const pulseMonitor = createPulseMonitorHook(ctx); + const commentChecker = createCommentCheckerHooks(); + const grepOutputTruncator = createGrepOutputTruncatorHook(ctx); + const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx); - updateTerminalTitle({ sessionId: "main" }) + updateTerminalTitle({ sessionId: "main" }); - const pluginConfig = loadPluginConfig(ctx.directory) + const pluginConfig = loadPluginConfig(ctx.directory); - let mainSessionID: string | undefined - let currentSessionID: string | undefined - let currentSessionTitle: string | undefined + let mainSessionID: string | undefined; + let currentSessionID: string | undefined; + let currentSessionTitle: string | undefined; return { tool: builtinTools, @@ -61,76 +72,80 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { config: async (config) => { const agents = createBuiltinAgents( pluginConfig.disabled_agents, - pluginConfig.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) - await pulseMonitor.event(input) + await todoContinuationEnforcer(input); + await contextWindowMonitor.event(input); + await pulseMonitor.event(input); + await directoryAgentsInjector.event(input); - const { event } = input - const props = event.properties as Record | undefined + const { event } = input; + const props = event.properties as Record | undefined; if (event.type === "session.created") { - const sessionInfo = props?.info as { id?: string; title?: string; parentID?: string } | undefined + const sessionInfo = props?.info as + | { id?: string; title?: string; parentID?: string } + | undefined; if (!sessionInfo?.parentID) { - mainSessionID = sessionInfo?.id - currentSessionID = sessionInfo?.id - currentSessionTitle = sessionInfo?.title + 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 + const sessionInfo = props?.info as + | { id?: string; title?: string; parentID?: string } + | undefined; if (!sessionInfo?.parentID) { - currentSessionID = sessionInfo?.id - currentSessionTitle = sessionInfo?.title + 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 + const sessionInfo = props?.info as { id?: string } | undefined; if (sessionInfo?.id === mainSessionID) { - mainSessionID = undefined - currentSessionID = undefined - currentSessionTitle = undefined + 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 + const sessionID = props?.sessionID as string | undefined; + const error = props?.error; if (sessionRecovery.isRecoverableError(error)) { const messageInfo = { @@ -138,15 +153,18 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { role: "assistant" as const, sessionID, error, - } - const recovered = await sessionRecovery.handleSessionRecovery(messageInfo) + }; + const recovered = + await sessionRecovery.handleSessionRecovery(messageInfo); if (recovered && sessionID && sessionID === mainSessionID) { - await ctx.client.session.prompt({ - path: { id: sessionID }, - body: { parts: [{ type: "text", text: "continue" }] }, - query: { directory: ctx.directory }, - }).catch(() => {}) + await ctx.client.session + .prompt({ + path: { id: sessionID }, + body: { parts: [{ type: "text", text: "continue" }] }, + query: { directory: ctx.directory }, + }) + .catch(() => {}); } } @@ -156,26 +174,26 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { status: "error", directory: ctx.directory, sessionTitle: currentSessionTitle, - }) + }); } } if (event.type === "session.idle") { - const sessionID = props?.sessionID as string | undefined + 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 pulseMonitor["tool.execute.before"]() - await commentChecker["tool.execute.before"](input, output) + await pulseMonitor["tool.execute.before"](); + await commentChecker["tool.execute.before"](input, output); if (input.sessionID === mainSessionID) { updateTerminalTitle({ @@ -184,15 +202,16 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { currentTool: input.tool, directory: ctx.directory, sessionTitle: currentSessionTitle, - }) + }); } }, "tool.execute.after": async (input, output) => { - await pulseMonitor["tool.execute.after"](input) - await grepOutputTruncator["tool.execute.after"](input, output) - await contextWindowMonitor["tool.execute.after"](input, output) - await commentChecker["tool.execute.after"](input, output) + await pulseMonitor["tool.execute.after"](input); + await grepOutputTruncator["tool.execute.after"](input, output); + await contextWindowMonitor["tool.execute.after"](input, output); + await commentChecker["tool.execute.after"](input, output); + await directoryAgentsInjector["tool.execute.after"](input, output); if (input.sessionID === mainSessionID) { updateTerminalTitle({ @@ -200,13 +219,13 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { status: "idle", directory: ctx.directory, sessionTitle: currentSessionTitle, - }) + }); } }, - } -} + }; +}; -export default OhMyOpenCodePlugin +export default OhMyOpenCodePlugin; export type { OhMyOpenCodeConfig, @@ -214,4 +233,4 @@ export type { AgentOverrideConfig, AgentOverrides, McpName, -} from "./config" +} from "./config";