feat(hooks): integrate directory-agents-injector hook into plugin pipeline

- Add directoryAgentsInjector to plugin event handlers
- Wire up tool.execute.after hook for directory agents injection
- Fix: Format src/index.ts with consistent semicolon style
This commit is contained in:
YeonGyu-Kim
2025-12-08 15:01:38 +09:00
parent a500f0c9ad
commit 4e328a937c
2 changed files with 98 additions and 78 deletions

View File

@@ -6,3 +6,4 @@ export { createCommentCheckerHooks } from "./comment-checker";
export { createGrepOutputTruncatorHook } from "./grep-output-truncator"; export { createGrepOutputTruncatorHook } from "./grep-output-truncator";
export { createPulseMonitorHook } from "./pulse-monitor"; export { createPulseMonitorHook } from "./pulse-monitor";
export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector"; export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector";
export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detector";

View File

@@ -1,59 +1,70 @@
import type { Plugin } from "@opencode-ai/plugin" import type { Plugin } from "@opencode-ai/plugin";
import { createBuiltinAgents } from "./agents" import { createBuiltinAgents } from "./agents";
import { createTodoContinuationEnforcer, createContextWindowMonitorHook, createSessionRecoveryHook, createCommentCheckerHooks, createGrepOutputTruncatorHook, createPulseMonitorHook } from "./hooks" import {
import { updateTerminalTitle } from "./features/terminal" createTodoContinuationEnforcer,
import { builtinTools } from "./tools" createContextWindowMonitorHook,
import { createBuiltinMcps } from "./mcp" createSessionRecoveryHook,
import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig } from "./config" createCommentCheckerHooks,
import * as fs from "fs" createGrepOutputTruncatorHook,
import * as path from "path" 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 { function loadPluginConfig(directory: string): OhMyOpenCodeConfig {
const configPaths = [ const configPaths = [
path.join(directory, "oh-my-opencode.json"), path.join(directory, "oh-my-opencode.json"),
path.join(directory, ".oh-my-opencode.json"), path.join(directory, ".oh-my-opencode.json"),
] ];
for (const configPath of configPaths) { for (const configPath of configPaths) {
try { try {
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
const content = fs.readFileSync(configPath, "utf-8") const content = fs.readFileSync(configPath, "utf-8");
const rawConfig = JSON.parse(content) const rawConfig = JSON.parse(content);
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig) const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
if (!result.success) { 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) { 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 { } catch {
// Ignore parse errors, use defaults // Ignore parse errors, use defaults
} }
} }
return {} return {};
} }
const OhMyOpenCodePlugin: Plugin = async (ctx) => { const OhMyOpenCodePlugin: Plugin = async (ctx) => {
const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx) const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx);
const contextWindowMonitor = createContextWindowMonitorHook(ctx) const contextWindowMonitor = createContextWindowMonitorHook(ctx);
const sessionRecovery = createSessionRecoveryHook(ctx) const sessionRecovery = createSessionRecoveryHook(ctx);
const pulseMonitor = createPulseMonitorHook(ctx) const pulseMonitor = createPulseMonitorHook(ctx);
const commentChecker = createCommentCheckerHooks() const commentChecker = createCommentCheckerHooks();
const grepOutputTruncator = createGrepOutputTruncatorHook(ctx) 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 mainSessionID: string | undefined;
let currentSessionID: string | undefined let currentSessionID: string | undefined;
let currentSessionTitle: string | undefined let currentSessionTitle: string | undefined;
return { return {
tool: builtinTools, tool: builtinTools,
@@ -61,76 +72,80 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
config: async (config) => { config: async (config) => {
const agents = createBuiltinAgents( const agents = createBuiltinAgents(
pluginConfig.disabled_agents, pluginConfig.disabled_agents,
pluginConfig.agents pluginConfig.agents,
) );
config.agent = { config.agent = {
...config.agent, ...config.agent,
...agents, ...agents,
} };
config.tools = { config.tools = {
...config.tools, ...config.tools,
grep: false, };
}
config.mcp = { config.mcp = {
...config.mcp, ...config.mcp,
...createBuiltinMcps(pluginConfig.disabled_mcps), ...createBuiltinMcps(pluginConfig.disabled_mcps),
} };
}, },
event: async (input) => { event: async (input) => {
await todoContinuationEnforcer(input) await todoContinuationEnforcer(input);
await contextWindowMonitor.event(input) await contextWindowMonitor.event(input);
await pulseMonitor.event(input) await pulseMonitor.event(input);
await directoryAgentsInjector.event(input);
const { event } = input const { event } = input;
const props = event.properties as Record<string, unknown> | undefined const props = event.properties as Record<string, unknown> | undefined;
if (event.type === "session.created") { 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) { if (!sessionInfo?.parentID) {
mainSessionID = sessionInfo?.id mainSessionID = sessionInfo?.id;
currentSessionID = sessionInfo?.id currentSessionID = sessionInfo?.id;
currentSessionTitle = sessionInfo?.title currentSessionTitle = sessionInfo?.title;
updateTerminalTitle({ updateTerminalTitle({
sessionId: currentSessionID || "main", sessionId: currentSessionID || "main",
status: "idle", status: "idle",
directory: ctx.directory, directory: ctx.directory,
sessionTitle: currentSessionTitle, sessionTitle: currentSessionTitle,
}) });
} }
} }
if (event.type === "session.updated") { 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) { if (!sessionInfo?.parentID) {
currentSessionID = sessionInfo?.id currentSessionID = sessionInfo?.id;
currentSessionTitle = sessionInfo?.title currentSessionTitle = sessionInfo?.title;
updateTerminalTitle({ updateTerminalTitle({
sessionId: currentSessionID || "main", sessionId: currentSessionID || "main",
status: "processing", status: "processing",
directory: ctx.directory, directory: ctx.directory,
sessionTitle: currentSessionTitle, sessionTitle: currentSessionTitle,
}) });
} }
} }
if (event.type === "session.deleted") { 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) { if (sessionInfo?.id === mainSessionID) {
mainSessionID = undefined mainSessionID = undefined;
currentSessionID = undefined currentSessionID = undefined;
currentSessionTitle = undefined currentSessionTitle = undefined;
updateTerminalTitle({ updateTerminalTitle({
sessionId: "main", sessionId: "main",
status: "idle", status: "idle",
}) });
} }
} }
if (event.type === "session.error") { if (event.type === "session.error") {
const sessionID = props?.sessionID as string | undefined const sessionID = props?.sessionID as string | undefined;
const error = props?.error const error = props?.error;
if (sessionRecovery.isRecoverableError(error)) { if (sessionRecovery.isRecoverableError(error)) {
const messageInfo = { const messageInfo = {
@@ -138,15 +153,18 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
role: "assistant" as const, role: "assistant" as const,
sessionID, sessionID,
error, error,
} };
const recovered = await sessionRecovery.handleSessionRecovery(messageInfo) const recovered =
await sessionRecovery.handleSessionRecovery(messageInfo);
if (recovered && sessionID && sessionID === mainSessionID) { if (recovered && sessionID && sessionID === mainSessionID) {
await ctx.client.session.prompt({ await ctx.client.session
path: { id: sessionID }, .prompt({
body: { parts: [{ type: "text", text: "continue" }] }, path: { id: sessionID },
query: { directory: ctx.directory }, body: { parts: [{ type: "text", text: "continue" }] },
}).catch(() => {}) query: { directory: ctx.directory },
})
.catch(() => {});
} }
} }
@@ -156,26 +174,26 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
status: "error", status: "error",
directory: ctx.directory, directory: ctx.directory,
sessionTitle: currentSessionTitle, sessionTitle: currentSessionTitle,
}) });
} }
} }
if (event.type === "session.idle") { if (event.type === "session.idle") {
const sessionID = props?.sessionID as string | undefined const sessionID = props?.sessionID as string | undefined;
if (sessionID && sessionID === mainSessionID) { if (sessionID && sessionID === mainSessionID) {
updateTerminalTitle({ updateTerminalTitle({
sessionId: sessionID, sessionId: sessionID,
status: "idle", status: "idle",
directory: ctx.directory, directory: ctx.directory,
sessionTitle: currentSessionTitle, sessionTitle: currentSessionTitle,
}) });
} }
} }
}, },
"tool.execute.before": async (input, output) => { "tool.execute.before": async (input, output) => {
await pulseMonitor["tool.execute.before"]() await pulseMonitor["tool.execute.before"]();
await commentChecker["tool.execute.before"](input, output) await commentChecker["tool.execute.before"](input, output);
if (input.sessionID === mainSessionID) { if (input.sessionID === mainSessionID) {
updateTerminalTitle({ updateTerminalTitle({
@@ -184,15 +202,16 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
currentTool: input.tool, currentTool: input.tool,
directory: ctx.directory, directory: ctx.directory,
sessionTitle: currentSessionTitle, sessionTitle: currentSessionTitle,
}) });
} }
}, },
"tool.execute.after": async (input, output) => { "tool.execute.after": async (input, output) => {
await pulseMonitor["tool.execute.after"](input) await pulseMonitor["tool.execute.after"](input);
await grepOutputTruncator["tool.execute.after"](input, output) await grepOutputTruncator["tool.execute.after"](input, output);
await contextWindowMonitor["tool.execute.after"](input, output) await contextWindowMonitor["tool.execute.after"](input, output);
await commentChecker["tool.execute.after"](input, output) await commentChecker["tool.execute.after"](input, output);
await directoryAgentsInjector["tool.execute.after"](input, output);
if (input.sessionID === mainSessionID) { if (input.sessionID === mainSessionID) {
updateTerminalTitle({ updateTerminalTitle({
@@ -200,13 +219,13 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
status: "idle", status: "idle",
directory: ctx.directory, directory: ctx.directory,
sessionTitle: currentSessionTitle, sessionTitle: currentSessionTitle,
}) });
} }
}, },
} };
} };
export default OhMyOpenCodePlugin export default OhMyOpenCodePlugin;
export type { export type {
OhMyOpenCodeConfig, OhMyOpenCodeConfig,
@@ -214,4 +233,4 @@ export type {
AgentOverrideConfig, AgentOverrideConfig,
AgentOverrides, AgentOverrides,
McpName, McpName,
} from "./config" } from "./config";