@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.
Files changed (2) hide show
  1. package/dist/index.js +83 -46
  2. 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
- unlinkSync(stateFilePath);
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
- const isIdle = event.type === "session.idle" ||
96
- (event.type === "session.status" && event.properties?.status?.type === "idle");
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
- const sessionID = event.properties?.sessionID;
104
- // Check if ralph-loop is active
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
- // Check if max iterations reached
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
- if (sessionID) {
136
- await client.session.promptAsync({
137
- path: { id: sessionID },
138
- body: {
139
- parts: [
140
- {
141
- type: "text",
142
- text: `[${systemMsg}]\n\n${state.prompt}`,
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
- catch (err) {
150
- console.error("Ralph loop: Failed to send prompt", err);
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.0",
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",