- Add imports: agent-loader, mcp-loader, session-state, logger - Remove local session variables (mainSessionID, currentSessionID, currentSessionTitle) - Use setter/getter functions from session-state module - Add agent loading in config hook (loadUserAgents, loadProjectAgents) - Add MCP loading in config hook (loadMcpConfigs) - Replace console.error with logger 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
273 lines
8.3 KiB
TypeScript
273 lines
8.3 KiB
TypeScript
import type { Plugin } from "@opencode-ai/plugin";
|
|
import { createBuiltinAgents } from "./agents";
|
|
import {
|
|
createTodoContinuationEnforcer,
|
|
createContextWindowMonitorHook,
|
|
createSessionRecoveryHook,
|
|
createCommentCheckerHooks,
|
|
createGrepOutputTruncatorHook,
|
|
createDirectoryAgentsInjectorHook,
|
|
createEmptyTaskResponseDetectorHook,
|
|
createThinkModeHook,
|
|
} from "./hooks";
|
|
import {
|
|
loadUserCommands,
|
|
loadProjectCommands,
|
|
loadOpencodeGlobalCommands,
|
|
loadOpencodeProjectCommands,
|
|
} from "./features/claude-code-command-loader";
|
|
import {
|
|
loadUserSkillsAsCommands,
|
|
loadProjectSkillsAsCommands,
|
|
} from "./features/claude-code-skill-loader";
|
|
import {
|
|
loadUserAgents,
|
|
loadProjectAgents,
|
|
} from "./features/claude-code-agent-loader";
|
|
import { loadMcpConfigs } from "./features/claude-code-mcp-loader";
|
|
import {
|
|
setCurrentSession,
|
|
setMainSession,
|
|
getMainSessionID,
|
|
getCurrentSessionTitle,
|
|
} from "./features/claude-code-session-state";
|
|
import { updateTerminalTitle } from "./features/terminal";
|
|
import { builtinTools } from "./tools";
|
|
import { createBuiltinMcps } from "./mcp";
|
|
import { OhMyOpenCodeConfigSchema, type OhMyOpenCodeConfig } from "./config";
|
|
import { log } from "./shared/logger";
|
|
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) {
|
|
log(`Config validation error in ${configPath}:`, result.error.issues);
|
|
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();
|
|
const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
|
|
const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
|
|
const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
|
|
const thinkMode = createThinkModeHook();
|
|
|
|
updateTerminalTitle({ sessionId: "main" });
|
|
|
|
const pluginConfig = loadPluginConfig(ctx.directory);
|
|
|
|
return {
|
|
tool: builtinTools,
|
|
|
|
config: async (config) => {
|
|
const builtinAgents = createBuiltinAgents(
|
|
pluginConfig.disabled_agents,
|
|
pluginConfig.agents,
|
|
);
|
|
const userAgents = loadUserAgents();
|
|
const projectAgents = loadProjectAgents();
|
|
|
|
config.agent = {
|
|
...config.agent,
|
|
...builtinAgents,
|
|
...userAgents,
|
|
...projectAgents,
|
|
};
|
|
config.tools = {
|
|
...config.tools,
|
|
};
|
|
|
|
const mcpResult = await loadMcpConfigs();
|
|
config.mcp = {
|
|
...config.mcp,
|
|
...createBuiltinMcps(pluginConfig.disabled_mcps),
|
|
...mcpResult.servers,
|
|
};
|
|
|
|
const userCommands = loadUserCommands();
|
|
const opencodeGlobalCommands = loadOpencodeGlobalCommands();
|
|
const systemCommands = config.command ?? {};
|
|
const projectCommands = loadProjectCommands();
|
|
const opencodeProjectCommands = loadOpencodeProjectCommands();
|
|
const userSkills = loadUserSkillsAsCommands();
|
|
const projectSkills = loadProjectSkillsAsCommands();
|
|
|
|
config.command = {
|
|
...userCommands,
|
|
...userSkills,
|
|
...opencodeGlobalCommands,
|
|
...systemCommands,
|
|
...projectCommands,
|
|
...projectSkills,
|
|
...opencodeProjectCommands,
|
|
};
|
|
},
|
|
|
|
event: async (input) => {
|
|
await todoContinuationEnforcer(input);
|
|
await contextWindowMonitor.event(input);
|
|
await directoryAgentsInjector.event(input);
|
|
await thinkMode.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) {
|
|
setMainSession(sessionInfo?.id);
|
|
setCurrentSession(sessionInfo?.id, sessionInfo?.title);
|
|
updateTerminalTitle({
|
|
sessionId: sessionInfo?.id || "main",
|
|
status: "idle",
|
|
directory: ctx.directory,
|
|
sessionTitle: sessionInfo?.title,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (event.type === "session.updated") {
|
|
const sessionInfo = props?.info as
|
|
| { id?: string; title?: string; parentID?: string }
|
|
| undefined;
|
|
if (!sessionInfo?.parentID) {
|
|
setCurrentSession(sessionInfo?.id, sessionInfo?.title);
|
|
updateTerminalTitle({
|
|
sessionId: sessionInfo?.id || "main",
|
|
status: "processing",
|
|
directory: ctx.directory,
|
|
sessionTitle: sessionInfo?.title,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (event.type === "session.deleted") {
|
|
const sessionInfo = props?.info as { id?: string } | undefined;
|
|
if (sessionInfo?.id === getMainSessionID()) {
|
|
setMainSession(undefined);
|
|
setCurrentSession(undefined, 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,
|
|
};
|
|
const recovered =
|
|
await sessionRecovery.handleSessionRecovery(messageInfo);
|
|
|
|
if (recovered && sessionID && sessionID === getMainSessionID()) {
|
|
await ctx.client.session
|
|
.prompt({
|
|
path: { id: sessionID },
|
|
body: { parts: [{ type: "text", text: "continue" }] },
|
|
query: { directory: ctx.directory },
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
}
|
|
|
|
if (sessionID && sessionID === getMainSessionID()) {
|
|
updateTerminalTitle({
|
|
sessionId: sessionID,
|
|
status: "error",
|
|
directory: ctx.directory,
|
|
sessionTitle: getCurrentSessionTitle(),
|
|
});
|
|
}
|
|
}
|
|
|
|
if (event.type === "session.idle") {
|
|
const sessionID = props?.sessionID as string | undefined;
|
|
if (sessionID && sessionID === getMainSessionID()) {
|
|
updateTerminalTitle({
|
|
sessionId: sessionID,
|
|
status: "idle",
|
|
directory: ctx.directory,
|
|
sessionTitle: getCurrentSessionTitle(),
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
"tool.execute.before": async (input, output) => {
|
|
await commentChecker["tool.execute.before"](input, output);
|
|
|
|
if (input.sessionID === getMainSessionID()) {
|
|
updateTerminalTitle({
|
|
sessionId: input.sessionID,
|
|
status: "tool",
|
|
currentTool: input.tool,
|
|
directory: ctx.directory,
|
|
sessionTitle: getCurrentSessionTitle(),
|
|
});
|
|
}
|
|
},
|
|
|
|
"tool.execute.after": async (input, output) => {
|
|
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);
|
|
await emptyTaskResponseDetector["tool.execute.after"](input, output);
|
|
|
|
if (input.sessionID === getMainSessionID()) {
|
|
updateTerminalTitle({
|
|
sessionId: input.sessionID,
|
|
status: "idle",
|
|
directory: ctx.directory,
|
|
sessionTitle: getCurrentSessionTitle(),
|
|
});
|
|
}
|
|
},
|
|
};
|
|
};
|
|
|
|
export default OhMyOpenCodePlugin;
|
|
|
|
export type {
|
|
OhMyOpenCodeConfig,
|
|
AgentName,
|
|
AgentOverrideConfig,
|
|
AgentOverrides,
|
|
McpName,
|
|
} from "./config";
|