2026-03-31 20:02:00 -07:00
|
|
|
import type { CreatedHooks } from "../create-hooks"
|
2026-04-10 10:47:27 +09:00
|
|
|
import { parseRalphLoopArguments } from "../hooks/ralph-loop/command-arguments"
|
2026-04-10 18:47:18 +09:00
|
|
|
import { log } from "../shared/logger"
|
2026-03-31 20:02:00 -07:00
|
|
|
|
|
|
|
|
type CommandExecuteBeforeInput = {
|
|
|
|
|
command: string
|
|
|
|
|
sessionID: string
|
|
|
|
|
arguments: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type CommandExecuteBeforeOutput = {
|
|
|
|
|
parts: Array<{ type: string; text?: string; [key: string]: unknown }>
|
2026-04-10 10:47:27 +09:00
|
|
|
message?: Record<string, unknown>
|
2026-03-31 20:02:00 -07:00
|
|
|
}
|
|
|
|
|
|
2026-04-10 10:47:27 +09:00
|
|
|
const NATIVE_LOOP_TRIGGERED_FLAG = "__omoNativeLoopTriggered"
|
|
|
|
|
|
2026-03-31 20:02:00 -07:00
|
|
|
function hasPartsOutput(value: unknown): value is CommandExecuteBeforeOutput {
|
|
|
|
|
if (typeof value !== "object" || value === null) return false
|
|
|
|
|
const record = value as Record<string, unknown>
|
|
|
|
|
const parts = record["parts"]
|
|
|
|
|
return Array.isArray(parts)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createCommandExecuteBeforeHandler(args: {
|
|
|
|
|
hooks: CreatedHooks
|
|
|
|
|
}): (
|
|
|
|
|
input: CommandExecuteBeforeInput,
|
|
|
|
|
output: CommandExecuteBeforeOutput,
|
|
|
|
|
) => Promise<void> {
|
|
|
|
|
const { hooks } = args
|
|
|
|
|
|
|
|
|
|
return async (input, output): Promise<void> => {
|
|
|
|
|
await hooks.autoSlashCommand?.["command.execute.before"]?.(input, output)
|
|
|
|
|
|
2026-04-10 10:47:27 +09:00
|
|
|
const normalizedCommand = input.command.toLowerCase()
|
|
|
|
|
const sessionID = input.sessionID
|
|
|
|
|
if (hooks.ralphLoop && sessionID) {
|
|
|
|
|
if (normalizedCommand === "ralph-loop" || normalizedCommand === "ulw-loop") {
|
|
|
|
|
const parsedArguments = parseRalphLoopArguments(input.arguments || "")
|
|
|
|
|
hooks.ralphLoop.startLoop(sessionID, parsedArguments.prompt, {
|
|
|
|
|
ultrawork: normalizedCommand === "ulw-loop",
|
|
|
|
|
maxIterations: parsedArguments.maxIterations,
|
|
|
|
|
completionPromise: parsedArguments.completionPromise,
|
|
|
|
|
strategy: parsedArguments.strategy,
|
|
|
|
|
})
|
|
|
|
|
output.message ??= {}
|
|
|
|
|
output.message[NATIVE_LOOP_TRIGGERED_FLAG] = true
|
2026-04-10 18:47:18 +09:00
|
|
|
if (hooks.stopContinuationGuard?.isStopped(sessionID)) {
|
|
|
|
|
hooks.stopContinuationGuard.clear(sessionID)
|
|
|
|
|
log("[stop-continuation] Stop state cleared by native command", {
|
|
|
|
|
sessionID,
|
|
|
|
|
command: normalizedCommand,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-04-10 10:47:27 +09:00
|
|
|
} else if (normalizedCommand === "cancel-ralph") {
|
|
|
|
|
hooks.ralphLoop.cancelLoop(sessionID)
|
|
|
|
|
output.message ??= {}
|
|
|
|
|
output.message[NATIVE_LOOP_TRIGGERED_FLAG] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 20:02:00 -07:00
|
|
|
if (
|
|
|
|
|
hooks.startWork
|
2026-04-10 10:47:27 +09:00
|
|
|
&& normalizedCommand === "start-work"
|
2026-03-31 20:02:00 -07:00
|
|
|
&& hasPartsOutput(output)
|
|
|
|
|
) {
|
|
|
|
|
await hooks.startWork["command.execute.before"]?.(input, output)
|
2026-04-10 18:47:18 +09:00
|
|
|
if (hooks.stopContinuationGuard?.isStopped(sessionID)) {
|
|
|
|
|
hooks.stopContinuationGuard.clear(sessionID)
|
|
|
|
|
log("[stop-continuation] Stop state cleared by native command", {
|
|
|
|
|
sessionID,
|
|
|
|
|
command: normalizedCommand,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-03-31 20:02:00 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-10 10:47:27 +09:00
|
|
|
|
|
|
|
|
export { NATIVE_LOOP_TRIGGERED_FLAG }
|