feat: add Ralph Wiggum mode (#484)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-01-06 21:13:50 -08:00
committed by GitHub
parent 807bd362f2
commit c96a5204eb
4 changed files with 597 additions and 4 deletions

View File

@@ -14,6 +14,7 @@ import {
import type { PermissionMode } from "../../permissions/mode";
import { permissionMode } from "../../permissions/mode";
import { ANTHROPIC_PROVIDER_NAME } from "../../providers/anthropic-provider";
import { ralphMode } from "../../ralph/mode";
import { settingsManager } from "../../settings-manager";
import { charsToTokens, formatCompact } from "../helpers/format";
import { useTerminalWidth } from "../hooks/useTerminalWidth";
@@ -56,6 +57,10 @@ export function Input({
messageQueue,
onEnterQueueEditMode,
onEscapeCancel,
ralphActive = false,
ralphPending = false,
ralphPendingYolo = false,
onRalphExit,
}: {
visible?: boolean;
streaming: boolean;
@@ -75,6 +80,10 @@ export function Input({
messageQueue?: string[];
onEnterQueueEditMode?: () => void;
onEscapeCancel?: () => void;
ralphActive?: boolean;
ralphPending?: boolean;
ralphPendingYolo?: boolean;
onRalphExit?: () => void;
}) {
const [value, setValue] = useState("");
const [escapePressed, setEscapePressed] = useState(false);
@@ -236,7 +245,7 @@ export function Input({
// Note: bash mode entry/exit is implemented inside PasteAwareTextInput so we can
// consume the keystroke before it renders (no flicker).
// Handle Shift+Tab for permission mode cycling
// Handle Shift+Tab for permission mode cycling (or ralph mode exit)
useInput((_input, key) => {
if (!visible) return;
// Debug logging for shift+tab detection
@@ -247,6 +256,12 @@ export function Input({
);
}
if (key.shift && key.tab) {
// If ralph mode is active, exit it first (goes to default mode)
if (ralphActive && onRalphExit) {
onRalphExit();
return;
}
// Cycle through permission modes
const modes: PermissionMode[] = [
"default",
@@ -570,8 +585,43 @@ export function Input({
setCursorPos(selectedCommand.length);
};
// Get display name and color for permission mode
// Get display name and color for permission mode (ralph modes take precedence)
const getModeInfo = () => {
// Check ralph pending first (waiting for task input)
if (ralphPending) {
if (ralphPendingYolo) {
return {
name: "yolo-ralph (waiting)",
color: "#FF8C00", // dark orange
};
}
return {
name: "ralph (waiting)",
color: "#FEE19C", // yellow (brandColors.statusWarning)
};
}
// Check ralph mode active (using prop for reactivity)
if (ralphActive) {
const ralph = ralphMode.getState();
const iterDisplay =
ralph.maxIterations > 0
? `${ralph.currentIteration}/${ralph.maxIterations}`
: `${ralph.currentIteration}`;
if (ralph.isYolo) {
return {
name: `yolo-ralph (iter ${iterDisplay})`,
color: "#FF8C00", // dark orange
};
}
return {
name: `ralph (iter ${iterDisplay})`,
color: "#FEE19C", // yellow (brandColors.statusWarning)
};
}
// Fall through to permission modes
switch (currentMode) {
case "acceptEdits":
return { name: "accept edits", color: colors.status.processing };
@@ -597,6 +647,7 @@ export function Input({
const elapsedMinutes = Math.floor(elapsedMs / 60000);
// Build the status hint text (esc to interrupt · 2m · 1.2k ↑)
// In ralph mode, also show "shift+tab to exit"
const statusHintText = (() => {
const hintColor = chalk.hex(colors.subagent.hint);
const hintBold = hintColor.bold;
@@ -716,7 +767,7 @@ export function Input({
<Text color={modeInfo.color}> {modeInfo.name}</Text>
<Text color={modeInfo.color} dimColor>
{" "}
(shift+tab to cycle)
(shift+tab to {ralphActive || ralphPending ? "exit" : "cycle"})
</Text>
</Text>
) : (