From 24f10b0d092024d191630d3b3f254588ac209516 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Sun, 19 Apr 2026 19:16:31 -0400 Subject: [PATCH] need dist for plugin import Signed-off-by: Dmytro Stanchiev --- .gitignore | 1 - dist/index.d.ts | 3 + dist/index.js | 1036 +++++++++++++++++ dist/ralph-loop/claude-config-dir.d.ts | 1 + dist/ralph-loop/command-arguments.d.ts | 8 + dist/ralph-loop/commands.d.ts | 3 + .../completion-promise-detector.d.ts | 8 + dist/ralph-loop/config.d.ts | 8 + dist/ralph-loop/constants.d.ts | 4 + .../continuation-prompt-builder.d.ts | 2 + .../continuation-prompt-injector.d.ts | 8 + .../ralph-loop/internal-initiator-marker.d.ts | 5 + dist/ralph-loop/iteration-continuation.d.ts | 12 + dist/ralph-loop/logger.d.ts | 2 + dist/ralph-loop/loop-session-recovery.d.ts | 7 + dist/ralph-loop/loop-state-controller.d.ts | 18 + dist/ralph-loop/normalize-sdk-response.d.ts | 3 + dist/ralph-loop/prompt-tools.d.ts | 2 + dist/ralph-loop/ralph-loop-event-handler.d.ts | 28 + dist/ralph-loop/ralph-loop-hook.d.ts | 19 + dist/ralph-loop/session-reset-strategy.d.ts | 3 + dist/ralph-loop/simple-frontmatter.d.ts | 4 + dist/ralph-loop/storage.d.ts | 6 + dist/ralph-loop/system-directive.d.ts | 1 + dist/ralph-loop/transcript.d.ts | 1 + dist/ralph-loop/types.d.ts | 18 + dist/ralph-loop/with-timeout.d.ts | 1 + 27 files changed, 1211 insertions(+), 1 deletion(-) create mode 100644 dist/index.d.ts create mode 100644 dist/index.js create mode 100644 dist/ralph-loop/claude-config-dir.d.ts create mode 100644 dist/ralph-loop/command-arguments.d.ts create mode 100644 dist/ralph-loop/commands.d.ts create mode 100644 dist/ralph-loop/completion-promise-detector.d.ts create mode 100644 dist/ralph-loop/config.d.ts create mode 100644 dist/ralph-loop/constants.d.ts create mode 100644 dist/ralph-loop/continuation-prompt-builder.d.ts create mode 100644 dist/ralph-loop/continuation-prompt-injector.d.ts create mode 100644 dist/ralph-loop/internal-initiator-marker.d.ts create mode 100644 dist/ralph-loop/iteration-continuation.d.ts create mode 100644 dist/ralph-loop/logger.d.ts create mode 100644 dist/ralph-loop/loop-session-recovery.d.ts create mode 100644 dist/ralph-loop/loop-state-controller.d.ts create mode 100644 dist/ralph-loop/normalize-sdk-response.d.ts create mode 100644 dist/ralph-loop/prompt-tools.d.ts create mode 100644 dist/ralph-loop/ralph-loop-event-handler.d.ts create mode 100644 dist/ralph-loop/ralph-loop-hook.d.ts create mode 100644 dist/ralph-loop/session-reset-strategy.d.ts create mode 100644 dist/ralph-loop/simple-frontmatter.d.ts create mode 100644 dist/ralph-loop/storage.d.ts create mode 100644 dist/ralph-loop/system-directive.d.ts create mode 100644 dist/ralph-loop/transcript.d.ts create mode 100644 dist/ralph-loop/types.d.ts create mode 100644 dist/ralph-loop/with-timeout.d.ts diff --git a/.gitignore b/.gitignore index ba34fb6..2752eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ -dist/ node_modules/ .DS_Store diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..61ee373 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,3 @@ +import type { Plugin } from "@opencode-ai/plugin"; +declare const RalphLoopPlugin: Plugin; +export default RalphLoopPlugin; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..9203a53 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,1036 @@ +// @bun +// src/ralph-loop/config.ts +import { existsSync, readFileSync } from "fs"; +import { homedir } from "os"; +import { join } from "path"; +function resolveOpenCodeConfigDir() { + const xdg = process.env.XDG_CONFIG_HOME; + if (xdg && xdg.length > 0) { + return join(xdg, "opencode"); + } + return join(homedir(), ".config", "opencode"); +} +function stripJsonComments(input) { + let out = ""; + let i = 0; + let inString = null; + while (i < input.length) { + const ch = input[i]; + const next = i + 1 < input.length ? input[i + 1] : ""; + if (inString) { + out += ch; + if (ch === "\\") { + if (i + 1 < input.length) { + out += input[i + 1]; + i += 2; + continue; + } + } + if (ch === inString) { + inString = null; + } + i++; + continue; + } + if (ch === '"' || ch === "'") { + inString = ch; + out += ch; + i++; + continue; + } + if (ch === "/" && next === "/") { + i += 2; + while (i < input.length && input[i] !== ` +`) + i++; + continue; + } + if (ch === "/" && next === "*") { + i += 2; + while (i < input.length) { + if (input[i] === "*" && i + 1 < input.length && input[i + 1] === "/") { + i += 2; + break; + } + i++; + } + continue; + } + out += ch; + i++; + } + return out; +} +function tryReadConfig(path) { + if (!existsSync(path)) + return null; + try { + const raw = readFileSync(path, "utf-8"); + const parsed = JSON.parse(stripJsonComments(raw)); + if (!parsed || typeof parsed !== "object") + return null; + return parsed; + } catch { + return null; + } +} +function loadRalphLoopConfig(projectDir) { + const userDir = resolveOpenCodeConfigDir(); + const user = tryReadConfig(join(userDir, "ralph-loop.jsonc")) ?? tryReadConfig(join(userDir, "ralph-loop.json")); + const project = tryReadConfig(join(projectDir, ".opencode", "ralph-loop.jsonc")) ?? tryReadConfig(join(projectDir, ".opencode", "ralph-loop.json")); + return { + ...user ?? {}, + ...project ?? {} + }; +} + +// src/ralph-loop/commands.ts +var RALPH_LOOP_TEMPLATE = `You are starting a Ralph Loop - a self-referential development loop that runs until task completion. + +## How Ralph Loop Works + +1. You will work on the task continuously +2. When you believe the task is FULLY complete, output: \`{{COMPLETION_PROMISE}}\` +3. If you don't output the promise, the loop will automatically inject another prompt to continue +4. Maximum iterations: Configurable (default 100) + +## Rules + +- Focus on completing the task fully, not partially +- Don't output the completion promise until the task is truly done +- Each iteration should make meaningful progress toward the goal +- If stuck, try different approaches +- Use todos to track your progress + +## Exit Conditions + +1. **Completion**: Output your completion promise tag when fully complete +2. **Max Iterations**: Loop stops automatically at limit +3. **Cancel**: User runs \`/cancel-ralph\` command + +## Your Task + +Parse the arguments below and begin working on the task. The format is: +\`"task description" [--completion-promise=TEXT] [--max-iterations=N] [--strategy=reset|continue]\` + +Default completion promise is "DONE" and default max iterations is 100.`; +var CANCEL_RALPH_TEMPLATE = `Cancel the currently active Ralph Loop. + +This will: +1. Stop the loop from continuing +2. Clear the loop state file +3. Allow the session to end normally + +Check if a loop is active and cancel it. Inform the user of the result.`; +var RALPH_LOOP_COMMANDS = { + "ralph-loop": { + name: "ralph-loop", + description: "(builtin) Start self-referential development loop until completion", + template: ` +${RALPH_LOOP_TEMPLATE} + + + +$ARGUMENTS +` + }, + "ulw-loop": { + name: "ulw-loop", + description: "(builtin) Start ultrawork loop - continues until completion with ultrawork mode", + template: ` +${RALPH_LOOP_TEMPLATE} + + + +$ARGUMENTS +` + }, + "cancel-ralph": { + name: "cancel-ralph", + description: "(builtin) Cancel active Ralph Loop", + template: ` +${CANCEL_RALPH_TEMPLATE} +` + } +}; + +// src/ralph-loop/command-arguments.ts +var DEFAULT_PROMPT = "Complete the task as instructed"; +function parseRalphLoopArguments(rawArguments) { + const taskMatch = rawArguments.match(/^("|')(.+?)\1/); + const promptCandidate = taskMatch?.[2] ?? (rawArguments.startsWith("--") ? "" : rawArguments.split(/\s+--/)[0]?.trim() ?? ""); + const prompt = promptCandidate || DEFAULT_PROMPT; + const maxIterationMatch = rawArguments.match(/--max-iterations=(\d+)/i); + const maxIterationsRaw = maxIterationMatch?.[1]; + const completionPromiseQuoted = rawArguments.match(/--completion-promise=("|')(.+?)\1/i); + const completionPromiseUnquoted = rawArguments.match(/--completion-promise=([^\s\"']+)/i); + const completionPromise = completionPromiseQuoted?.[2] ?? completionPromiseUnquoted?.[1]; + const strategyMatch = rawArguments.match(/--strategy=(reset|continue)/i); + const strategyValue = strategyMatch?.[1]?.toLowerCase(); + return { + prompt, + maxIterations: maxIterationsRaw ? Number.parseInt(maxIterationsRaw, 10) : undefined, + completionPromise, + strategy: strategyValue === "reset" || strategyValue === "continue" ? strategyValue : undefined + }; +} + +// src/ralph-loop/transcript.ts +import { join as join3 } from "path"; + +// src/ralph-loop/claude-config-dir.ts +import { homedir as homedir2 } from "os"; +import { join as join2 } from "path"; +function getClaudeConfigDir() { + const envConfigDir = process.env.CLAUDE_CONFIG_DIR; + if (envConfigDir) + return envConfigDir; + return join2(homedir2(), ".claude"); +} + +// src/ralph-loop/transcript.ts +var TRANSCRIPT_DIR = join3(getClaudeConfigDir(), "transcripts"); +function getTranscriptPath(sessionId) { + return join3(TRANSCRIPT_DIR, `${sessionId}.jsonl`); +} + +// src/ralph-loop/loop-session-recovery.ts +function createLoopSessionRecovery(options) { + const recoveryWindowMs = options?.recoveryWindowMs ?? 5000; + const sessions = new Map; + function getSessionState(sessionID) { + let state = sessions.get(sessionID); + if (!state) { + state = {}; + sessions.set(sessionID, state); + } + return state; + } + return { + isRecovering(sessionID) { + return getSessionState(sessionID).isRecovering === true; + }, + markRecovering(sessionID) { + const state = getSessionState(sessionID); + state.isRecovering = true; + setTimeout(() => { + state.isRecovering = false; + }, recoveryWindowMs); + }, + clear(sessionID) { + sessions.delete(sessionID); + } + }; +} + +// src/ralph-loop/constants.ts +var HOOK_NAME = "ralph-loop"; +var DEFAULT_STATE_FILE = ".sisyphus/ralph-loop.local.md"; +var DEFAULT_MAX_ITERATIONS = 100; +var DEFAULT_COMPLETION_PROMISE = "DONE"; + +// src/ralph-loop/storage.ts +import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs"; +import { dirname, join as join4 } from "path"; + +// src/ralph-loop/simple-frontmatter.ts +function parseFrontmatter(content) { + const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/; + const match = content.match(frontmatterRegex); + if (!match) { + return { data: {}, body: content }; + } + const yaml = match[1] ?? ""; + const body = match[2] ?? ""; + const data = {}; + for (const rawLine of yaml.split(/\r?\n/)) { + const line = rawLine.trim(); + if (!line) + continue; + const m = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/); + if (!m) + continue; + const key = m[1]; + if (!key) + continue; + let value = m[2] ?? ""; + if (typeof value === "string") { + const v = value.trim(); + if (v === "true") + value = true; + else if (v === "false") + value = false; + else if (/^-?\d+(\.\d+)?$/.test(v)) + value = Number(v); + else + value = v; + } + data[key] = value; + } + return { data, body }; +} + +// src/ralph-loop/storage.ts +function getStateFilePath(directory, customPath) { + return customPath ? join4(directory, customPath) : join4(directory, DEFAULT_STATE_FILE); +} +function readState(directory, customPath) { + const filePath = getStateFilePath(directory, customPath); + if (!existsSync2(filePath)) + return null; + try { + const content = readFileSync2(filePath, "utf-8"); + const { data, body } = parseFrontmatter(content); + const active = data.active; + const iteration = data.iteration; + if (active === undefined || iteration === undefined) + return null; + const isActive = active === true || active === "true"; + const iterationNum = typeof iteration === "number" ? iteration : Number(iteration); + if (Number.isNaN(iterationNum)) + return null; + const stripQuotes = (val) => { + const str = String(val ?? ""); + return str.replace(/^["']|["']$/g, ""); + }; + return { + active: isActive, + iteration: iterationNum, + max_iterations: Number(data.max_iterations) || DEFAULT_MAX_ITERATIONS, + completion_promise: stripQuotes(data.completion_promise) || DEFAULT_COMPLETION_PROMISE, + started_at: stripQuotes(data.started_at) || new Date().toISOString(), + prompt: body.trim(), + session_id: data.session_id ? stripQuotes(data.session_id) : undefined, + ultrawork: data.ultrawork === true || data.ultrawork === "true" ? true : undefined, + strategy: data.strategy === "reset" || data.strategy === "continue" ? data.strategy : undefined + }; + } catch { + return null; + } +} +function writeState(directory, state, customPath) { + const filePath = getStateFilePath(directory, customPath); + try { + const dir = dirname(filePath); + if (!existsSync2(dir)) { + mkdirSync(dir, { recursive: true }); + } + const sessionIdLine = state.session_id ? `session_id: "${state.session_id}" +` : ""; + const ultraworkLine = state.ultrawork !== undefined ? `ultrawork: ${state.ultrawork} +` : ""; + const strategyLine = state.strategy ? `strategy: "${state.strategy}" +` : ""; + const content = `--- +active: ${state.active} +iteration: ${state.iteration} +max_iterations: ${state.max_iterations} +completion_promise: "${state.completion_promise}" +started_at: "${state.started_at}" +${sessionIdLine}${ultraworkLine}${strategyLine}--- +${state.prompt} +`; + writeFileSync(filePath, content, "utf-8"); + return true; + } catch { + return false; + } +} +function clearState(directory, customPath) { + const filePath = getStateFilePath(directory, customPath); + try { + if (existsSync2(filePath)) { + unlinkSync(filePath); + } + return true; + } catch { + return false; + } +} +function incrementIteration(directory, customPath) { + const state = readState(directory, customPath); + if (!state) + return null; + state.iteration += 1; + return writeState(directory, state, customPath) ? state : null; +} + +// src/ralph-loop/logger.ts +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +var logFile = path.join(os.tmpdir(), "opencode-plugin-ralph-loop.log"); +function shouldLogToConsole() { + const raw = process.env.RALPH_LOOP_DEBUG; + if (!raw) + return false; + return raw === "1" || raw.toLowerCase() === "true" || raw.toLowerCase() === "yes"; +} +function log(message, meta) { + try { + const timestamp = new Date().toISOString(); + const entry = `[${timestamp}] ${message}${meta !== undefined ? ` ${JSON.stringify(meta)}` : ""} +`; + fs.appendFileSync(logFile, entry); + } catch {} + if (shouldLogToConsole()) { + try { + console.log(message, meta); + } catch { + console.log(message); + } + } +} + +// src/ralph-loop/loop-state-controller.ts +function createLoopStateController(options) { + const directory = options.directory; + const stateDir = options.stateDir; + const config = options.config; + return { + startLoop(sessionID, prompt, loopOptions) { + const state = { + active: true, + iteration: 1, + max_iterations: loopOptions?.maxIterations ?? config?.default_max_iterations ?? DEFAULT_MAX_ITERATIONS, + completion_promise: loopOptions?.completionPromise ?? DEFAULT_COMPLETION_PROMISE, + ultrawork: loopOptions?.ultrawork, + strategy: loopOptions?.strategy ?? config?.default_strategy ?? "continue", + started_at: new Date().toISOString(), + prompt, + session_id: sessionID + }; + const success = writeState(directory, state, stateDir); + if (success) { + log(`[${HOOK_NAME}] Loop started`, { + sessionID, + maxIterations: state.max_iterations, + completionPromise: state.completion_promise + }); + } + return success; + }, + cancelLoop(sessionID) { + const state = readState(directory, stateDir); + if (!state || state.session_id !== sessionID) { + return false; + } + const success = clearState(directory, stateDir); + if (success) { + log(`[${HOOK_NAME}] Loop cancelled`, { sessionID, iteration: state.iteration }); + } + return success; + }, + getState() { + return readState(directory, stateDir); + }, + clear() { + return clearState(directory, stateDir); + }, + incrementIteration() { + return incrementIteration(directory, stateDir); + }, + setSessionID(sessionID) { + const state = readState(directory, stateDir); + if (!state) + return null; + state.session_id = sessionID; + if (!writeState(directory, state, stateDir)) + return null; + return state; + } + }; +} + +// src/ralph-loop/completion-promise-detector.ts +import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs"; + +// src/ralph-loop/with-timeout.ts +async function withTimeout(promise, timeoutMs) { + let timeoutId; + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject(new Error("API timeout")); + }, timeoutMs); + }); + try { + return await Promise.race([promise, timeoutPromise]); + } finally { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + } +} + +// src/ralph-loop/completion-promise-detector.ts +function isRecord(value) { + return typeof value === "object" && value !== null; +} +function escapeRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} +function buildPromisePattern(promise) { + return new RegExp(`\\s*${escapeRegex(promise)}\\s*`, "is"); +} +function detectCompletionInTranscript(transcriptPath, promise) { + if (!transcriptPath) + return false; + try { + if (!existsSync3(transcriptPath)) + return false; + const content = readFileSync3(transcriptPath, "utf-8"); + const pattern = buildPromisePattern(promise); + const lines = content.split(` +`).filter((line) => line.trim()); + for (const line of lines) { + try { + const entry = JSON.parse(line); + if (entry.type !== "user" && pattern.test(line)) + return true; + } catch {} + } + return false; + } catch { + return false; + } +} +async function detectCompletionInSessionMessages(ctx, options) { + try { + const sessionApi = ctx.client.session; + if (typeof sessionApi.messages !== "function") + return false; + const response = await withTimeout(sessionApi.messages({ + path: { id: options.sessionID }, + query: { directory: options.directory } + }), options.apiTimeoutMs); + const messageArray = Array.isArray(response) ? response : isRecord(response) && Array.isArray(response.data) ? response.data : isRecord(response) && Array.isArray(response["200"]) ? response["200"] : []; + const assistantMessages = messageArray.filter((msg) => msg.info?.role === "assistant"); + if (assistantMessages.length === 0) + return false; + const pattern = buildPromisePattern(options.promise); + const recentAssistants = assistantMessages.slice(-3); + for (const assistant of recentAssistants) { + if (!assistant.parts) + continue; + let responseText = ""; + for (const part of assistant.parts) { + if (part.type !== "text") + continue; + responseText += `${responseText ? ` +` : ""}${part.text ?? ""}`; + } + if (pattern.test(responseText)) { + return true; + } + } + return false; + } catch (err) { + setTimeout(() => { + log(`[${HOOK_NAME}] Session messages check failed`, { + sessionID: options.sessionID, + error: String(err) + }); + }, 0); + return false; + } +} + +// src/ralph-loop/system-directive.ts +var SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: OH-MY-OPENCODE"; + +// src/ralph-loop/continuation-prompt-builder.ts +var CONTINUATION_PROMPT = `${SYSTEM_DIRECTIVE_PREFIX} - RALPH LOOP {{ITERATION}}/{{MAX}}] + +Your previous attempt did not output the completion promise. Continue working on the task. + +IMPORTANT: +- Review your progress so far +- Continue from where you left off +- When FULLY complete, output: {{PROMISE}} +- Do not stop until the task is truly done + +Original task: +{{PROMPT}}`; +function buildContinuationPrompt(state) { + const continuationPrompt = CONTINUATION_PROMPT.replace("{{ITERATION}}", String(state.iteration)).replace("{{MAX}}", String(state.max_iterations)).replace("{{PROMISE}}", state.completion_promise).replace("{{PROMPT}}", state.prompt); + return state.ultrawork ? `ultrawork ${continuationPrompt}` : continuationPrompt; +} + +// src/ralph-loop/internal-initiator-marker.ts +var OMO_INTERNAL_INITIATOR_MARKER = ""; +function createInternalAgentTextPart(text) { + return { + type: "text", + text: `${text} +${OMO_INTERNAL_INITIATOR_MARKER}` + }; +} + +// src/ralph-loop/normalize-sdk-response.ts +function normalizeSDKResponse(response, fallback, options) { + const prefer = options?.preferResponseOnMissingData ?? false; + if (response && typeof response === "object") { + const rec = response; + if ("data" in rec) { + const d = rec.data; + if (d !== undefined) + return d; + } + if ("200" in rec) { + const d = rec["200"]; + if (d !== undefined) + return d; + } + } + if (prefer && response !== undefined) { + return response; + } + return fallback; +} + +// src/ralph-loop/prompt-tools.ts +function normalizePromptTools(tools) { + if (!tools) + return; + const normalized = {}; + for (const [toolName, permission] of Object.entries(tools)) { + if (permission === false || permission === "deny") { + normalized[toolName] = false; + continue; + } + if (permission === true || permission === "allow" || permission === "ask") { + normalized[toolName] = true; + } + } + return Object.keys(normalized).length > 0 ? normalized : undefined; +} + +// src/ralph-loop/continuation-prompt-injector.ts +function isRecord2(value) { + return typeof value === "object" && value !== null; +} +function getPromptApi(client) { + const clientRecord = client; + if (!isRecord2(clientRecord)) + return null; + const sessionValue = clientRecord.session; + if (!isRecord2(sessionValue)) + return null; + const promptAsyncValue = sessionValue.promptAsync; + if (typeof promptAsyncValue === "function") { + return (args) => Reflect.apply(promptAsyncValue, sessionValue, [args]); + } + const promptValue = sessionValue.prompt; + if (typeof promptValue === "function") { + return (args) => Reflect.apply(promptValue, sessionValue, [args]); + } + return null; +} +async function injectContinuationPrompt(ctx, options) { + let agent; + let model; + let tools; + const sourceSessionID = options.inheritFromSessionID ?? options.sessionID; + try { + const sessionApi = ctx.client.session; + if (typeof sessionApi.messages !== "function") { + throw new Error("OpenCode client missing session.messages"); + } + const messagesResp = await withTimeout(sessionApi.messages({ + path: { id: sourceSessionID }, + query: { directory: options.directory } + }), options.apiTimeoutMs); + const messages = normalizeSDKResponse(messagesResp, [], { + preferResponseOnMissingData: true + }); + for (let i = messages.length - 1;i >= 0; i--) { + const info = messages[i]?.info; + if (info?.agent || info?.model || info?.modelID && info?.providerID) { + agent = info.agent; + model = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined); + tools = info.tools; + break; + } + } + } catch {} + const inheritedTools = normalizePromptTools(tools); + const promptApi = getPromptApi(ctx.client); + if (!promptApi) { + throw new Error("OpenCode client missing session.prompt/promptAsync"); + } + await promptApi({ + path: { id: options.sessionID }, + body: { + ...agent !== undefined ? { agent } : {}, + ...model !== undefined ? { model } : {}, + ...inheritedTools ? { tools: inheritedTools } : {}, + parts: [createInternalAgentTextPart(options.prompt)] + }, + query: { directory: options.directory } + }); + log("[ralph-loop] continuation injected", { sessionID: options.sessionID }); +} + +// src/ralph-loop/session-reset-strategy.ts +function isRecord3(value) { + return typeof value === "object" && value !== null; +} +async function createIterationSession(ctx, parentSessionID, directory) { + const sessionApi = ctx.client.session; + if (typeof sessionApi.create !== "function") + return null; + const createResult = await sessionApi.create({ + body: { parentID: parentSessionID, title: "Ralph Loop Iteration" }, + query: { directory } + }); + const data = isRecord3(createResult) ? createResult.data : undefined; + const id = isRecord3(data) ? data.id : undefined; + const error = isRecord3(createResult) ? createResult.error : undefined; + if (error !== undefined || typeof id !== "string" || id.length === 0) { + log("[ralph-loop] Failed to create iteration session", { + parentSessionID, + error: String(error ?? "No session ID returned") + }); + return null; + } + return id; +} +async function selectSessionInTui(client, sessionID) { + const selectSession = getSelectSessionApi(client); + if (!selectSession) + return false; + try { + await selectSession({ body: { sessionID } }); + return true; + } catch (error) { + log("[ralph-loop] Failed to select session in TUI", { + sessionID, + error: String(error) + }); + return false; + } +} +function getSelectSessionApi(client) { + if (!isRecord3(client)) + return null; + const tuiValue = client.tui; + if (!isRecord3(tuiValue)) + return null; + const selectSessionValue = tuiValue.selectSession; + if (typeof selectSessionValue !== "function") + return null; + return (args) => Reflect.apply(selectSessionValue, tuiValue, [args]); +} + +// src/ralph-loop/iteration-continuation.ts +async function continueIteration(ctx, state, options) { + const strategy = state.strategy ?? "continue"; + const continuationPrompt = buildContinuationPrompt(state); + if (strategy === "reset") { + const newSessionID = await createIterationSession(ctx, options.previousSessionID, options.directory); + if (!newSessionID) + return; + await injectContinuationPrompt(ctx, { + sessionID: newSessionID, + inheritFromSessionID: options.previousSessionID, + prompt: continuationPrompt, + directory: options.directory, + apiTimeoutMs: options.apiTimeoutMs + }); + await selectSessionInTui(ctx.client, newSessionID); + const boundState = options.loopState.setSessionID(newSessionID); + if (!boundState) { + log(`[${HOOK_NAME}] Failed to bind loop state to new session`, { + previousSessionID: options.previousSessionID, + newSessionID + }); + return; + } + return; + } + await injectContinuationPrompt(ctx, { + sessionID: options.previousSessionID, + prompt: continuationPrompt, + directory: options.directory, + apiTimeoutMs: options.apiTimeoutMs + }); +} + +// src/ralph-loop/ralph-loop-event-handler.ts +function isRecord4(value) { + return typeof value === "object" && value !== null; +} +function getShowToastApi(client) { + if (!isRecord4(client)) + return null; + const tui = client.tui; + if (!isRecord4(tui)) + return null; + const showToast = tui.showToast; + if (typeof showToast !== "function") + return null; + return (args) => Reflect.apply(showToast, tui, [args]); +} +function createRalphLoopEventHandler(ctx, options) { + return async ({ event }) => { + const props = event.properties; + const showToast = getShowToastApi(ctx.client); + if (event.type === "session.idle") { + const sessionID = props?.sessionID; + if (!sessionID) + return; + if (options.sessionRecovery.isRecovering(sessionID)) { + log(`[${HOOK_NAME}] Skipped: in recovery`, { sessionID }); + return; + } + const state = options.loopState.getState(); + if (!state || !state.active) + return; + if (state.session_id && state.session_id !== sessionID) { + if (options.checkSessionExists) { + try { + const exists = await options.checkSessionExists(state.session_id); + if (!exists) { + options.loopState.clear(); + log(`[${HOOK_NAME}] Cleared orphaned state from deleted session`, { + orphanedSessionId: state.session_id, + currentSessionId: sessionID + }); + return; + } + } catch (err) { + log(`[${HOOK_NAME}] Failed to check session existence`, { + sessionId: state.session_id, + error: String(err) + }); + } + } + return; + } + const transcriptPath = options.getTranscriptPath(sessionID); + const completionViaTranscript = detectCompletionInTranscript(transcriptPath, state.completion_promise); + const completionViaApi = completionViaTranscript ? false : await detectCompletionInSessionMessages(ctx, { + sessionID, + promise: state.completion_promise, + apiTimeoutMs: options.apiTimeoutMs, + directory: options.directory + }); + if (completionViaTranscript || completionViaApi) { + log(`[${HOOK_NAME}] Completion detected!`, { + sessionID, + iteration: state.iteration, + promise: state.completion_promise, + detectedVia: completionViaTranscript ? "transcript_file" : "session_messages_api" + }); + options.loopState.clear(); + const title = state.ultrawork ? "ULTRAWORK LOOP COMPLETE!" : "Ralph Loop Complete!"; + const message = state.ultrawork ? `JUST ULW ULW! Task completed after ${state.iteration} iteration(s)` : `Task completed after ${state.iteration} iteration(s)`; + await showToast?.({ body: { title, message, variant: "success", duration: 5000 } }).catch(() => {}); + return; + } + if (state.iteration >= state.max_iterations) { + log(`[${HOOK_NAME}] Max iterations reached`, { + sessionID, + iteration: state.iteration, + max: state.max_iterations + }); + options.loopState.clear(); + await showToast?.({ + body: { + title: "Ralph Loop Stopped", + message: `Max iterations (${state.max_iterations}) reached without completion`, + variant: "warning", + duration: 5000 + } + }).catch(() => {}); + return; + } + const newState = options.loopState.incrementIteration(); + if (!newState) { + log(`[${HOOK_NAME}] Failed to increment iteration`, { sessionID }); + return; + } + log(`[${HOOK_NAME}] Continuing loop`, { + sessionID, + iteration: newState.iteration, + max: newState.max_iterations + }); + await showToast?.({ + body: { + title: "Ralph Loop", + message: `Iteration ${newState.iteration}/${newState.max_iterations}`, + variant: "info", + duration: 2000 + } + }).catch(() => {}); + try { + await continueIteration(ctx, newState, { + previousSessionID: sessionID, + directory: options.directory, + apiTimeoutMs: options.apiTimeoutMs, + loopState: options.loopState + }); + } catch (err) { + log(`[${HOOK_NAME}] Failed to inject continuation`, { + sessionID, + error: String(err) + }); + } + return; + } + if (event.type === "session.deleted") { + const sessionInfo = props?.info; + if (!sessionInfo?.id) + return; + const state = options.loopState.getState(); + if (state?.session_id === sessionInfo.id) { + options.loopState.clear(); + log(`[${HOOK_NAME}] Session deleted, loop cleared`, { sessionID: sessionInfo.id }); + } + options.sessionRecovery.clear(sessionInfo.id); + return; + } + if (event.type === "session.error") { + const sessionID = props?.sessionID; + const error = props?.error; + if (error?.name === "MessageAbortedError") { + if (sessionID) { + const state = options.loopState.getState(); + if (state?.session_id === sessionID) { + options.loopState.clear(); + log(`[${HOOK_NAME}] User aborted, loop cleared`, { sessionID }); + } + options.sessionRecovery.clear(sessionID); + } + return; + } + if (sessionID) { + options.sessionRecovery.markRecovering(sessionID); + } + } + }; +} + +// src/ralph-loop/ralph-loop-hook.ts +var DEFAULT_API_TIMEOUT = 5000; +function createRalphLoopHook(ctx, options) { + const config = options?.config; + const stateDir = config?.state_dir; + const getTranscriptPath2 = options?.getTranscriptPath ?? ((id) => getTranscriptPath(id)); + const apiTimeout = options?.apiTimeout ?? DEFAULT_API_TIMEOUT; + const checkSessionExists = options?.checkSessionExists; + const loopState = createLoopStateController({ + directory: ctx.directory, + stateDir, + config + }); + const sessionRecovery = createLoopSessionRecovery(); + const event = createRalphLoopEventHandler(ctx, { + directory: ctx.directory, + apiTimeoutMs: apiTimeout, + getTranscriptPath: getTranscriptPath2, + checkSessionExists, + sessionRecovery, + loopState + }); + return { + event, + startLoop: loopState.startLoop, + cancelLoop: loopState.cancelLoop, + getState: loopState.getState + }; +} + +// src/index.ts +function isRecord5(value) { + return typeof value === "object" && value !== null; +} +var RalphLoopPlugin = async (ctx) => { + const ralphConfig = loadRalphLoopConfig(ctx.directory); + const ralphLoop = createRalphLoopHook(ctx, { + config: ralphConfig, + checkSessionExists: async (sessionID) => { + try { + const sessionApi = ctx.client.session; + if (typeof sessionApi.get !== "function") + return false; + const response = await sessionApi.get({ + path: { id: sessionID }, + query: { directory: ctx.directory } + }); + const data = isRecord5(response) && "data" in response ? response.data : isRecord5(response) && ("200" in response) ? response["200"] : response; + const id = isRecord5(data) ? data.id : undefined; + return typeof id === "string" && id.length > 0; + } catch { + return false; + } + } + }); + return { + config: async (config) => { + const existing = config.command ?? {}; + config.command = { ...RALPH_LOOP_COMMANDS, ...existing }; + }, + "tool.execute.before": async (input, output) => { + if (!ralphLoop) + return; + if (input.tool !== "skill") + return; + const rawName = typeof output.args.name === "string" ? output.args.name : undefined; + if (!rawName) + return; + const command = rawName.replace(/^\//, "").toLowerCase(); + const sessionID = input.sessionID; + if (!sessionID) + return; + if (command.startsWith("ralph-loop")) { + const rawArgs = rawName.replace(/^\/?(ralph-loop)\s*/i, ""); + const parsed = parseRalphLoopArguments(rawArgs); + ralphLoop.startLoop(sessionID, parsed.prompt, { + maxIterations: parsed.maxIterations, + completionPromise: parsed.completionPromise, + strategy: parsed.strategy + }); + } else if (command.startsWith("ulw-loop")) { + const rawArgs = rawName.replace(/^\/?(ulw-loop)\s*/i, ""); + const parsed = parseRalphLoopArguments(rawArgs); + ralphLoop.startLoop(sessionID, parsed.prompt, { + ultrawork: true, + maxIterations: parsed.maxIterations, + completionPromise: parsed.completionPromise, + strategy: parsed.strategy + }); + } else if (command.startsWith("cancel-ralph")) { + ralphLoop.cancelLoop(sessionID); + } + }, + "chat.message": async (input, output) => { + if (!ralphLoop) + return; + const parts = output.parts; + const promptText = parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(` +`).trim() || ""; + const isRalphLoopTemplate = promptText.includes("You are starting a Ralph Loop") && promptText.includes(""); + const isCancelRalphTemplate = promptText.includes("Cancel the currently active Ralph Loop"); + if (isRalphLoopTemplate) { + const taskMatch = promptText.match(/\s*([\s\S]*?)\s*<\/user-task>/i); + const rawTask = taskMatch?.[1]?.trim() || ""; + const parsed = parseRalphLoopArguments(rawTask); + ralphLoop.startLoop(input.sessionID, parsed.prompt, { + maxIterations: parsed.maxIterations, + completionPromise: parsed.completionPromise, + strategy: parsed.strategy + }); + } else if (isCancelRalphTemplate) { + ralphLoop.cancelLoop(input.sessionID); + } + }, + event: async (input) => { + await ralphLoop.event(input); + } + }; +}; +var src_default = RalphLoopPlugin; +export { + src_default as default +}; diff --git a/dist/ralph-loop/claude-config-dir.d.ts b/dist/ralph-loop/claude-config-dir.d.ts new file mode 100644 index 0000000..d44e148 --- /dev/null +++ b/dist/ralph-loop/claude-config-dir.d.ts @@ -0,0 +1 @@ +export declare function getClaudeConfigDir(): string; diff --git a/dist/ralph-loop/command-arguments.d.ts b/dist/ralph-loop/command-arguments.d.ts new file mode 100644 index 0000000..8c0b542 --- /dev/null +++ b/dist/ralph-loop/command-arguments.d.ts @@ -0,0 +1,8 @@ +export type RalphLoopStrategy = "reset" | "continue"; +export type ParsedRalphLoopArguments = { + prompt: string; + maxIterations?: number; + completionPromise?: string; + strategy?: RalphLoopStrategy; +}; +export declare function parseRalphLoopArguments(rawArguments: string): ParsedRalphLoopArguments; diff --git a/dist/ralph-loop/commands.d.ts b/dist/ralph-loop/commands.d.ts new file mode 100644 index 0000000..137128a --- /dev/null +++ b/dist/ralph-loop/commands.d.ts @@ -0,0 +1,3 @@ +export declare const RALPH_LOOP_TEMPLATE = "You are starting a Ralph Loop - a self-referential development loop that runs until task completion.\n\n## How Ralph Loop Works\n\n1. You will work on the task continuously\n2. When you believe the task is FULLY complete, output: `{{COMPLETION_PROMISE}}`\n3. If you don't output the promise, the loop will automatically inject another prompt to continue\n4. Maximum iterations: Configurable (default 100)\n\n## Rules\n\n- Focus on completing the task fully, not partially\n- Don't output the completion promise until the task is truly done\n- Each iteration should make meaningful progress toward the goal\n- If stuck, try different approaches\n- Use todos to track your progress\n\n## Exit Conditions\n\n1. **Completion**: Output your completion promise tag when fully complete\n2. **Max Iterations**: Loop stops automatically at limit\n3. **Cancel**: User runs `/cancel-ralph` command\n\n## Your Task\n\nParse the arguments below and begin working on the task. The format is:\n`\"task description\" [--completion-promise=TEXT] [--max-iterations=N] [--strategy=reset|continue]`\n\nDefault completion promise is \"DONE\" and default max iterations is 100."; +export declare const CANCEL_RALPH_TEMPLATE = "Cancel the currently active Ralph Loop.\n\nThis will:\n1. Stop the loop from continuing\n2. Clear the loop state file\n3. Allow the session to end normally\n\nCheck if a loop is active and cancel it. Inform the user of the result."; +export declare const RALPH_LOOP_COMMANDS: Record; diff --git a/dist/ralph-loop/completion-promise-detector.d.ts b/dist/ralph-loop/completion-promise-detector.d.ts new file mode 100644 index 0000000..e61f927 --- /dev/null +++ b/dist/ralph-loop/completion-promise-detector.d.ts @@ -0,0 +1,8 @@ +import type { PluginInput } from "@opencode-ai/plugin"; +export declare function detectCompletionInTranscript(transcriptPath: string | undefined, promise: string): boolean; +export declare function detectCompletionInSessionMessages(ctx: PluginInput, options: { + sessionID: string; + promise: string; + apiTimeoutMs: number; + directory: string; +}): Promise; diff --git a/dist/ralph-loop/config.d.ts b/dist/ralph-loop/config.d.ts new file mode 100644 index 0000000..212461a --- /dev/null +++ b/dist/ralph-loop/config.d.ts @@ -0,0 +1,8 @@ +export type RalphLoopStrategy = "reset" | "continue"; +export type RalphLoopConfig = { + enabled?: boolean; + default_max_iterations?: number; + state_dir?: string; + default_strategy?: RalphLoopStrategy; +}; +export declare function loadRalphLoopConfig(projectDir: string): RalphLoopConfig; diff --git a/dist/ralph-loop/constants.d.ts b/dist/ralph-loop/constants.d.ts new file mode 100644 index 0000000..e980c6b --- /dev/null +++ b/dist/ralph-loop/constants.d.ts @@ -0,0 +1,4 @@ +export declare const HOOK_NAME = "ralph-loop"; +export declare const DEFAULT_STATE_FILE = ".sisyphus/ralph-loop.local.md"; +export declare const DEFAULT_MAX_ITERATIONS = 100; +export declare const DEFAULT_COMPLETION_PROMISE = "DONE"; diff --git a/dist/ralph-loop/continuation-prompt-builder.d.ts b/dist/ralph-loop/continuation-prompt-builder.d.ts new file mode 100644 index 0000000..4f3d2a1 --- /dev/null +++ b/dist/ralph-loop/continuation-prompt-builder.d.ts @@ -0,0 +1,2 @@ +import type { RalphLoopState } from "./types"; +export declare function buildContinuationPrompt(state: RalphLoopState): string; diff --git a/dist/ralph-loop/continuation-prompt-injector.d.ts b/dist/ralph-loop/continuation-prompt-injector.d.ts new file mode 100644 index 0000000..1aa451e --- /dev/null +++ b/dist/ralph-loop/continuation-prompt-injector.d.ts @@ -0,0 +1,8 @@ +import type { PluginInput } from "@opencode-ai/plugin"; +export declare function injectContinuationPrompt(ctx: PluginInput, options: { + sessionID: string; + prompt: string; + directory: string; + apiTimeoutMs: number; + inheritFromSessionID?: string; +}): Promise; diff --git a/dist/ralph-loop/internal-initiator-marker.d.ts b/dist/ralph-loop/internal-initiator-marker.d.ts new file mode 100644 index 0000000..2610934 --- /dev/null +++ b/dist/ralph-loop/internal-initiator-marker.d.ts @@ -0,0 +1,5 @@ +export declare const OMO_INTERNAL_INITIATOR_MARKER = ""; +export declare function createInternalAgentTextPart(text: string): { + type: "text"; + text: string; +}; diff --git a/dist/ralph-loop/iteration-continuation.d.ts b/dist/ralph-loop/iteration-continuation.d.ts new file mode 100644 index 0000000..43fb9fa --- /dev/null +++ b/dist/ralph-loop/iteration-continuation.d.ts @@ -0,0 +1,12 @@ +import type { PluginInput } from "@opencode-ai/plugin"; +import type { RalphLoopState } from "./types"; +type ContinuationOptions = { + directory: string; + apiTimeoutMs: number; + previousSessionID: string; + loopState: { + setSessionID: (sessionID: string) => RalphLoopState | null; + }; +}; +export declare function continueIteration(ctx: PluginInput, state: RalphLoopState, options: ContinuationOptions): Promise; +export {}; diff --git a/dist/ralph-loop/logger.d.ts b/dist/ralph-loop/logger.d.ts new file mode 100644 index 0000000..1aff0a6 --- /dev/null +++ b/dist/ralph-loop/logger.d.ts @@ -0,0 +1,2 @@ +export declare function log(message: string, meta?: unknown): void; +export declare function getLogFilePath(): string; diff --git a/dist/ralph-loop/loop-session-recovery.d.ts b/dist/ralph-loop/loop-session-recovery.d.ts new file mode 100644 index 0000000..6fd01aa --- /dev/null +++ b/dist/ralph-loop/loop-session-recovery.d.ts @@ -0,0 +1,7 @@ +export declare function createLoopSessionRecovery(options?: { + recoveryWindowMs?: number; +}): { + isRecovering(sessionID: string): boolean; + markRecovering(sessionID: string): void; + clear(sessionID: string): void; +}; diff --git a/dist/ralph-loop/loop-state-controller.d.ts b/dist/ralph-loop/loop-state-controller.d.ts new file mode 100644 index 0000000..c88bc08 --- /dev/null +++ b/dist/ralph-loop/loop-state-controller.d.ts @@ -0,0 +1,18 @@ +import type { RalphLoopOptions, RalphLoopState } from "./types"; +export declare function createLoopStateController(options: { + directory: string; + stateDir: string | undefined; + config: RalphLoopOptions["config"] | undefined; +}): { + startLoop(sessionID: string, prompt: string, loopOptions?: { + maxIterations?: number; + completionPromise?: string; + ultrawork?: boolean; + strategy?: "reset" | "continue"; + }): boolean; + cancelLoop(sessionID: string): boolean; + getState(): RalphLoopState | null; + clear(): boolean; + incrementIteration(): RalphLoopState | null; + setSessionID(sessionID: string): RalphLoopState | null; +}; diff --git a/dist/ralph-loop/normalize-sdk-response.d.ts b/dist/ralph-loop/normalize-sdk-response.d.ts new file mode 100644 index 0000000..c9b3634 --- /dev/null +++ b/dist/ralph-loop/normalize-sdk-response.d.ts @@ -0,0 +1,3 @@ +export declare function normalizeSDKResponse(response: unknown, fallback: T, options?: { + preferResponseOnMissingData?: boolean; +}): T; diff --git a/dist/ralph-loop/prompt-tools.d.ts b/dist/ralph-loop/prompt-tools.d.ts new file mode 100644 index 0000000..0e4affe --- /dev/null +++ b/dist/ralph-loop/prompt-tools.d.ts @@ -0,0 +1,2 @@ +export type PromptToolPermission = boolean | "allow" | "deny" | "ask"; +export declare function normalizePromptTools(tools: Record | undefined): Record | undefined; diff --git a/dist/ralph-loop/ralph-loop-event-handler.d.ts b/dist/ralph-loop/ralph-loop-event-handler.d.ts new file mode 100644 index 0000000..67a9534 --- /dev/null +++ b/dist/ralph-loop/ralph-loop-event-handler.d.ts @@ -0,0 +1,28 @@ +import type { PluginInput } from "@opencode-ai/plugin"; +import type { RalphLoopOptions, RalphLoopState } from "./types"; +type SessionRecovery = { + isRecovering: (sessionID: string) => boolean; + markRecovering: (sessionID: string) => void; + clear: (sessionID: string) => void; +}; +type LoopStateController = { + getState: () => RalphLoopState | null; + clear: () => boolean; + incrementIteration: () => RalphLoopState | null; + setSessionID: (sessionID: string) => RalphLoopState | null; +}; +type RalphLoopEventHandlerOptions = { + directory: string; + apiTimeoutMs: number; + getTranscriptPath: (sessionID: string) => string | undefined; + checkSessionExists?: RalphLoopOptions["checkSessionExists"]; + sessionRecovery: SessionRecovery; + loopState: LoopStateController; +}; +export declare function createRalphLoopEventHandler(ctx: PluginInput, options: RalphLoopEventHandlerOptions): ({ event }: { + event: { + type: string; + properties?: unknown; + }; +}) => Promise; +export {}; diff --git a/dist/ralph-loop/ralph-loop-hook.d.ts b/dist/ralph-loop/ralph-loop-hook.d.ts new file mode 100644 index 0000000..80c1391 --- /dev/null +++ b/dist/ralph-loop/ralph-loop-hook.d.ts @@ -0,0 +1,19 @@ +import type { PluginInput } from "@opencode-ai/plugin"; +import type { RalphLoopOptions, RalphLoopState } from "./types"; +export interface RalphLoopHook { + event: (input: { + event: { + type: string; + properties?: unknown; + }; + }) => Promise; + startLoop: (sessionID: string, prompt: string, options?: { + maxIterations?: number; + completionPromise?: string; + ultrawork?: boolean; + strategy?: "reset" | "continue"; + }) => boolean; + cancelLoop: (sessionID: string) => boolean; + getState: () => RalphLoopState | null; +} +export declare function createRalphLoopHook(ctx: PluginInput, options?: RalphLoopOptions): RalphLoopHook; diff --git a/dist/ralph-loop/session-reset-strategy.d.ts b/dist/ralph-loop/session-reset-strategy.d.ts new file mode 100644 index 0000000..0619991 --- /dev/null +++ b/dist/ralph-loop/session-reset-strategy.d.ts @@ -0,0 +1,3 @@ +import type { PluginInput } from "@opencode-ai/plugin"; +export declare function createIterationSession(ctx: PluginInput, parentSessionID: string, directory: string): Promise; +export declare function selectSessionInTui(client: PluginInput["client"], sessionID: string): Promise; diff --git a/dist/ralph-loop/simple-frontmatter.d.ts b/dist/ralph-loop/simple-frontmatter.d.ts new file mode 100644 index 0000000..7aaab1d --- /dev/null +++ b/dist/ralph-loop/simple-frontmatter.d.ts @@ -0,0 +1,4 @@ +export declare function parseFrontmatter(content: string): { + data: Record; + body: string; +}; diff --git a/dist/ralph-loop/storage.d.ts b/dist/ralph-loop/storage.d.ts new file mode 100644 index 0000000..72948fc --- /dev/null +++ b/dist/ralph-loop/storage.d.ts @@ -0,0 +1,6 @@ +import type { RalphLoopState } from "./types"; +export declare function getStateFilePath(directory: string, customPath?: string): string; +export declare function readState(directory: string, customPath?: string): RalphLoopState | null; +export declare function writeState(directory: string, state: RalphLoopState, customPath?: string): boolean; +export declare function clearState(directory: string, customPath?: string): boolean; +export declare function incrementIteration(directory: string, customPath?: string): RalphLoopState | null; diff --git a/dist/ralph-loop/system-directive.d.ts b/dist/ralph-loop/system-directive.d.ts new file mode 100644 index 0000000..c556495 --- /dev/null +++ b/dist/ralph-loop/system-directive.d.ts @@ -0,0 +1 @@ +export declare const SYSTEM_DIRECTIVE_PREFIX = "[SYSTEM DIRECTIVE: OH-MY-OPENCODE"; diff --git a/dist/ralph-loop/transcript.d.ts b/dist/ralph-loop/transcript.d.ts new file mode 100644 index 0000000..3ba9503 --- /dev/null +++ b/dist/ralph-loop/transcript.d.ts @@ -0,0 +1 @@ +export declare function getTranscriptPath(sessionId: string): string; diff --git a/dist/ralph-loop/types.d.ts b/dist/ralph-loop/types.d.ts new file mode 100644 index 0000000..61dbd5d --- /dev/null +++ b/dist/ralph-loop/types.d.ts @@ -0,0 +1,18 @@ +import type { RalphLoopConfig } from "./config"; +export interface RalphLoopState { + active: boolean; + iteration: number; + max_iterations: number; + completion_promise: string; + started_at: string; + prompt: string; + session_id?: string; + ultrawork?: boolean; + strategy?: "reset" | "continue"; +} +export interface RalphLoopOptions { + config?: RalphLoopConfig; + getTranscriptPath?: (sessionId: string) => string | undefined; + apiTimeout?: number; + checkSessionExists?: (sessionId: string) => Promise; +} diff --git a/dist/ralph-loop/with-timeout.d.ts b/dist/ralph-loop/with-timeout.d.ts new file mode 100644 index 0000000..87fb4a5 --- /dev/null +++ b/dist/ralph-loop/with-timeout.d.ts @@ -0,0 +1 @@ +export declare function withTimeout(promise: Promise, timeoutMs: number): Promise;