@sureshsankaran/ralph-wiggum 0.1.0 → 0.1.2
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/dist/index.js +83 -46
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,8 +62,13 @@ function updateIteration(filePath, newIteration) {
|
|
|
62
62
|
*/
|
|
63
63
|
export const RalphWiggumPlugin = async ({ client, directory }) => {
|
|
64
64
|
const stateFilePath = join(directory, STATE_FILE);
|
|
65
|
+
const lockFilePath = join(directory, ".opencode/ralph-loop.lock");
|
|
65
66
|
// Track if completion was detected to prevent race conditions
|
|
66
67
|
let completionDetected = false;
|
|
68
|
+
// Track the last iteration we processed to prevent duplicate handling
|
|
69
|
+
let lastProcessedIteration = 0;
|
|
70
|
+
// Lock to prevent concurrent event handling
|
|
71
|
+
let isProcessing = false;
|
|
67
72
|
return {
|
|
68
73
|
event: async ({ event }) => {
|
|
69
74
|
// Check message.part.updated events for completion promise
|
|
@@ -84,7 +89,14 @@ export const RalphWiggumPlugin = async ({ client, directory }) => {
|
|
|
84
89
|
if (promiseText === state.completion_promise) {
|
|
85
90
|
console.log(`Ralph loop: Detected <promise>${state.completion_promise}</promise> - task complete!`);
|
|
86
91
|
completionDetected = true;
|
|
87
|
-
|
|
92
|
+
try {
|
|
93
|
+
unlinkSync(stateFilePath);
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
try {
|
|
97
|
+
unlinkSync(lockFilePath);
|
|
98
|
+
}
|
|
99
|
+
catch { }
|
|
88
100
|
return;
|
|
89
101
|
}
|
|
90
102
|
}
|
|
@@ -92,62 +104,87 @@ export const RalphWiggumPlugin = async ({ client, directory }) => {
|
|
|
92
104
|
return;
|
|
93
105
|
}
|
|
94
106
|
// Handle session idle to continue the loop
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
// Only listen to session.status (session.idle is deprecated and fires at the same time)
|
|
108
|
+
const isIdle = event.type === "session.status" && event.properties?.status?.type === "idle";
|
|
97
109
|
if (!isIdle)
|
|
98
110
|
return;
|
|
99
111
|
// If completion was already detected, don't continue
|
|
100
112
|
if (completionDetected) {
|
|
101
113
|
return;
|
|
102
114
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (!existsSync(stateFilePath))
|
|
106
|
-
return;
|
|
107
|
-
const content = readFileSync(stateFilePath, "utf-8");
|
|
108
|
-
const state = parseState(content);
|
|
109
|
-
if (!state || !state.active) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
console.log("Ralph loop: session idle, iteration " + state.iteration);
|
|
113
|
-
// Validate numeric fields
|
|
114
|
-
if (isNaN(state.iteration) || isNaN(state.max_iterations)) {
|
|
115
|
-
console.error("Ralph loop: State file corrupted - invalid numeric fields");
|
|
116
|
-
unlinkSync(stateFilePath);
|
|
115
|
+
// Prevent concurrent processing
|
|
116
|
+
if (isProcessing) {
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
if (state.max_iterations > 0 && state.iteration >= state.max_iterations) {
|
|
121
|
-
console.log(`Ralph loop: Max iterations (${state.max_iterations}) reached.`);
|
|
122
|
-
unlinkSync(stateFilePath);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
// Not complete - continue loop with SAME PROMPT
|
|
126
|
-
const nextIteration = state.iteration + 1;
|
|
127
|
-
updateIteration(stateFilePath, nextIteration);
|
|
128
|
-
// Build system message
|
|
129
|
-
const systemMsg = state.completion_promise
|
|
130
|
-
? `Ralph iteration ${nextIteration} | To stop: output <promise>${state.completion_promise}</promise> (ONLY when statement is TRUE - do not lie to exit!)`
|
|
131
|
-
: `Ralph iteration ${nextIteration} | No completion promise set - loop runs infinitely`;
|
|
132
|
-
console.log(systemMsg);
|
|
133
|
-
// Send the same prompt back to continue the session
|
|
119
|
+
isProcessing = true;
|
|
134
120
|
try {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
121
|
+
const sessionID = event.properties?.sessionID;
|
|
122
|
+
// Check if ralph-loop is active
|
|
123
|
+
if (!existsSync(stateFilePath))
|
|
124
|
+
return;
|
|
125
|
+
const content = readFileSync(stateFilePath, "utf-8");
|
|
126
|
+
const state = parseState(content);
|
|
127
|
+
if (!state || !state.active) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Prevent processing the same iteration multiple times
|
|
131
|
+
if (state.iteration <= lastProcessedIteration) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
console.log("Ralph loop: session idle, iteration " + state.iteration);
|
|
135
|
+
// Validate numeric fields
|
|
136
|
+
if (isNaN(state.iteration) || isNaN(state.max_iterations)) {
|
|
137
|
+
console.error("Ralph loop: State file corrupted - invalid numeric fields");
|
|
138
|
+
try {
|
|
139
|
+
unlinkSync(stateFilePath);
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Check if max iterations reached
|
|
145
|
+
if (state.max_iterations > 0 && state.iteration >= state.max_iterations) {
|
|
146
|
+
console.log(`Ralph loop: Max iterations (${state.max_iterations}) reached.`);
|
|
147
|
+
try {
|
|
148
|
+
unlinkSync(stateFilePath);
|
|
149
|
+
}
|
|
150
|
+
catch { }
|
|
151
|
+
try {
|
|
152
|
+
unlinkSync(lockFilePath);
|
|
153
|
+
}
|
|
154
|
+
catch { }
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Not complete - continue loop with SAME PROMPT
|
|
158
|
+
const nextIteration = state.iteration + 1;
|
|
159
|
+
lastProcessedIteration = nextIteration;
|
|
160
|
+
updateIteration(stateFilePath, nextIteration);
|
|
161
|
+
// Build system message
|
|
162
|
+
const systemMsg = state.completion_promise
|
|
163
|
+
? `Ralph iteration ${nextIteration} | To stop: output <promise>${state.completion_promise}</promise> (ONLY when statement is TRUE - do not lie to exit!)`
|
|
164
|
+
: `Ralph iteration ${nextIteration} | No completion promise set - loop runs infinitely`;
|
|
165
|
+
console.log(systemMsg);
|
|
166
|
+
// Send the same prompt back to continue the session
|
|
167
|
+
try {
|
|
168
|
+
if (sessionID) {
|
|
169
|
+
await client.session.promptAsync({
|
|
170
|
+
path: { id: sessionID },
|
|
171
|
+
body: {
|
|
172
|
+
parts: [
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: `[${systemMsg}]\n\n${state.prompt}`,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
console.error("Ralph loop: Failed to send prompt", err);
|
|
147
184
|
}
|
|
148
185
|
}
|
|
149
|
-
|
|
150
|
-
|
|
186
|
+
finally {
|
|
187
|
+
isProcessing = false;
|
|
151
188
|
}
|
|
152
189
|
},
|
|
153
190
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sureshsankaran/ralph-wiggum",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Ralph Wiggum iterative AI development plugin for OpenCode - continuously loops the same prompt until task completion",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|