oh-my-claude-sisyphus 3.8.6 → 3.8.8
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/dist/hooks/keyword-detector/index.js +1 -1
- package/docs/CLAUDE.md +5 -5
- package/package.json +1 -1
- package/scripts/keyword-detector.mjs +4 -2
- package/scripts/persistent-mode.mjs +264 -314
- package/templates/hooks/persistent-mode.mjs +276 -177
|
@@ -1,23 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// OMC Persistent Mode Hook (Node.js)
|
|
3
|
-
// Unified handler for ultrawork, ralph-loop, and todo continuation
|
|
4
|
-
// Cross-platform: Windows, macOS, Linux
|
|
5
|
-
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import { homedir } from 'os';
|
|
9
2
|
|
|
10
3
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
4
|
+
* OMC Persistent Mode Hook (Node.js)
|
|
5
|
+
* Minimal continuation enforcer for all OMC modes.
|
|
6
|
+
* Stripped down for reliability — no optional imports, no PRD, no notepad pruning.
|
|
7
|
+
*
|
|
8
|
+
* Supported modes: ralph, autopilot, ultrapilot, swarm, ultrawork, ecomode, ultraqa, pipeline
|
|
14
9
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId);
|
|
20
|
-
}
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { homedir } from 'os';
|
|
21
14
|
|
|
22
15
|
async function readStdin() {
|
|
23
16
|
const chunks = [];
|
|
@@ -36,19 +29,43 @@ function readJsonFile(path) {
|
|
|
36
29
|
}
|
|
37
30
|
}
|
|
38
31
|
|
|
32
|
+
function writeJsonFile(path, data) {
|
|
33
|
+
try {
|
|
34
|
+
// Ensure directory exists
|
|
35
|
+
const dir = path.substring(0, path.lastIndexOf('/'));
|
|
36
|
+
if (dir && !existsSync(dir)) {
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
/**
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
* Read state file from local or global location, tracking the source.
|
|
48
|
+
*/
|
|
49
|
+
function readStateFile(stateDir, globalStateDir, filename) {
|
|
50
|
+
const localPath = join(stateDir, filename);
|
|
51
|
+
const globalPath = join(globalStateDir, filename);
|
|
52
|
+
|
|
53
|
+
let state = readJsonFile(localPath);
|
|
54
|
+
if (state) return { state, path: localPath };
|
|
55
|
+
|
|
56
|
+
state = readJsonFile(globalPath);
|
|
57
|
+
if (state) return { state, path: globalPath };
|
|
58
|
+
|
|
59
|
+
return { state: null, path: localPath }; // Default to local for new writes
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Count incomplete Tasks from Claude Code's native Task system.
|
|
49
64
|
*/
|
|
50
65
|
function countIncompleteTasks(sessionId) {
|
|
51
|
-
if (!sessionId ||
|
|
66
|
+
if (!sessionId || typeof sessionId !== 'string') return 0;
|
|
67
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) return 0;
|
|
68
|
+
|
|
52
69
|
const taskDir = join(homedir(), '.claude', 'tasks', sessionId);
|
|
53
70
|
if (!existsSync(taskDir)) return 0;
|
|
54
71
|
|
|
@@ -59,232 +76,312 @@ function countIncompleteTasks(sessionId) {
|
|
|
59
76
|
try {
|
|
60
77
|
const content = readFileSync(join(taskDir, file), 'utf-8');
|
|
61
78
|
const task = JSON.parse(content);
|
|
62
|
-
// Match TypeScript isTaskIncomplete(): only pending/in_progress are incomplete
|
|
63
|
-
// 'deleted' and 'completed' are both treated as done
|
|
64
79
|
if (task.status === 'pending' || task.status === 'in_progress') count++;
|
|
65
|
-
} catch { /* skip
|
|
80
|
+
} catch { /* skip */ }
|
|
66
81
|
}
|
|
67
|
-
} catch { /*
|
|
82
|
+
} catch { /* skip */ }
|
|
68
83
|
return count;
|
|
69
84
|
}
|
|
70
85
|
|
|
71
|
-
function writeJsonFile(path, data) {
|
|
72
|
-
try {
|
|
73
|
-
writeFileSync(path, JSON.stringify(data, null, 2));
|
|
74
|
-
return true;
|
|
75
|
-
} catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
86
|
function countIncompleteTodos(todosDir, projectDir) {
|
|
81
87
|
let count = 0;
|
|
82
88
|
|
|
83
|
-
// Check global todos
|
|
84
89
|
if (existsSync(todosDir)) {
|
|
85
90
|
try {
|
|
86
91
|
const files = readdirSync(todosDir).filter(f => f.endsWith('.json'));
|
|
87
92
|
for (const file of files) {
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
try {
|
|
94
|
+
const content = readFileSync(join(todosDir, file), 'utf-8');
|
|
95
|
+
const data = JSON.parse(content);
|
|
96
|
+
const todos = Array.isArray(data) ? data : (Array.isArray(data?.todos) ? data.todos : []);
|
|
90
97
|
count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
|
|
91
|
-
}
|
|
98
|
+
} catch { /* skip */ }
|
|
92
99
|
}
|
|
93
|
-
} catch {}
|
|
100
|
+
} catch { /* skip */ }
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
// Check project todos
|
|
97
103
|
for (const path of [
|
|
98
104
|
join(projectDir, '.omc', 'todos.json'),
|
|
99
105
|
join(projectDir, '.claude', 'todos.json')
|
|
100
106
|
]) {
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
try {
|
|
108
|
+
const data = readJsonFile(path);
|
|
109
|
+
const todos = Array.isArray(data) ? data : (Array.isArray(data?.todos) ? data.todos : []);
|
|
103
110
|
count += todos.filter(t => t.status !== 'completed' && t.status !== 'cancelled').length;
|
|
104
|
-
}
|
|
111
|
+
} catch { /* skip */ }
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
return count;
|
|
108
115
|
}
|
|
109
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Detect if stop was triggered by context-limit related reasons.
|
|
119
|
+
* When context is exhausted, Claude Code needs to stop so it can compact.
|
|
120
|
+
* Blocking these stops causes a deadlock: can't compact because can't stop,
|
|
121
|
+
* can't continue because context is full.
|
|
122
|
+
*
|
|
123
|
+
* See: https://github.com/Yeachan-Heo/oh-my-claudecode/issues/213
|
|
124
|
+
*/
|
|
125
|
+
function isContextLimitStop(data) {
|
|
126
|
+
const reason = (data.stop_reason || data.stopReason || '').toLowerCase();
|
|
127
|
+
|
|
128
|
+
const contextPatterns = [
|
|
129
|
+
'context_limit',
|
|
130
|
+
'context_window',
|
|
131
|
+
'context_exceeded',
|
|
132
|
+
'context_full',
|
|
133
|
+
'max_context',
|
|
134
|
+
'token_limit',
|
|
135
|
+
'max_tokens',
|
|
136
|
+
'conversation_too_long',
|
|
137
|
+
'input_too_long',
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
if (contextPatterns.some(p => reason.includes(p))) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const endTurnReason = (data.end_turn_reason || data.endTurnReason || '').toLowerCase();
|
|
145
|
+
if (endTurnReason && contextPatterns.some(p => endTurnReason.includes(p))) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Detect if stop was triggered by user abort (Ctrl+C, cancel button, etc.)
|
|
154
|
+
*/
|
|
155
|
+
function isUserAbort(data) {
|
|
156
|
+
if (data.user_requested || data.userRequested) return true;
|
|
157
|
+
|
|
158
|
+
const reason = (data.stop_reason || data.stopReason || '').toLowerCase();
|
|
159
|
+
const abortPatterns = [
|
|
160
|
+
'user_cancel', 'user_interrupt', 'ctrl_c', 'manual_stop',
|
|
161
|
+
'aborted', 'abort', 'cancel', 'interrupt',
|
|
162
|
+
];
|
|
163
|
+
return abortPatterns.some(p => reason.includes(p));
|
|
164
|
+
}
|
|
165
|
+
|
|
110
166
|
async function main() {
|
|
111
167
|
try {
|
|
112
168
|
const input = await readStdin();
|
|
113
169
|
let data = {};
|
|
114
170
|
try { data = JSON.parse(input); } catch {}
|
|
115
171
|
|
|
116
|
-
const
|
|
117
|
-
const userRequested = data.user_requested || data.userRequested || false;
|
|
172
|
+
const directory = data.directory || process.cwd();
|
|
118
173
|
const sessionId = data.sessionId || data.session_id || '';
|
|
174
|
+
const todosDir = join(homedir(), '.claude', 'todos');
|
|
175
|
+
const stateDir = join(directory, '.omc', 'state');
|
|
176
|
+
const globalStateDir = join(homedir(), '.omc', 'state');
|
|
119
177
|
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
|
|
178
|
+
// CRITICAL: Never block context-limit stops.
|
|
179
|
+
// Blocking these causes a deadlock where Claude Code cannot compact.
|
|
180
|
+
// See: https://github.com/Yeachan-Heo/oh-my-claudecode/issues/213
|
|
181
|
+
if (isContextLimitStop(data)) {
|
|
123
182
|
console.log(JSON.stringify({ continue: true }));
|
|
124
183
|
return;
|
|
125
184
|
}
|
|
126
185
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|| readJsonFile(join(homedir(), '.claude', 'ultrawork-state.json'));
|
|
133
|
-
|
|
134
|
-
// Check for ralph loop state
|
|
135
|
-
const ralphState = readJsonFile(join(directory, '.omc', 'ralph-state.json'));
|
|
186
|
+
// Respect user abort (Ctrl+C, cancel)
|
|
187
|
+
if (isUserAbort(data)) {
|
|
188
|
+
console.log(JSON.stringify({ continue: true }));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
136
191
|
|
|
137
|
-
//
|
|
138
|
-
const
|
|
192
|
+
// Read all mode states (local-first with fallback to global)
|
|
193
|
+
const ralph = readStateFile(stateDir, globalStateDir, 'ralph-state.json');
|
|
194
|
+
const autopilot = readStateFile(stateDir, globalStateDir, 'autopilot-state.json');
|
|
195
|
+
const ultrapilot = readStateFile(stateDir, globalStateDir, 'ultrapilot-state.json');
|
|
196
|
+
const ultrawork = readStateFile(stateDir, globalStateDir, 'ultrawork-state.json');
|
|
197
|
+
const ecomode = readStateFile(stateDir, globalStateDir, 'ecomode-state.json');
|
|
198
|
+
const ultraqa = readStateFile(stateDir, globalStateDir, 'ultraqa-state.json');
|
|
199
|
+
const pipeline = readStateFile(stateDir, globalStateDir, 'pipeline-state.json');
|
|
139
200
|
|
|
140
|
-
//
|
|
141
|
-
const
|
|
201
|
+
// Swarm uses swarm-summary.json (not swarm-state.json) + marker file
|
|
202
|
+
const swarmMarker = existsSync(join(stateDir, 'swarm-active.marker'));
|
|
203
|
+
const swarmSummary = readJsonFile(join(stateDir, 'swarm-summary.json'));
|
|
142
204
|
|
|
143
|
-
// Count incomplete
|
|
205
|
+
// Count incomplete items
|
|
144
206
|
const taskCount = countIncompleteTasks(sessionId);
|
|
145
|
-
const
|
|
207
|
+
const todoCount = countIncompleteTodos(todosDir, directory);
|
|
208
|
+
const totalIncomplete = taskCount + todoCount;
|
|
146
209
|
|
|
147
|
-
// Priority 1: Ralph Loop
|
|
148
|
-
if (
|
|
149
|
-
const iteration =
|
|
150
|
-
const maxIter =
|
|
210
|
+
// Priority 1: Ralph Loop (explicit persistence mode)
|
|
211
|
+
if (ralph.state?.active) {
|
|
212
|
+
const iteration = ralph.state.iteration || 1;
|
|
213
|
+
const maxIter = ralph.state.max_iterations || 100;
|
|
151
214
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const maxAttempts = verificationState.max_verification_attempts || 3;
|
|
215
|
+
if (iteration < maxIter) {
|
|
216
|
+
ralph.state.iteration = iteration + 1;
|
|
217
|
+
writeJsonFile(ralph.path, ralph.state);
|
|
156
218
|
|
|
157
219
|
console.log(JSON.stringify({
|
|
158
|
-
|
|
159
|
-
reason:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
**Original Task:**
|
|
166
|
-
${verificationState.original_task || ralphState.prompt || 'No task specified'}
|
|
167
|
-
|
|
168
|
-
**Completion Claim:**
|
|
169
|
-
${verificationState.completion_claim || 'Task marked complete'}
|
|
170
|
-
|
|
171
|
-
${verificationState.oracle_feedback ? `**Previous Oracle Feedback (rejected):**
|
|
172
|
-
${verificationState.oracle_feedback}
|
|
173
|
-
` : ''}
|
|
174
|
-
|
|
175
|
-
## MANDATORY VERIFICATION STEPS
|
|
220
|
+
continue: false,
|
|
221
|
+
reason: `[RALPH LOOP - ITERATION ${iteration + 1}/${maxIter}] Work is NOT done. Continue. When complete, output: <promise>${ralph.state.completion_promise || 'DONE'}</promise>\n${ralph.state.prompt ? `Task: ${ralph.state.prompt}` : ''}`
|
|
222
|
+
}));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
176
226
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
227
|
+
// Priority 2: Autopilot (high-level orchestration)
|
|
228
|
+
if (autopilot.state?.active) {
|
|
229
|
+
const phase = autopilot.state.phase || 'unknown';
|
|
230
|
+
if (phase !== 'complete') {
|
|
231
|
+
const newCount = (autopilot.state.reinforcement_count || 0) + 1;
|
|
232
|
+
if (newCount <= 20) {
|
|
233
|
+
autopilot.state.reinforcement_count = newCount;
|
|
234
|
+
writeJsonFile(autopilot.path, autopilot.state);
|
|
235
|
+
|
|
236
|
+
console.log(JSON.stringify({
|
|
237
|
+
continue: false,
|
|
238
|
+
reason: `[AUTOPILOT - Phase: ${phase}] Autopilot not complete. Continue working.`
|
|
239
|
+
}));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
181
244
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
245
|
+
// Priority 3: Ultrapilot (parallel autopilot)
|
|
246
|
+
if (ultrapilot.state?.active) {
|
|
247
|
+
const workers = ultrapilot.state.workers || [];
|
|
248
|
+
const incomplete = workers.filter(w => w.status !== 'complete' && w.status !== 'failed').length;
|
|
249
|
+
if (incomplete > 0) {
|
|
250
|
+
const newCount = (ultrapilot.state.reinforcement_count || 0) + 1;
|
|
251
|
+
if (newCount <= 20) {
|
|
252
|
+
ultrapilot.state.reinforcement_count = newCount;
|
|
253
|
+
writeJsonFile(ultrapilot.path, ultrapilot.state);
|
|
254
|
+
|
|
255
|
+
console.log(JSON.stringify({
|
|
256
|
+
continue: false,
|
|
257
|
+
reason: `[ULTRAPILOT] ${incomplete} workers still running. Continue.`
|
|
258
|
+
}));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
188
263
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
264
|
+
// Priority 4: Swarm (coordinated agents with SQLite)
|
|
265
|
+
if (swarmMarker && swarmSummary?.active) {
|
|
266
|
+
const pending = (swarmSummary.tasks_pending || 0) + (swarmSummary.tasks_claimed || 0);
|
|
267
|
+
if (pending > 0) {
|
|
268
|
+
const newCount = (swarmSummary.reinforcement_count || 0) + 1;
|
|
269
|
+
if (newCount <= 15) {
|
|
270
|
+
swarmSummary.reinforcement_count = newCount;
|
|
271
|
+
writeJsonFile(join(stateDir, 'swarm-summary.json'), swarmSummary);
|
|
272
|
+
|
|
273
|
+
console.log(JSON.stringify({
|
|
274
|
+
continue: false,
|
|
275
|
+
reason: `[SWARM ACTIVE] ${pending} tasks remain. Continue working.`
|
|
276
|
+
}));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
192
281
|
|
|
193
|
-
|
|
282
|
+
// Priority 5: Pipeline (sequential stages)
|
|
283
|
+
if (pipeline.state?.active) {
|
|
284
|
+
const currentStage = pipeline.state.current_stage || 0;
|
|
285
|
+
const totalStages = pipeline.state.stages?.length || 0;
|
|
286
|
+
if (currentStage < totalStages) {
|
|
287
|
+
const newCount = (pipeline.state.reinforcement_count || 0) + 1;
|
|
288
|
+
if (newCount <= 15) {
|
|
289
|
+
pipeline.state.reinforcement_count = newCount;
|
|
290
|
+
writeJsonFile(pipeline.path, pipeline.state);
|
|
291
|
+
|
|
292
|
+
console.log(JSON.stringify({
|
|
293
|
+
continue: false,
|
|
294
|
+
reason: `[PIPELINE - Stage ${currentStage + 1}/${totalStages}] Pipeline not complete. Continue.`
|
|
295
|
+
}));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
194
300
|
|
|
195
|
-
|
|
301
|
+
// Priority 6: UltraQA (QA cycling)
|
|
302
|
+
if (ultraqa.state?.active) {
|
|
303
|
+
const cycle = ultraqa.state.cycle || 1;
|
|
304
|
+
const maxCycles = ultraqa.state.max_cycles || 10;
|
|
305
|
+
if (cycle < maxCycles && !ultraqa.state.all_passing) {
|
|
306
|
+
ultraqa.state.cycle = cycle + 1;
|
|
307
|
+
writeJsonFile(ultraqa.path, ultraqa.state);
|
|
196
308
|
|
|
197
|
-
|
|
198
|
-
|
|
309
|
+
console.log(JSON.stringify({
|
|
310
|
+
continue: false,
|
|
311
|
+
reason: `[ULTRAQA - Cycle ${cycle + 1}/${maxCycles}] Tests not all passing. Continue fixing.`
|
|
199
312
|
}));
|
|
200
313
|
return;
|
|
201
314
|
}
|
|
315
|
+
}
|
|
202
316
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
317
|
+
// Priority 7: Ultrawork with incomplete todos/tasks
|
|
318
|
+
if (ultrawork.state?.active && totalIncomplete > 0) {
|
|
319
|
+
const newCount = (ultrawork.state.reinforcement_count || 0) + 1;
|
|
320
|
+
const maxReinforcements = ultrawork.state.max_reinforcements || 15;
|
|
207
321
|
|
|
322
|
+
if (newCount > maxReinforcements) {
|
|
208
323
|
console.log(JSON.stringify({
|
|
209
|
-
|
|
210
|
-
reason:
|
|
211
|
-
|
|
212
|
-
[RALPH LOOP - ITERATION ${newIter}/${maxIter}]
|
|
213
|
-
|
|
214
|
-
Your previous attempt did not output the completion promise. The work is NOT done yet.
|
|
215
|
-
|
|
216
|
-
CRITICAL INSTRUCTIONS:
|
|
217
|
-
1. Review your progress and the original task
|
|
218
|
-
2. Check your todo list - are ALL items marked complete?
|
|
219
|
-
3. Continue from where you left off
|
|
220
|
-
4. When FULLY complete, output: <promise>${ralphState.completion_promise || 'TASK_COMPLETE'}</promise>
|
|
221
|
-
5. Do NOT stop until the task is truly done
|
|
222
|
-
|
|
223
|
-
${ralphState.prompt ? `Original task: ${ralphState.prompt}` : ''}
|
|
224
|
-
|
|
225
|
-
</ralph-loop-continuation>
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
`
|
|
324
|
+
continue: true,
|
|
325
|
+
reason: `[ULTRAWORK ESCAPE] Max reinforcements reached. Allowing stop.`
|
|
229
326
|
}));
|
|
230
327
|
return;
|
|
231
328
|
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Priority 2: Ultrawork with incomplete todos
|
|
235
|
-
if (ultraworkState?.active && totalIncomplete > 0) {
|
|
236
|
-
const newCount = (ultraworkState.reinforcement_count || 0) + 1;
|
|
237
|
-
ultraworkState.reinforcement_count = newCount;
|
|
238
|
-
ultraworkState.last_checked_at = new Date().toISOString();
|
|
239
329
|
|
|
240
|
-
|
|
330
|
+
ultrawork.state.reinforcement_count = newCount;
|
|
331
|
+
ultrawork.state.last_checked_at = new Date().toISOString();
|
|
332
|
+
writeJsonFile(ultrawork.path, ultrawork.state);
|
|
241
333
|
|
|
334
|
+
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
242
335
|
console.log(JSON.stringify({
|
|
243
|
-
|
|
244
|
-
reason:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
Your ultrawork session is NOT complete. ${totalIncomplete} incomplete items remain.
|
|
249
|
-
|
|
250
|
-
REMEMBER THE ULTRAWORK RULES:
|
|
251
|
-
- **PARALLEL**: Fire independent calls simultaneously - NEVER wait sequentially
|
|
252
|
-
- **BACKGROUND FIRST**: Use Task(run_in_background=true) for exploration (10+ concurrent)
|
|
253
|
-
- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each
|
|
254
|
-
- **VERIFY**: Check ALL requirements met before done
|
|
255
|
-
- **NO Premature Stopping**: ALL TODOs must be complete
|
|
336
|
+
continue: false,
|
|
337
|
+
reason: `[ULTRAWORK #${newCount}] ${totalIncomplete} incomplete ${itemType}. Continue working.\n${ultrawork.state.original_prompt ? `Task: ${ultrawork.state.original_prompt}` : ''}`
|
|
338
|
+
}));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
256
341
|
|
|
257
|
-
|
|
342
|
+
// Priority 8: Ecomode with incomplete todos/tasks
|
|
343
|
+
if (ecomode.state?.active && totalIncomplete > 0) {
|
|
344
|
+
const newCount = (ecomode.state.reinforcement_count || 0) + 1;
|
|
345
|
+
const maxReinforcements = ecomode.state.max_reinforcements || 15;
|
|
258
346
|
|
|
259
|
-
|
|
347
|
+
if (newCount > maxReinforcements) {
|
|
348
|
+
console.log(JSON.stringify({
|
|
349
|
+
continue: true,
|
|
350
|
+
reason: `[ECOMODE ESCAPE] Max reinforcements reached. Allowing stop.`
|
|
351
|
+
}));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
260
354
|
|
|
261
|
-
|
|
355
|
+
ecomode.state.reinforcement_count = newCount;
|
|
356
|
+
writeJsonFile(ecomode.path, ecomode.state);
|
|
262
357
|
|
|
263
|
-
|
|
264
|
-
|
|
358
|
+
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
359
|
+
console.log(JSON.stringify({
|
|
360
|
+
continue: false,
|
|
361
|
+
reason: `[ECOMODE #${newCount}] ${totalIncomplete} incomplete ${itemType}. Continue working.`
|
|
265
362
|
}));
|
|
266
363
|
return;
|
|
267
364
|
}
|
|
268
365
|
|
|
269
|
-
// Priority
|
|
366
|
+
// Priority 9: Generic Task/Todo continuation (no specific mode)
|
|
270
367
|
if (totalIncomplete > 0) {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
[SYSTEM REMINDER - CONTINUATION]
|
|
277
|
-
|
|
278
|
-
Incomplete ${itemType} remain (${totalIncomplete} remaining). Continue working on the next pending item.
|
|
368
|
+
const contFile = join(stateDir, 'continuation-count.json');
|
|
369
|
+
let contState = readJsonFile(contFile) || { count: 0 };
|
|
370
|
+
contState.count = (contState.count || 0) + 1;
|
|
371
|
+
writeJsonFile(contFile, contState);
|
|
279
372
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
373
|
+
if (contState.count > 15) {
|
|
374
|
+
console.log(JSON.stringify({
|
|
375
|
+
continue: true,
|
|
376
|
+
reason: `[CONTINUATION ESCAPE] Max continuations reached. Allowing stop.`
|
|
377
|
+
}));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
285
380
|
|
|
286
|
-
|
|
287
|
-
|
|
381
|
+
const itemType = taskCount > 0 ? 'Tasks' : 'todos';
|
|
382
|
+
console.log(JSON.stringify({
|
|
383
|
+
continue: false,
|
|
384
|
+
reason: `[CONTINUATION ${contState.count}/15] ${totalIncomplete} incomplete ${itemType}. Continue working.`
|
|
288
385
|
}));
|
|
289
386
|
return;
|
|
290
387
|
}
|
|
@@ -292,6 +389,8 @@ Incomplete ${itemType} remain (${totalIncomplete} remaining). Continue working o
|
|
|
292
389
|
// No blocking needed
|
|
293
390
|
console.log(JSON.stringify({ continue: true }));
|
|
294
391
|
} catch (error) {
|
|
392
|
+
// On any error, allow stop rather than blocking forever
|
|
393
|
+
console.error(`[persistent-mode] Error: ${error.message}`);
|
|
295
394
|
console.log(JSON.stringify({ continue: true }));
|
|
296
395
|
}
|
|
297
396
|
}
|