minimal-agent 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -72
- package/package.json +18 -13
- package/plugins/ralph-wiggum/plugin.js +205 -0
- package/plugins/ralph-wiggum/src/goalState.js +260 -0
- package/plugins/ralph-wiggum/src/{sentinels.ts → sentinels.js} +4 -7
- package/plugins/ralph-wiggum/src/stopHookRunner.js +104 -0
- package/plugins/ralph-wiggum/src/verificationGate.js +202 -0
- package/plugins/workflow-runner/{plugin.ts → plugin.js} +20 -26
- package/plugins/workflow-runner/src/expressions.js +369 -0
- package/plugins/workflow-runner/src/index.js +174 -0
- package/plugins/workflow-runner/src/loader.js +183 -0
- package/plugins/workflow-runner/src/runner.js +290 -0
- package/plugins/workflow-runner/src/stepExecutors/assert.js +28 -0
- package/plugins/workflow-runner/src/stepExecutors/llm.js +44 -0
- package/plugins/workflow-runner/src/stepExecutors/skill.js +103 -0
- package/plugins/workflow-runner/src/stepExecutors/{tool.ts → tool.js} +19 -25
- package/plugins/workflow-runner/src/types.js +59 -0
- package/plugins/workflow-runner/src/{workflowState.ts → workflowState.js} +21 -40
- package/src/bootstrap/cwdArg.js +22 -0
- package/src/bootstrap/workingDir.js +31 -0
- package/src/cli/configWizard.js +272 -0
- package/src/cli/print.js +192 -0
- package/src/config/configFile.js +78 -0
- package/src/config.js +118 -0
- package/src/context/compact.js +357 -0
- package/src/context/microCompactLite.js +151 -0
- package/src/context/persistContext.js +109 -0
- package/src/context/reactiveCompact.js +121 -0
- package/src/context/sessionPath.js +58 -0
- package/src/context/snipCompact.js +112 -0
- package/src/context/tokenCounter.js +66 -0
- package/src/llm/client.js +182 -0
- package/src/loop.js +230 -0
- package/src/main.js +116 -0
- package/src/plugin-sdk.js +24 -0
- package/src/plugins/commandRouter.js +169 -0
- package/src/plugins/hookEngine.js +258 -0
- package/src/plugins/pluginApi.js +23 -0
- package/src/plugins/pluginLoader.js +71 -0
- package/src/plugins/pluginRunner.js +65 -0
- package/src/plugins/transcript.js +171 -0
- package/src/prompts/projectInstructions.js +48 -0
- package/src/prompts/skillList.js +126 -0
- package/src/prompts/system.js +155 -0
- package/src/session/runTurn.js +41 -0
- package/src/session/sessionState.js +19 -0
- package/src/tools/bash/bash.js +352 -0
- package/src/tools/bash/semantics.js +85 -0
- package/src/tools/bash/warnings.js +98 -0
- package/src/tools/edit/edit.js +253 -0
- package/src/tools/edit/multi-edit.js +155 -0
- package/src/tools/glob/glob.js +97 -0
- package/src/tools/grep/grep.js +185 -0
- package/src/tools/grep/rgPath.js +173 -0
- package/src/tools/index.js +94 -0
- package/src/tools/read/read.js +209 -0
- package/src/tools/shared/fileState.js +61 -0
- package/src/tools/shared/fileUtils.js +281 -0
- package/src/tools/shared/schemas.js +16 -0
- package/src/tools/types.js +21 -0
- package/src/tools/webbrowser/browser.js +55 -0
- package/src/tools/webbrowser/webbrowser.js +194 -0
- package/src/tools/webfetch/preapproved.js +267 -0
- package/src/tools/webfetch/webfetch.js +317 -0
- package/src/tools/websearch/websearch.js +161 -0
- package/src/tools/write/write.js +125 -0
- package/src/types/turndown.d.ts +23 -0
- package/src/types.js +16 -0
- package/src/ui/App.js +37 -0
- package/src/ui/InputBox.js +240 -0
- package/src/ui/MessageList.js +28 -0
- package/src/ui/Root.js +70 -0
- package/src/ui/StatusLine.js +41 -0
- package/src/ui/ToolStatus.js +11 -0
- package/src/ui/hooks/useChat.js +234 -0
- package/src/ui/hooks/usePasteHandler.js +137 -0
- package/src/ui/hooks/useTextBuffer.js +55 -0
- package/src/ui/hooks/useTokenUsage.js +30 -0
- package/src/ui/textBuffer.js +217 -0
- package/src/utils/packageRoot.js +37 -0
- package/src/utils/resourcePaths.js +49 -0
- package/src/utils/zodToJson.js +29 -0
- package/dist/main.js +0 -5315
- package/plugins/ralph-wiggum/plugin.ts +0 -275
- package/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh +0 -203
- package/plugins/ralph-wiggum/src/goalState.ts +0 -310
- package/plugins/ralph-wiggum/src/stopHookRunner.ts +0 -136
- package/plugins/ralph-wiggum/src/verificationGate.ts +0 -252
- package/plugins/ralph-wiggum/test/goalState.test.ts +0 -410
- package/plugins/ralph-wiggum/test/verificationGate.test.ts +0 -122
- package/plugins/workflow-runner/src/expressions.ts +0 -371
- package/plugins/workflow-runner/src/index.ts +0 -194
- package/plugins/workflow-runner/src/loader.ts +0 -193
- package/plugins/workflow-runner/src/runner.ts +0 -313
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -30
- package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -54
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -115
- package/plugins/workflow-runner/src/types.ts +0 -183
- package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -114
- package/plugins/workflow-runner/test/e2e.test.ts +0 -268
- package/plugins/workflow-runner/test/expressions.test.ts +0 -140
- package/plugins/workflow-runner/test/fixtures/cli-e2e.yaml +0 -27
- package/plugins/workflow-runner/test/fixtures/hello-workflow.yaml +0 -49
- package/plugins/workflow-runner/test/graceful.test.ts +0 -139
- package/plugins/workflow-runner/test/loader.test.ts +0 -216
- package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -230
- package/plugins/workflow-runner/test/runner.test.ts +0 -511
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ============================================================
|
|
3
|
-
* plugins/ralph-wiggum/plugin.ts —— ralph-wiggum 真·插件入口
|
|
4
|
-
* ------------------------------------------------------------
|
|
5
|
-
* 把原 src/plugins/pluginRunner.ts 的 do-while 循环驱动整段搬来。
|
|
6
|
-
* 对外通过 PluginApi.runCommand 暴露 /ralph-loop。
|
|
7
|
-
*
|
|
8
|
-
* 循环契约:
|
|
9
|
-
* - 每轮把 history 重置成进入循环前的快照(fresh context)
|
|
10
|
-
* - 用 GoalState.composeContext() 拼 PLAN/BUILD/VERIFY/HEAL 阶段信息
|
|
11
|
-
* - 跑 runQuery
|
|
12
|
-
* - 检测 <promise>DONE</promise> → runVerification → 通过则退出
|
|
13
|
-
* - 检测 <PROMISE>NEED_REPLAN</PROMISE> → forceSetPhase(PLAN)
|
|
14
|
-
* - executeStopHook 是咨询式:block 才把 reason 注入下一轮,pass 不退出
|
|
15
|
-
*
|
|
16
|
-
* 终止(独占):sentinel + verify 通过 / 达 max-iterations / abort / 安全天花板
|
|
17
|
-
*
|
|
18
|
-
* Windows 上 hooks/stop-hook.sh 不可用,但循环不依赖 hook,功能完整,
|
|
19
|
-
* 只是 hook 的咨询通道失效。
|
|
20
|
-
* ============================================================
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { fileURLToPath } from 'node:url';
|
|
24
|
-
import { dirname } from 'node:path';
|
|
25
|
-
|
|
26
|
-
import {
|
|
27
|
-
runQuery,
|
|
28
|
-
getWorkingDir,
|
|
29
|
-
type PluginApi,
|
|
30
|
-
type PluginContext,
|
|
31
|
-
type LoopEvent,
|
|
32
|
-
type Message,
|
|
33
|
-
} from '../../src/plugin-sdk.ts';
|
|
34
|
-
|
|
35
|
-
import { GoalState, Phase } from './src/goalState.ts';
|
|
36
|
-
import { parseVerifyArgs, runVerification } from './src/verificationGate.ts';
|
|
37
|
-
import {
|
|
38
|
-
hasCompleteSentinel,
|
|
39
|
-
hasNeedReplanSentinel,
|
|
40
|
-
} from './src/sentinels.ts';
|
|
41
|
-
import { executeStopHook } from './src/stopHookRunner.ts';
|
|
42
|
-
|
|
43
|
-
const PLUGIN_NAME = 'ralph-wiggum';
|
|
44
|
-
const DEFAULT_MAX_ITERATIONS = 50;
|
|
45
|
-
const SAFETY_CEILING = 200;
|
|
46
|
-
|
|
47
|
-
const PLUGIN_ROOT = dirname(fileURLToPath(import.meta.url));
|
|
48
|
-
|
|
49
|
-
function extractMaxIterations(args: string): number | undefined {
|
|
50
|
-
const match = args.match(/--max-iterations\s+(\d+)/i);
|
|
51
|
-
return match ? parseInt(match[1], 10) : undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function* runRalphLoop(
|
|
55
|
-
args: string,
|
|
56
|
-
ctx: PluginContext,
|
|
57
|
-
): AsyncGenerator<LoopEvent, void, void> {
|
|
58
|
-
const { provider, history, signal } = ctx;
|
|
59
|
-
|
|
60
|
-
const maxIter = Math.min(
|
|
61
|
-
extractMaxIterations(args) ?? DEFAULT_MAX_ITERATIONS,
|
|
62
|
-
SAFETY_CEILING,
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// 没有 args(用户只敲 /ralph-loop)→ 留个最小 goal placeholder,避免 GoalState.init 拒空串
|
|
66
|
-
const userGoal = args.trim() || '(未提供目标)';
|
|
67
|
-
|
|
68
|
-
yield {
|
|
69
|
-
type: 'plugin_progress',
|
|
70
|
-
pluginId: PLUGIN_NAME,
|
|
71
|
-
current: 0,
|
|
72
|
-
max: maxIter,
|
|
73
|
-
message: 'Ralph Wiggum copy-task loop 启动',
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const checks = parseVerifyArgs(args);
|
|
77
|
-
|
|
78
|
-
// sessionTag = 插件名 → 多插件可并发不打架,/new 也能扫到清掉
|
|
79
|
-
const goalState = new GoalState(getWorkingDir(), PLUGIN_NAME);
|
|
80
|
-
await goalState.reset();
|
|
81
|
-
await goalState.init(userGoal, checks);
|
|
82
|
-
await goalState.appendProgress(
|
|
83
|
-
`=== Loop 启动 === 目标: ${userGoal.slice(0, 120)}...`,
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
// 进循环前快照 history —— 每轮 runQuery 前用它重置,保证 fresh context
|
|
87
|
-
const baseHistory = history.slice();
|
|
88
|
-
|
|
89
|
-
let iterationCount = 0;
|
|
90
|
-
let consecutiveFailures = 0;
|
|
91
|
-
let currentInput = userGoal;
|
|
92
|
-
let finalAssistantMsg: Message | null = null;
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
do {
|
|
96
|
-
iterationCount++;
|
|
97
|
-
if (iterationCount > maxIter) {
|
|
98
|
-
await goalState.forceSetPhase(Phase.DONE, `达到迭代上限 ${maxIter}`);
|
|
99
|
-
await goalState.appendLearning(
|
|
100
|
-
`[迭代上限] 循环在 ${iterationCount - 1} 轮后强制终止,可能目标过大或陷入死循环`,
|
|
101
|
-
);
|
|
102
|
-
yield {
|
|
103
|
-
type: 'error',
|
|
104
|
-
error: `Loop 已达迭代上限 ${maxIter},自动停止`,
|
|
105
|
-
};
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (signal?.aborted) {
|
|
110
|
-
yield { type: 'interrupted' };
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
yield {
|
|
115
|
-
type: 'plugin_progress',
|
|
116
|
-
pluginId: PLUGIN_NAME,
|
|
117
|
-
current: iterationCount,
|
|
118
|
-
max: maxIter,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// fresh context:清空 history,重置为入循环前的快照
|
|
122
|
-
history.length = 0;
|
|
123
|
-
history.push(...baseHistory);
|
|
124
|
-
|
|
125
|
-
const freshContext = goalState.composeContext(iterationCount);
|
|
126
|
-
const enhancedInput = `${freshContext}\n\n${currentInput}`;
|
|
127
|
-
|
|
128
|
-
yield* runQuery(enhancedInput, {
|
|
129
|
-
provider,
|
|
130
|
-
history,
|
|
131
|
-
signal,
|
|
132
|
-
maxTurns: ctx.maxTurns,
|
|
133
|
-
sessionState: ctx.sessionState,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
if (signal?.aborted) {
|
|
137
|
-
yield { type: 'interrupted' };
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 从本轮 history 抓最后一个 assistant 消息(runQuery 已 push 进去)
|
|
142
|
-
const lastAssistantIdx = (() => {
|
|
143
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
144
|
-
if (history[i].role === 'assistant') return i;
|
|
145
|
-
}
|
|
146
|
-
return -1;
|
|
147
|
-
})();
|
|
148
|
-
finalAssistantMsg =
|
|
149
|
-
lastAssistantIdx >= 0 ? history[lastAssistantIdx] : null;
|
|
150
|
-
const lastAssistantText = finalAssistantMsg
|
|
151
|
-
? typeof finalAssistantMsg.content === 'string'
|
|
152
|
-
? finalAssistantMsg.content
|
|
153
|
-
: JSON.stringify(finalAssistantMsg.content)
|
|
154
|
-
: '';
|
|
155
|
-
|
|
156
|
-
if (hasCompleteSentinel(lastAssistantText)) {
|
|
157
|
-
// 哨兵:可能来自任意阶段(包括 iter 1 的 PLAN),用 force 跳到 VERIFY
|
|
158
|
-
await goalState.forceSetPhase(Phase.VERIFY, '检测到完成哨兵,进入验证');
|
|
159
|
-
await goalState.appendProgress(
|
|
160
|
-
`迭代 ${iterationCount}: 检测到完成哨兵,运行验证门...`,
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
if (checks.length > 0) {
|
|
164
|
-
const vResult = await runVerification(checks);
|
|
165
|
-
|
|
166
|
-
if (!vResult.passed) {
|
|
167
|
-
consecutiveFailures++;
|
|
168
|
-
await goalState.appendLearning(
|
|
169
|
-
`[迭代 ${iterationCount}] 声称完成但验证未通过: ${vResult.summary}`,
|
|
170
|
-
);
|
|
171
|
-
yield {
|
|
172
|
-
type: 'error',
|
|
173
|
-
error: `⚠️ 验证未通过: ${vResult.summary}。继续尝试...`,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (consecutiveFailures >= 3) {
|
|
177
|
-
await goalState.forceSetPhase(
|
|
178
|
-
Phase.HEAL,
|
|
179
|
-
`连续 ${consecutiveFailures} 次验证失败`,
|
|
180
|
-
);
|
|
181
|
-
} else {
|
|
182
|
-
await goalState.setPhase(Phase.BUILD, '验证未通过,返回构建');
|
|
183
|
-
}
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
await goalState.appendProgress(`✅ 验证通过: ${vResult.summary}`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
await goalState.setPhase(Phase.DONE, 'goal complete & verified');
|
|
191
|
-
yield {
|
|
192
|
-
type: 'plugin_iteration',
|
|
193
|
-
pluginName: PLUGIN_NAME,
|
|
194
|
-
current: iterationCount,
|
|
195
|
-
max: maxIter,
|
|
196
|
-
};
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (hasNeedReplanSentinel(lastAssistantText)) {
|
|
201
|
-
await goalState.forceSetPhase(Phase.PLAN, 'agent 请求重新规划');
|
|
202
|
-
await goalState.appendLearning(
|
|
203
|
-
'[NEED_REPLAN] Agent 认为当前方案不可行,需要重新规划',
|
|
204
|
-
);
|
|
205
|
-
await goalState.appendProgress(
|
|
206
|
-
'Agent 请求 NEED_REPLAN,回 PLAN 阶段',
|
|
207
|
-
);
|
|
208
|
-
consecutiveFailures = 0;
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// PLAN 阶段跑过一轮还没哨兵,认为规划已完成,自动推进 BUILD
|
|
213
|
-
if (goalState.currentPhase === Phase.PLAN && iterationCount >= 2) {
|
|
214
|
-
await goalState.setPhase(Phase.BUILD, '规划阶段已完成,进入构建');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Stop-hook 咨询式调用:block 才把 reason 注入下一轮 prompt
|
|
218
|
-
// pass / 报错都不再终止循环;终止由 sentinel/maxIter/abort/NEED_REPLAN 独占
|
|
219
|
-
const hookResult = await executeStopHook(PLUGIN_ROOT, lastAssistantText);
|
|
220
|
-
|
|
221
|
-
if (hookResult.decision === 'block' && hookResult.reason) {
|
|
222
|
-
currentInput = hookResult.reason;
|
|
223
|
-
consecutiveFailures = 0;
|
|
224
|
-
await goalState.recordDecision(
|
|
225
|
-
{
|
|
226
|
-
iteration: iterationCount,
|
|
227
|
-
phase: goalState.currentPhase,
|
|
228
|
-
summary: 'stop-hook 反馈',
|
|
229
|
-
},
|
|
230
|
-
['继续循环', '终止'],
|
|
231
|
-
'继续循环',
|
|
232
|
-
hookResult.reason.slice(0, 200),
|
|
233
|
-
);
|
|
234
|
-
if (hookResult.systemMessage) {
|
|
235
|
-
baseHistory.push({
|
|
236
|
-
role: 'user',
|
|
237
|
-
content: `[Plugin Stop Hook] ${hookResult.systemMessage}`,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
await goalState.appendProgress(
|
|
241
|
-
`迭代 ${iterationCount}: Stop hook block,注入反馈继续`,
|
|
242
|
-
);
|
|
243
|
-
} else {
|
|
244
|
-
await goalState.appendProgress(
|
|
245
|
-
`迭代 ${iterationCount}: 无哨兵 / hook pass,继续下一轮`,
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
} while (true);
|
|
249
|
-
} finally {
|
|
250
|
-
// 收尾:把循环里临时累积的 history 还原成 baseHistory + 最后一轮 assistant
|
|
251
|
-
// 这样 TUI 上看到的就是"一次问答",而不是 N 轮重复
|
|
252
|
-
history.length = 0;
|
|
253
|
-
history.push(...baseHistory);
|
|
254
|
-
if (finalAssistantMsg) {
|
|
255
|
-
history.push(finalAssistantMsg);
|
|
256
|
-
}
|
|
257
|
-
await goalState.cleanup();
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const api: PluginApi = {
|
|
262
|
-
async *runCommand(commandName, args, ctx) {
|
|
263
|
-
if (commandName === 'ralph-loop') {
|
|
264
|
-
yield* runRalphLoop(args, ctx);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
// 其它命令(help / cancel-ralph)→ 不接管,让框架走声明式 fallback
|
|
268
|
-
yield {
|
|
269
|
-
type: 'error',
|
|
270
|
-
error: `ralph-wiggum: 命令 /${commandName} 未由 plugin.ts 接管`,
|
|
271
|
-
};
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
export default api;
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Ralph Loop Setup Script
|
|
4
|
-
# Creates state file for in-session Ralph loop
|
|
5
|
-
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
# Parse arguments
|
|
9
|
-
PROMPT_PARTS=()
|
|
10
|
-
MAX_ITERATIONS=0
|
|
11
|
-
COMPLETION_PROMISE="null"
|
|
12
|
-
|
|
13
|
-
# Parse options and positional arguments
|
|
14
|
-
while [[ $# -gt 0 ]]; do
|
|
15
|
-
case $1 in
|
|
16
|
-
-h|--help)
|
|
17
|
-
cat << 'HELP_EOF'
|
|
18
|
-
Ralph Loop - Interactive self-referential development loop
|
|
19
|
-
|
|
20
|
-
USAGE:
|
|
21
|
-
/ralph-loop [PROMPT...] [OPTIONS]
|
|
22
|
-
|
|
23
|
-
ARGUMENTS:
|
|
24
|
-
PROMPT... Initial prompt to start the loop (can be multiple words without quotes)
|
|
25
|
-
|
|
26
|
-
OPTIONS:
|
|
27
|
-
--max-iterations <n> Maximum iterations before auto-stop (default: unlimited)
|
|
28
|
-
--completion-promise '<text>' Promise phrase (USE QUOTES for multi-word)
|
|
29
|
-
-h, --help Show this help message
|
|
30
|
-
|
|
31
|
-
DESCRIPTION:
|
|
32
|
-
Starts a Ralph Wiggum loop in your CURRENT session. The stop hook prevents
|
|
33
|
-
exit and feeds your output back as input until completion or iteration limit.
|
|
34
|
-
|
|
35
|
-
To signal completion, you must output: <promise>YOUR_PHRASE</promise>
|
|
36
|
-
|
|
37
|
-
Use this for:
|
|
38
|
-
- Interactive iteration where you want to see progress
|
|
39
|
-
- Tasks requiring self-correction and refinement
|
|
40
|
-
- Learning how Ralph works
|
|
41
|
-
|
|
42
|
-
EXAMPLES:
|
|
43
|
-
/ralph-loop Build a todo API --completion-promise 'DONE' --max-iterations 20
|
|
44
|
-
/ralph-loop --max-iterations 10 Fix the auth bug
|
|
45
|
-
/ralph-loop Refactor cache layer (runs forever)
|
|
46
|
-
/ralph-loop --completion-promise 'TASK COMPLETE' Create a REST API
|
|
47
|
-
|
|
48
|
-
STOPPING:
|
|
49
|
-
Only by reaching --max-iterations or detecting --completion-promise
|
|
50
|
-
No manual stop - Ralph runs infinitely by default!
|
|
51
|
-
|
|
52
|
-
MONITORING:
|
|
53
|
-
# View current iteration:
|
|
54
|
-
grep '^iteration:' .minimal-agent/ralph-loop.local.md
|
|
55
|
-
|
|
56
|
-
# View full state:
|
|
57
|
-
head -10 .minimal-agent/ralph-loop.local.md
|
|
58
|
-
HELP_EOF
|
|
59
|
-
exit 0
|
|
60
|
-
;;
|
|
61
|
-
--max-iterations)
|
|
62
|
-
if [[ -z "${2:-}" ]]; then
|
|
63
|
-
echo "❌ Error: --max-iterations requires a number argument" >&2
|
|
64
|
-
echo "" >&2
|
|
65
|
-
echo " Valid examples:" >&2
|
|
66
|
-
echo " --max-iterations 10" >&2
|
|
67
|
-
echo " --max-iterations 50" >&2
|
|
68
|
-
echo " --max-iterations 0 (unlimited)" >&2
|
|
69
|
-
echo "" >&2
|
|
70
|
-
echo " You provided: --max-iterations (with no number)" >&2
|
|
71
|
-
exit 1
|
|
72
|
-
fi
|
|
73
|
-
if ! [[ "$2" =~ ^[0-9]+$ ]]; then
|
|
74
|
-
echo "❌ Error: --max-iterations must be a positive integer or 0, got: $2" >&2
|
|
75
|
-
echo "" >&2
|
|
76
|
-
echo " Valid examples:" >&2
|
|
77
|
-
echo " --max-iterations 10" >&2
|
|
78
|
-
echo " --max-iterations 50" >&2
|
|
79
|
-
echo " --max-iterations 0 (unlimited)" >&2
|
|
80
|
-
echo "" >&2
|
|
81
|
-
echo " Invalid: decimals (10.5), negative numbers (-5), text" >&2
|
|
82
|
-
exit 1
|
|
83
|
-
fi
|
|
84
|
-
MAX_ITERATIONS="$2"
|
|
85
|
-
shift 2
|
|
86
|
-
;;
|
|
87
|
-
--completion-promise)
|
|
88
|
-
if [[ -z "${2:-}" ]]; then
|
|
89
|
-
echo "❌ Error: --completion-promise requires a text argument" >&2
|
|
90
|
-
echo "" >&2
|
|
91
|
-
echo " Valid examples:" >&2
|
|
92
|
-
echo " --completion-promise 'DONE'" >&2
|
|
93
|
-
echo " --completion-promise 'TASK COMPLETE'" >&2
|
|
94
|
-
echo " --completion-promise 'All tests passing'" >&2
|
|
95
|
-
echo "" >&2
|
|
96
|
-
echo " You provided: --completion-promise (with no text)" >&2
|
|
97
|
-
echo "" >&2
|
|
98
|
-
echo " Note: Multi-word promises must be quoted!" >&2
|
|
99
|
-
exit 1
|
|
100
|
-
fi
|
|
101
|
-
COMPLETION_PROMISE="$2"
|
|
102
|
-
shift 2
|
|
103
|
-
;;
|
|
104
|
-
*)
|
|
105
|
-
# Non-option argument - collect all as prompt parts
|
|
106
|
-
PROMPT_PARTS+=("$1")
|
|
107
|
-
shift
|
|
108
|
-
;;
|
|
109
|
-
esac
|
|
110
|
-
done
|
|
111
|
-
|
|
112
|
-
# Join all prompt parts with spaces
|
|
113
|
-
PROMPT="${PROMPT_PARTS[*]}"
|
|
114
|
-
|
|
115
|
-
# Validate prompt is non-empty
|
|
116
|
-
if [[ -z "$PROMPT" ]]; then
|
|
117
|
-
echo "❌ Error: No prompt provided" >&2
|
|
118
|
-
echo "" >&2
|
|
119
|
-
echo " Ralph needs a task description to work on." >&2
|
|
120
|
-
echo "" >&2
|
|
121
|
-
echo " Examples:" >&2
|
|
122
|
-
echo " /ralph-loop Build a REST API for todos" >&2
|
|
123
|
-
echo " /ralph-loop Fix the auth bug --max-iterations 20" >&2
|
|
124
|
-
echo " /ralph-loop --completion-promise 'DONE' Refactor code" >&2
|
|
125
|
-
echo "" >&2
|
|
126
|
-
echo " For all options: /ralph-loop --help" >&2
|
|
127
|
-
exit 1
|
|
128
|
-
fi
|
|
129
|
-
|
|
130
|
-
# Create state file for stop hook (markdown with YAML frontmatter)
|
|
131
|
-
mkdir -p .minimal-agent
|
|
132
|
-
|
|
133
|
-
# Quote completion promise for YAML if it contains special chars or is not null
|
|
134
|
-
if [[ -n "$COMPLETION_PROMISE" ]] && [[ "$COMPLETION_PROMISE" != "null" ]]; then
|
|
135
|
-
COMPLETION_PROMISE_YAML="\"$COMPLETION_PROMISE\""
|
|
136
|
-
else
|
|
137
|
-
COMPLETION_PROMISE_YAML="null"
|
|
138
|
-
fi
|
|
139
|
-
|
|
140
|
-
cat > .minimal-agent/ralph-loop.local.md <<EOF
|
|
141
|
-
---
|
|
142
|
-
active: true
|
|
143
|
-
iteration: 1
|
|
144
|
-
max_iterations: $MAX_ITERATIONS
|
|
145
|
-
completion_promise: $COMPLETION_PROMISE_YAML
|
|
146
|
-
started_at: "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
147
|
-
---
|
|
148
|
-
|
|
149
|
-
$PROMPT
|
|
150
|
-
EOF
|
|
151
|
-
|
|
152
|
-
# Output setup message
|
|
153
|
-
cat <<EOF
|
|
154
|
-
🔄 Ralph loop activated in this session!
|
|
155
|
-
|
|
156
|
-
Iteration: 1
|
|
157
|
-
Max iterations: $(if [[ $MAX_ITERATIONS -gt 0 ]]; then echo $MAX_ITERATIONS; else echo "unlimited"; fi)
|
|
158
|
-
Completion promise: $(if [[ "$COMPLETION_PROMISE" != "null" ]]; then echo "${COMPLETION_PROMISE//\"/} (ONLY output when TRUE - do not lie!)"; else echo "none (runs forever)"; fi)
|
|
159
|
-
|
|
160
|
-
The stop hook is now active. When you try to exit, the SAME PROMPT will be
|
|
161
|
-
fed back to you. You'll see your previous work in files, creating a
|
|
162
|
-
self-referential loop where you iteratively improve on the same task.
|
|
163
|
-
|
|
164
|
-
To monitor: head -10 .minimal-agent/ralph-loop.local.md
|
|
165
|
-
|
|
166
|
-
⚠️ WARNING: This loop cannot be stopped manually! It will run infinitely
|
|
167
|
-
unless you set --max-iterations or --completion-promise.
|
|
168
|
-
|
|
169
|
-
🔄
|
|
170
|
-
EOF
|
|
171
|
-
|
|
172
|
-
# Output the initial prompt if provided
|
|
173
|
-
if [[ -n "$PROMPT" ]]; then
|
|
174
|
-
echo ""
|
|
175
|
-
echo "$PROMPT"
|
|
176
|
-
fi
|
|
177
|
-
|
|
178
|
-
# Display completion promise requirements if set
|
|
179
|
-
if [[ "$COMPLETION_PROMISE" != "null" ]]; then
|
|
180
|
-
echo ""
|
|
181
|
-
echo "═══════════════════════════════════════════════════════════"
|
|
182
|
-
echo "CRITICAL - Ralph Loop Completion Promise"
|
|
183
|
-
echo "═══════════════════════════════════════════════════════════"
|
|
184
|
-
echo ""
|
|
185
|
-
echo "To complete this loop, output this EXACT text:"
|
|
186
|
-
echo " <promise>$COMPLETION_PROMISE</promise>"
|
|
187
|
-
echo ""
|
|
188
|
-
echo "STRICT REQUIREMENTS (DO NOT VIOLATE):"
|
|
189
|
-
echo " ✓ Use <promise> XML tags EXACTLY as shown above"
|
|
190
|
-
echo " ✓ The statement MUST be completely and unequivocally TRUE"
|
|
191
|
-
echo " ✓ Do NOT output false statements to exit the loop"
|
|
192
|
-
echo " ✓ Do NOT lie even if you think you should exit"
|
|
193
|
-
echo ""
|
|
194
|
-
echo "IMPORTANT - Do not circumvent the loop:"
|
|
195
|
-
echo " Even if you believe you're stuck, the task is impossible,"
|
|
196
|
-
echo " or you've been running too long - you MUST NOT output a"
|
|
197
|
-
echo " false promise statement. The loop is designed to continue"
|
|
198
|
-
echo " until the promise is GENUINELY TRUE. Trust the process."
|
|
199
|
-
echo ""
|
|
200
|
-
echo " If the loop should stop, the promise statement will become"
|
|
201
|
-
echo " true naturally. Do not force it by lying."
|
|
202
|
-
echo "═══════════════════════════════════════════════════════════"
|
|
203
|
-
fi
|