Per reviewer feedback (code-yeongyu), keep the 'skill' tool as the main tool and merge slashcommand functionality INTO it, rather than the reverse. Changes: - skill/tools.ts: Add command discovery (discoverCommandsSync) support; handle both SKILL.md skills and .omo/commands/ slash commands in a single tool; show combined listing in tool description - skill/types.ts: Add 'commands' option to SkillLoadOptions - skill/constants.ts: Update description to mention both skills and commands - plugin/tool-registry.ts: Replace createSlashcommandTool with createSkillTool; register tool as 'skill' instead of 'slashcommand' - tools/index.ts: Export createSkillTool instead of createSlashcommandTool - plugin/tool-execute-before.ts: Update tool name checks from 'slashcommand' to 'skill'; update arg name from 'command' to 'name' - agents/dynamic-agent-prompt-builder.ts: Categorize 'skill' tool as 'command' - tools/skill-mcp/tools.ts: Update hint message to reference 'skill' tool - hooks/auto-slash-command/executor.ts: Update error message The slashcommand/ module files are kept (they provide shared utilities used by the skill tool), but the slashcommand tool itself is no longer registered.
104 lines
4.5 KiB
TypeScript
104 lines
4.5 KiB
TypeScript
import type { PluginContext } from "./types"
|
|
|
|
import { getMainSessionID } from "../features/claude-code-session-state"
|
|
import { clearBoulderState } from "../features/boulder-state"
|
|
import { log } from "../shared"
|
|
import { resolveSessionAgent } from "./session-agent-resolver"
|
|
|
|
import type { CreatedHooks } from "../create-hooks"
|
|
|
|
export function createToolExecuteBeforeHandler(args: {
|
|
ctx: PluginContext
|
|
hooks: CreatedHooks
|
|
}): (
|
|
input: { tool: string; sessionID: string; callID: string },
|
|
output: { args: Record<string, unknown> },
|
|
) => Promise<void> {
|
|
const { ctx, hooks } = args
|
|
|
|
return async (input, output): Promise<void> => {
|
|
await hooks.writeExistingFileGuard?.["tool.execute.before"]?.(input, output)
|
|
await hooks.questionLabelTruncator?.["tool.execute.before"]?.(input, output)
|
|
await hooks.claudeCodeHooks?.["tool.execute.before"]?.(input, output)
|
|
await hooks.nonInteractiveEnv?.["tool.execute.before"]?.(input, output)
|
|
await hooks.commentChecker?.["tool.execute.before"]?.(input, output)
|
|
await hooks.directoryAgentsInjector?.["tool.execute.before"]?.(input, output)
|
|
await hooks.directoryReadmeInjector?.["tool.execute.before"]?.(input, output)
|
|
await hooks.rulesInjector?.["tool.execute.before"]?.(input, output)
|
|
await hooks.tasksTodowriteDisabler?.["tool.execute.before"]?.(input, output)
|
|
await hooks.prometheusMdOnly?.["tool.execute.before"]?.(input, output)
|
|
await hooks.sisyphusJuniorNotepad?.["tool.execute.before"]?.(input, output)
|
|
await hooks.atlasHook?.["tool.execute.before"]?.(input, output)
|
|
if (input.tool === "task") {
|
|
const argsObject = output.args
|
|
const category = typeof argsObject.category === "string" ? argsObject.category : undefined
|
|
const subagentType = typeof argsObject.subagent_type === "string" ? argsObject.subagent_type : undefined
|
|
const sessionId = typeof argsObject.session_id === "string" ? argsObject.session_id : undefined
|
|
|
|
if (category) {
|
|
argsObject.subagent_type = "sisyphus-junior"
|
|
} else if (!subagentType && sessionId) {
|
|
const resolvedAgent = await resolveSessionAgent(ctx.client, sessionId)
|
|
argsObject.subagent_type = resolvedAgent ?? "continue"
|
|
}
|
|
}
|
|
|
|
if (hooks.ralphLoop && input.tool === "skill") {
|
|
const rawName = typeof output.args.name === "string" ? output.args.name : undefined
|
|
const command = rawName?.replace(/^\//, "").toLowerCase()
|
|
const sessionID = input.sessionID || getMainSessionID()
|
|
|
|
if (command === "ralph-loop" && sessionID) {
|
|
const rawArgs = rawName?.replace(/^\/?(ralph-loop)\s*/i, "") || ""
|
|
const taskMatch = rawArgs.match(/^["'](.+?)["']/)
|
|
const prompt =
|
|
taskMatch?.[1] ||
|
|
rawArgs.split(/\s+--/)[0]?.trim() ||
|
|
"Complete the task as instructed"
|
|
|
|
const maxIterMatch = rawArgs.match(/--max-iterations=(\d+)/i)
|
|
const promiseMatch = rawArgs.match(/--completion-promise=["']?([^"'\s]+)["']?/i)
|
|
|
|
hooks.ralphLoop.startLoop(sessionID, prompt, {
|
|
maxIterations: maxIterMatch ? parseInt(maxIterMatch[1], 10) : undefined,
|
|
completionPromise: promiseMatch?.[1],
|
|
})
|
|
} else if (command === "cancel-ralph" && sessionID) {
|
|
hooks.ralphLoop.cancelLoop(sessionID)
|
|
} else if (command === "ulw-loop" && sessionID) {
|
|
const rawArgs = rawName?.replace(/^\/?(ulw-loop)\s*/i, "") || ""
|
|
const taskMatch = rawArgs.match(/^["'](.+?)["']/)
|
|
const prompt =
|
|
taskMatch?.[1] ||
|
|
rawArgs.split(/\s+--/)[0]?.trim() ||
|
|
"Complete the task as instructed"
|
|
|
|
const maxIterMatch = rawArgs.match(/--max-iterations=(\d+)/i)
|
|
const promiseMatch = rawArgs.match(/--completion-promise=["']?([^"'\s]+)["']?/i)
|
|
|
|
hooks.ralphLoop.startLoop(sessionID, prompt, {
|
|
ultrawork: true,
|
|
maxIterations: maxIterMatch ? parseInt(maxIterMatch[1], 10) : undefined,
|
|
completionPromise: promiseMatch?.[1],
|
|
})
|
|
}
|
|
}
|
|
|
|
if (input.tool === "skill") {
|
|
const rawName = typeof output.args.name === "string" ? output.args.name : undefined
|
|
const command = rawName?.replace(/^\//, "").toLowerCase()
|
|
const sessionID = input.sessionID || getMainSessionID()
|
|
|
|
if (command === "stop-continuation" && sessionID) {
|
|
hooks.stopContinuationGuard?.stop(sessionID)
|
|
hooks.todoContinuationEnforcer?.cancelAllCountdowns()
|
|
hooks.ralphLoop?.cancelLoop(sessionID)
|
|
clearBoulderState(ctx.directory)
|
|
log("[stop-continuation] All continuation mechanisms stopped", {
|
|
sessionID,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|