@litmers/cursorflow-orchestrator 0.1.20 → 0.1.26
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/CHANGELOG.md +9 -0
- package/commands/cursorflow-clean.md +19 -0
- package/commands/cursorflow-runs.md +59 -0
- package/commands/cursorflow-stop.md +55 -0
- package/dist/cli/clean.js +171 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +83 -42
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1007 -189
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +4 -3
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +188 -236
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +8 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.d.ts +5 -0
- package/dist/cli/runs.js +214 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/setup-commands.js +0 -0
- package/dist/cli/signal.js +1 -1
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/stop.d.ts +5 -0
- package/dist/cli/stop.js +215 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/tasks.d.ts +10 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +212 -0
- package/dist/core/auto-recovery.js +737 -0
- package/dist/core/auto-recovery.js.map +1 -0
- package/dist/core/failure-policy.d.ts +156 -0
- package/dist/core/failure-policy.js +488 -0
- package/dist/core/failure-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +15 -2
- package/dist/core/orchestrator.js +392 -15
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +2 -0
- package/dist/core/reviewer.js +2 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +33 -10
- package/dist/core/runner.js +321 -146
- package/dist/core/runner.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +67 -0
- package/dist/services/logging/buffer.js +309 -0
- package/dist/services/logging/buffer.js.map +1 -0
- package/dist/services/logging/console.d.ts +89 -0
- package/dist/services/logging/console.js +169 -0
- package/dist/services/logging/console.js.map +1 -0
- package/dist/services/logging/file-writer.d.ts +71 -0
- package/dist/services/logging/file-writer.js +516 -0
- package/dist/services/logging/file-writer.js.map +1 -0
- package/dist/services/logging/formatter.d.ts +39 -0
- package/dist/services/logging/formatter.js +227 -0
- package/dist/services/logging/formatter.js.map +1 -0
- package/dist/services/logging/index.d.ts +11 -0
- package/dist/services/logging/index.js +30 -0
- package/dist/services/logging/index.js.map +1 -0
- package/dist/services/logging/parser.d.ts +31 -0
- package/dist/services/logging/parser.js +222 -0
- package/dist/services/logging/parser.js.map +1 -0
- package/dist/services/process/index.d.ts +59 -0
- package/dist/services/process/index.js +257 -0
- package/dist/services/process/index.js.map +1 -0
- package/dist/types/agent.d.ts +20 -0
- package/dist/types/agent.js +6 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/config.d.ts +65 -0
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +125 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lane.d.ts +43 -0
- package/dist/types/lane.js +6 -0
- package/dist/types/lane.js.map +1 -0
- package/dist/types/logging.d.ts +71 -0
- package/dist/types/logging.js +16 -0
- package/dist/types/logging.js.map +1 -0
- package/dist/types/review.d.ts +17 -0
- package/dist/types/review.js +6 -0
- package/dist/types/review.js.map +1 -0
- package/dist/types/run.d.ts +32 -0
- package/dist/types/run.js +6 -0
- package/dist/types/run.js.map +1 -0
- package/dist/types/task.d.ts +71 -0
- package/dist/types/task.js +6 -0
- package/dist/types/task.js.map +1 -0
- package/dist/ui/components.d.ts +134 -0
- package/dist/ui/components.js +389 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/log-viewer.d.ts +49 -0
- package/dist/ui/log-viewer.js +449 -0
- package/dist/ui/log-viewer.js.map +1 -0
- package/dist/utils/checkpoint.d.ts +87 -0
- package/dist/utils/checkpoint.js +317 -0
- package/dist/utils/checkpoint.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.js +11 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/dependency.d.ts +74 -0
- package/dist/utils/dependency.js +420 -0
- package/dist/utils/dependency.js.map +1 -0
- package/dist/utils/doctor.js +10 -5
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +94 -9
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +322 -2
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.d.ts +91 -0
- package/dist/utils/health.js +556 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/lock.d.ts +95 -0
- package/dist/utils/lock.js +332 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/log-buffer.d.ts +17 -0
- package/dist/utils/log-buffer.js +14 -0
- package/dist/utils/log-buffer.js.map +1 -0
- package/dist/utils/log-constants.d.ts +23 -0
- package/dist/utils/log-constants.js +28 -0
- package/dist/utils/log-constants.js.map +1 -0
- package/dist/utils/log-formatter.d.ts +9 -0
- package/dist/utils/log-formatter.js +113 -70
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/log-service.d.ts +19 -0
- package/dist/utils/log-service.js +47 -0
- package/dist/utils/log-service.js.map +1 -0
- package/dist/utils/logger.d.ts +46 -27
- package/dist/utils/logger.js +82 -60
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/process-manager.d.ts +21 -0
- package/dist/utils/process-manager.js +138 -0
- package/dist/utils/process-manager.js.map +1 -0
- package/dist/utils/retry.d.ts +121 -0
- package/dist/utils/retry.js +374 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/run-service.d.ts +88 -0
- package/dist/utils/run-service.js +412 -0
- package/dist/utils/run-service.js.map +1 -0
- package/dist/utils/state.d.ts +58 -2
- package/dist/utils/state.js +306 -3
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +82 -0
- package/dist/utils/task-service.js +348 -0
- package/dist/utils/task-service.js.map +1 -0
- package/dist/utils/types.d.ts +2 -272
- package/dist/utils/types.js +16 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +38 -23
- package/scripts/ai-security-check.js +0 -1
- package/scripts/local-security-gate.sh +0 -0
- package/scripts/monitor-lanes.sh +94 -0
- package/scripts/patches/test-cursor-agent.js +0 -1
- package/scripts/release.sh +0 -0
- package/scripts/setup-security.sh +0 -0
- package/scripts/stream-logs.sh +72 -0
- package/scripts/verify-and-fix.sh +0 -0
- package/src/cli/clean.ts +180 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/init.ts +1 -1
- package/src/cli/logs.ts +79 -42
- package/src/cli/monitor.ts +1815 -899
- package/src/cli/prepare.ts +4 -3
- package/src/cli/resume.ts +220 -277
- package/src/cli/run.ts +9 -3
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +1 -1
- package/src/cli/stop.ts +209 -0
- package/src/cli/tasks.ts +154 -0
- package/src/core/auto-recovery.ts +909 -0
- package/src/core/failure-policy.ts +592 -0
- package/src/core/orchestrator.ts +1131 -675
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +388 -162
- package/src/services/logging/buffer.ts +326 -0
- package/src/services/logging/console.ts +193 -0
- package/src/services/logging/file-writer.ts +526 -0
- package/src/services/logging/formatter.ts +268 -0
- package/src/services/logging/index.ts +16 -0
- package/src/services/logging/parser.ts +232 -0
- package/src/services/process/index.ts +261 -0
- package/src/types/agent.ts +24 -0
- package/src/types/config.ts +79 -0
- package/src/types/events.ts +156 -0
- package/src/types/index.ts +29 -0
- package/src/types/lane.ts +56 -0
- package/src/types/logging.ts +96 -0
- package/src/types/review.ts +20 -0
- package/src/types/run.ts +37 -0
- package/src/types/task.ts +79 -0
- package/src/ui/components.ts +430 -0
- package/src/ui/log-viewer.ts +485 -0
- package/src/utils/checkpoint.ts +374 -0
- package/src/utils/config.ts +11 -2
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +11 -5
- package/src/utils/enhanced-logger.ts +108 -49
- package/src/utils/git.ts +374 -2
- package/src/utils/health.ts +596 -0
- package/src/utils/lock.ts +346 -0
- package/src/utils/log-buffer.ts +28 -0
- package/src/utils/log-constants.ts +26 -0
- package/src/utils/log-formatter.ts +120 -37
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/process-manager.ts +100 -0
- package/src/utils/retry.ts +413 -0
- package/src/utils/run-service.ts +433 -0
- package/src/utils/state.ts +369 -3
- package/src/utils/task-service.ts +370 -0
- package/src/utils/types.ts +2 -315
package/dist/cli/resume.js
CHANGED
|
@@ -44,6 +44,7 @@ const state_1 = require("../utils/state");
|
|
|
44
44
|
const doctor_1 = require("../utils/doctor");
|
|
45
45
|
const path_1 = require("../utils/path");
|
|
46
46
|
const enhanced_logger_1 = require("../utils/enhanced-logger");
|
|
47
|
+
const log_formatter_1 = require("../utils/log-formatter");
|
|
47
48
|
function printHelp() {
|
|
48
49
|
console.log(`
|
|
49
50
|
Usage: cursorflow resume [lane] [options]
|
|
@@ -51,7 +52,7 @@ Usage: cursorflow resume [lane] [options]
|
|
|
51
52
|
Resume interrupted or failed lanes.
|
|
52
53
|
|
|
53
54
|
Options:
|
|
54
|
-
<lane> Lane name
|
|
55
|
+
<lane> Lane name or tasks directory to resume
|
|
55
56
|
--all Resume ALL incomplete/failed lanes
|
|
56
57
|
--status Show status of all lanes in the run (no resume)
|
|
57
58
|
--run-dir <path> Use a specific run directory (default: latest)
|
|
@@ -67,8 +68,8 @@ Examples:
|
|
|
67
68
|
cursorflow resume --status # Check status of all lanes
|
|
68
69
|
cursorflow resume --all # Resume all incomplete lanes
|
|
69
70
|
cursorflow resume lane-1 # Resume single lane
|
|
71
|
+
cursorflow resume _cursorflow/tasks/feat1 # Resume all lanes in directory
|
|
70
72
|
cursorflow resume --all --restart # Restart all incomplete lanes from task 0
|
|
71
|
-
cursorflow resume --all --max-concurrent 2 # Resume with max 2 parallel lanes
|
|
72
73
|
`);
|
|
73
74
|
}
|
|
74
75
|
function parseArgs(args) {
|
|
@@ -117,79 +118,138 @@ const STATUS_COLORS = {
|
|
|
117
118
|
};
|
|
118
119
|
const RESET = '\x1b[0m';
|
|
119
120
|
/**
|
|
120
|
-
*
|
|
121
|
+
* Check if a process is alive by its PID
|
|
121
122
|
*/
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
case 'thinking':
|
|
175
|
-
prefix = `${logger.COLORS.gray}🤔 THNK${logger.COLORS.reset}`;
|
|
176
|
-
break;
|
|
177
|
-
}
|
|
178
|
-
if (prefix) {
|
|
179
|
-
const lines = content.split('\n');
|
|
180
|
-
const tsPrefix = `${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${logger.COLORS.magenta}${laneLabel}${logger.COLORS.reset}`;
|
|
181
|
-
if (msg.type === 'user' || msg.type === 'assistant' || msg.type === 'result' || msg.type === 'thinking') {
|
|
182
|
-
const header = `${prefix} ┌${'─'.repeat(60)}`;
|
|
183
|
-
process.stdout.write(`${tsPrefix} ${header}\n`);
|
|
184
|
-
for (const line of lines) {
|
|
185
|
-
process.stdout.write(`${tsPrefix} ${' '.repeat((0, enhanced_logger_1.stripAnsi)(prefix).length)} │ ${line}\n`);
|
|
123
|
+
function isProcessAlive(pid) {
|
|
124
|
+
try {
|
|
125
|
+
// On Unix-like systems, sending signal 0 checks if process exists
|
|
126
|
+
process.kill(pid, 0);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check for zombie "running" lanes and fix them
|
|
135
|
+
* A zombie lane is one that has status "running" but its process is dead
|
|
136
|
+
*/
|
|
137
|
+
function checkAndFixZombieLanes(runDir) {
|
|
138
|
+
const lanesDir = (0, path_1.safeJoin)(runDir, 'lanes');
|
|
139
|
+
if (!fs.existsSync(lanesDir)) {
|
|
140
|
+
return { fixed: [], pofCreated: false };
|
|
141
|
+
}
|
|
142
|
+
const fixed = [];
|
|
143
|
+
const zombieDetails = [];
|
|
144
|
+
const laneDirs = fs.readdirSync(lanesDir)
|
|
145
|
+
.filter(f => fs.statSync((0, path_1.safeJoin)(lanesDir, f)).isDirectory());
|
|
146
|
+
for (const laneName of laneDirs) {
|
|
147
|
+
const dir = (0, path_1.safeJoin)(lanesDir, laneName);
|
|
148
|
+
const statePath = (0, path_1.safeJoin)(dir, 'state.json');
|
|
149
|
+
if (!fs.existsSync(statePath))
|
|
150
|
+
continue;
|
|
151
|
+
const state = (0, state_1.loadState)(statePath);
|
|
152
|
+
if (!state)
|
|
153
|
+
continue;
|
|
154
|
+
// Check for zombie: status is "running" but process is dead
|
|
155
|
+
if (state.status === 'running' && state.pid) {
|
|
156
|
+
const alive = isProcessAlive(state.pid);
|
|
157
|
+
if (!alive) {
|
|
158
|
+
logger.warn(`🧟 Zombie lane detected: ${laneName} (PID ${state.pid} is dead)`);
|
|
159
|
+
// Update state to failed
|
|
160
|
+
const updatedState = {
|
|
161
|
+
...state,
|
|
162
|
+
status: 'failed',
|
|
163
|
+
error: `Process terminated unexpectedly (PID ${state.pid} was running but is now dead)`,
|
|
164
|
+
endTime: Date.now(),
|
|
165
|
+
};
|
|
166
|
+
(0, state_1.saveState)(statePath, updatedState);
|
|
167
|
+
fixed.push(laneName);
|
|
168
|
+
zombieDetails.push({
|
|
169
|
+
name: laneName,
|
|
170
|
+
pid: state.pid,
|
|
171
|
+
taskIndex: state.currentTaskIndex,
|
|
172
|
+
totalTasks: state.totalTasks,
|
|
173
|
+
});
|
|
174
|
+
logger.info(` → Status changed to 'failed', ready for resume`);
|
|
186
175
|
}
|
|
187
|
-
process.stdout.write(`${tsPrefix} ${' '.repeat((0, enhanced_logger_1.stripAnsi)(prefix).length)} └${'─'.repeat(60)}\n`);
|
|
188
176
|
}
|
|
189
|
-
|
|
190
|
-
|
|
177
|
+
}
|
|
178
|
+
// Create POF file if any zombies were found
|
|
179
|
+
let pofCreated = false;
|
|
180
|
+
if (zombieDetails.length > 0) {
|
|
181
|
+
const config = (0, config_1.loadConfig)();
|
|
182
|
+
const pofDir = (0, config_1.getPofDir)(config);
|
|
183
|
+
if (!fs.existsSync(pofDir)) {
|
|
184
|
+
fs.mkdirSync(pofDir, { recursive: true });
|
|
185
|
+
}
|
|
186
|
+
const runId = path.basename(runDir);
|
|
187
|
+
const pofPath = (0, path_1.safeJoin)(pofDir, `pof-${runId}.json`);
|
|
188
|
+
let existingPof = null;
|
|
189
|
+
try {
|
|
190
|
+
existingPof = JSON.parse(fs.readFileSync(pofPath, 'utf-8'));
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Ignore errors (file might not exist)
|
|
194
|
+
}
|
|
195
|
+
const pof = {
|
|
196
|
+
title: 'Run Failure Post-mortem',
|
|
197
|
+
runId: path.basename(runDir),
|
|
198
|
+
failureTime: new Date().toISOString(),
|
|
199
|
+
detectedAt: new Date().toISOString(),
|
|
200
|
+
summary: `${zombieDetails.length} lane(s) found with dead processes (zombie state)`,
|
|
201
|
+
rootCause: {
|
|
202
|
+
type: 'ZOMBIE_PROCESS',
|
|
203
|
+
description: 'Lane processes were marked as running but the processes are no longer alive',
|
|
204
|
+
symptoms: [
|
|
205
|
+
'Process PIDs no longer exist in the system',
|
|
206
|
+
'Lanes were stuck in "running" state',
|
|
207
|
+
'No completion or error was recorded before process death',
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
affectedLanes: zombieDetails.map(z => ({
|
|
211
|
+
name: z.name,
|
|
212
|
+
status: 'failed (was: running)',
|
|
213
|
+
task: `[${z.taskIndex + 1}/${z.totalTasks}]`,
|
|
214
|
+
taskIndex: z.taskIndex,
|
|
215
|
+
pid: z.pid,
|
|
216
|
+
reason: 'Process terminated unexpectedly',
|
|
217
|
+
})),
|
|
218
|
+
possibleCauses: [
|
|
219
|
+
'System killed process due to memory pressure (OOM)',
|
|
220
|
+
'User killed process manually (Ctrl+C, kill command)',
|
|
221
|
+
'Agent timeout exceeded and process was terminated',
|
|
222
|
+
'System restart or crash',
|
|
223
|
+
'Agent hung and watchdog terminated it',
|
|
224
|
+
],
|
|
225
|
+
recovery: {
|
|
226
|
+
command: `cursorflow resume --all --run-dir ${runDir}`,
|
|
227
|
+
description: 'Resume all failed lanes from their last checkpoint',
|
|
228
|
+
alternativeCommand: `cursorflow resume --all --restart --run-dir ${runDir}`,
|
|
229
|
+
alternativeDescription: 'Restart all failed lanes from the beginning',
|
|
230
|
+
},
|
|
231
|
+
// Merge with existing POF if present
|
|
232
|
+
previousFailures: existingPof ? [existingPof] : undefined,
|
|
233
|
+
};
|
|
234
|
+
// Use atomic write: write to temp file then rename
|
|
235
|
+
const tempPath = `${pofPath}.${Math.random().toString(36).substring(2, 7)}.tmp`;
|
|
236
|
+
try {
|
|
237
|
+
fs.writeFileSync(tempPath, JSON.stringify(pof, null, 2), 'utf8');
|
|
238
|
+
fs.renameSync(tempPath, pofPath);
|
|
239
|
+
pofCreated = true;
|
|
240
|
+
logger.info(`📋 POF file created: ${pofPath}`);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
// If temp file was created, try to clean it up
|
|
244
|
+
try {
|
|
245
|
+
if (fs.existsSync(tempPath))
|
|
246
|
+
fs.unlinkSync(tempPath);
|
|
247
|
+
}
|
|
248
|
+
catch { /* ignore */ }
|
|
249
|
+
throw err;
|
|
191
250
|
}
|
|
192
251
|
}
|
|
252
|
+
return { fixed, pofCreated };
|
|
193
253
|
}
|
|
194
254
|
/**
|
|
195
255
|
* Get all lane statuses from a run directory
|
|
@@ -334,50 +394,25 @@ function spawnLaneResume(laneName, laneDir, state, options) {
|
|
|
334
394
|
if (options.executor) {
|
|
335
395
|
runnerArgs.push('--executor', options.executor);
|
|
336
396
|
}
|
|
337
|
-
const logManager = (0, enhanced_logger_1.createLogManager)(laneDir, laneName, options.enhancedLogConfig || {}, (msg) =>
|
|
397
|
+
const logManager = (0, enhanced_logger_1.createLogManager)(laneDir, laneName, options.enhancedLogConfig || {}, (msg) => {
|
|
398
|
+
const formatted = (0, log_formatter_1.formatMessageForConsole)(msg, {
|
|
399
|
+
laneLabel: `[${laneName}]`,
|
|
400
|
+
includeTimestamp: true
|
|
401
|
+
});
|
|
402
|
+
process.stdout.write(formatted + '\n');
|
|
403
|
+
});
|
|
338
404
|
const child = (0, child_process_1.spawn)('node', runnerArgs, {
|
|
339
405
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
340
406
|
env: process.env,
|
|
341
407
|
});
|
|
342
|
-
let lineBuffer = '';
|
|
343
408
|
if (child.stdout) {
|
|
344
409
|
child.stdout.on('data', (data) => {
|
|
345
410
|
logManager.writeStdout(data);
|
|
346
|
-
const str = data.toString();
|
|
347
|
-
lineBuffer += str;
|
|
348
|
-
const lines = lineBuffer.split('\n');
|
|
349
|
-
lineBuffer = lines.pop() || '';
|
|
350
|
-
for (const line of lines) {
|
|
351
|
-
const trimmed = line.trim();
|
|
352
|
-
if (trimmed &&
|
|
353
|
-
!trimmed.startsWith('{') &&
|
|
354
|
-
!trimmed.startsWith('[') &&
|
|
355
|
-
!trimmed.includes('{"type"')) {
|
|
356
|
-
process.stdout.write(`${logger.COLORS.gray}[${new Date().toLocaleTimeString('en-US', { hour12: false })}]${logger.COLORS.reset} ${logger.COLORS.magenta}${laneName.padEnd(10)}${logger.COLORS.reset} ${line}\n`);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
411
|
});
|
|
360
412
|
}
|
|
361
413
|
if (child.stderr) {
|
|
362
414
|
child.stderr.on('data', (data) => {
|
|
363
415
|
logManager.writeStderr(data);
|
|
364
|
-
const str = data.toString();
|
|
365
|
-
const lines = str.split('\n');
|
|
366
|
-
for (const line of lines) {
|
|
367
|
-
const trimmed = line.trim();
|
|
368
|
-
if (trimmed) {
|
|
369
|
-
const isStatus = trimmed.startsWith('Preparing worktree') ||
|
|
370
|
-
trimmed.startsWith('Switched to a new branch') ||
|
|
371
|
-
trimmed.startsWith('HEAD is now at') ||
|
|
372
|
-
trimmed.includes('actual output');
|
|
373
|
-
if (isStatus) {
|
|
374
|
-
process.stdout.write(`${logger.COLORS.gray}[${new Date().toLocaleTimeString('en-US', { hour12: false })}]${logger.COLORS.reset} ${logger.COLORS.magenta}${laneName.padEnd(10)}${logger.COLORS.reset} ${trimmed}\n`);
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
process.stderr.write(`${logger.COLORS.red}[${laneName}] ERROR: ${trimmed}${logger.COLORS.reset}\n`);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
416
|
});
|
|
382
417
|
}
|
|
383
418
|
child.on('exit', () => {
|
|
@@ -401,19 +436,7 @@ function waitForChild(child) {
|
|
|
401
436
|
/**
|
|
402
437
|
* Resume multiple lanes with concurrency control and dependency awareness
|
|
403
438
|
*/
|
|
404
|
-
async function
|
|
405
|
-
const allLanes = getAllLaneStatuses(runDir);
|
|
406
|
-
const lanesToResume = allLanes.filter(l => l.needsResume && l.state?.tasksFile);
|
|
407
|
-
const missingTaskInfo = allLanes.filter(l => l.needsResume && !l.state?.tasksFile);
|
|
408
|
-
if (missingTaskInfo.length > 0) {
|
|
409
|
-
logger.warn(`Lanes that haven't started yet and have no task info: ${missingTaskInfo.map(l => l.name).join(', ')}`);
|
|
410
|
-
logger.warn('These lanes cannot be resumed because their original task file paths were not recorded.');
|
|
411
|
-
}
|
|
412
|
-
if (lanesToResume.length === 0) {
|
|
413
|
-
logger.success('All lanes are already completed! Nothing to resume.');
|
|
414
|
-
return { succeeded: [], failed: [], skipped: [] };
|
|
415
|
-
}
|
|
416
|
-
// Check for lanes with unmet dependencies that can never be satisfied
|
|
439
|
+
async function resumeLanes(lanesToResume, allLanes, options) {
|
|
417
440
|
const completedSet = new Set(allLanes.filter(l => l.isCompleted).map(l => l.name));
|
|
418
441
|
const toResumeNames = new Set(lanesToResume.map(l => l.name));
|
|
419
442
|
const skippedLanes = [];
|
|
@@ -436,16 +459,9 @@ async function resumeAllLanes(runDir, options) {
|
|
|
436
459
|
logger.section(`🔁 Resuming ${resolvableLanes.length} Lane(s)`);
|
|
437
460
|
logger.info(`Max concurrent: ${options.maxConcurrent}`);
|
|
438
461
|
logger.info(`Mode: ${options.restart ? 'Restart from beginning' : 'Continue from last task'}`);
|
|
439
|
-
// Show dependency order
|
|
440
|
-
const lanesWithDeps = resolvableLanes.filter(l => l.dependsOn.length > 0);
|
|
441
|
-
if (lanesWithDeps.length > 0) {
|
|
442
|
-
logger.info(`Dependency-aware: ${lanesWithDeps.length} lane(s) have dependencies`);
|
|
443
|
-
}
|
|
444
|
-
console.log('');
|
|
445
462
|
// Run doctor check once if needed (check git status)
|
|
446
463
|
if (!options.skipDoctor) {
|
|
447
464
|
logger.info('Running pre-flight checks...');
|
|
448
|
-
// Use the first lane's tasksDir for doctor check
|
|
449
465
|
const firstLane = resolvableLanes[0];
|
|
450
466
|
const tasksDir = path.dirname(firstLane.state.tasksFile);
|
|
451
467
|
const report = (0, doctor_1.runDoctor)({
|
|
@@ -466,15 +482,10 @@ async function resumeAllLanes(runDir, options) {
|
|
|
466
482
|
}
|
|
467
483
|
const succeeded = [];
|
|
468
484
|
const failed = [];
|
|
469
|
-
// Create a mutable set for tracking completed lanes (including those from this session)
|
|
470
485
|
const sessionCompleted = new Set(completedSet);
|
|
471
|
-
// Queue management with dependency awareness
|
|
472
486
|
const pending = new Set(resolvableLanes.map(l => l.name));
|
|
473
487
|
const active = new Map();
|
|
474
488
|
const laneMap = new Map(resolvableLanes.map(l => [l.name, l]));
|
|
475
|
-
/**
|
|
476
|
-
* Find the next lane that can be started (all dependencies met)
|
|
477
|
-
*/
|
|
478
489
|
const findReadyLane = () => {
|
|
479
490
|
for (const laneName of pending) {
|
|
480
491
|
const lane = laneMap.get(laneName);
|
|
@@ -484,21 +495,15 @@ async function resumeAllLanes(runDir, options) {
|
|
|
484
495
|
}
|
|
485
496
|
return null;
|
|
486
497
|
};
|
|
487
|
-
/**
|
|
488
|
-
* Process lanes with dependency awareness
|
|
489
|
-
*/
|
|
490
498
|
const processNext = () => {
|
|
491
499
|
while (active.size < options.maxConcurrent) {
|
|
492
500
|
const lane = findReadyLane();
|
|
493
501
|
if (!lane) {
|
|
494
|
-
// No lane ready to start
|
|
495
502
|
if (pending.size > 0 && active.size === 0) {
|
|
496
|
-
// Deadlock: pending lanes exist but none can start and none are running
|
|
497
503
|
const pendingList = Array.from(pending).join(', ');
|
|
498
504
|
logger.error(`Deadlock detected! Lanes waiting: ${pendingList}`);
|
|
499
|
-
for (const ln of pending)
|
|
505
|
+
for (const ln of pending)
|
|
500
506
|
failed.push(ln);
|
|
501
|
-
}
|
|
502
507
|
pending.clear();
|
|
503
508
|
}
|
|
504
509
|
break;
|
|
@@ -513,13 +518,12 @@ async function resumeAllLanes(runDir, options) {
|
|
|
513
518
|
enhancedLogConfig: options.enhancedLogConfig,
|
|
514
519
|
});
|
|
515
520
|
active.set(lane.name, child);
|
|
516
|
-
// Handle completion
|
|
517
521
|
waitForChild(child).then(code => {
|
|
518
522
|
active.delete(lane.name);
|
|
519
523
|
if (code === 0) {
|
|
520
524
|
logger.success(`✓ ${lane.name} completed`);
|
|
521
525
|
succeeded.push(lane.name);
|
|
522
|
-
sessionCompleted.add(lane.name);
|
|
526
|
+
sessionCompleted.add(lane.name);
|
|
523
527
|
}
|
|
524
528
|
else if (code === 2) {
|
|
525
529
|
logger.warn(`⚠ ${lane.name} blocked on dependency change`);
|
|
@@ -529,7 +533,6 @@ async function resumeAllLanes(runDir, options) {
|
|
|
529
533
|
logger.error(`✗ ${lane.name} failed (exit ${code})`);
|
|
530
534
|
failed.push(lane.name);
|
|
531
535
|
}
|
|
532
|
-
// Try to start more lanes now that one completed
|
|
533
536
|
processNext();
|
|
534
537
|
}).catch(err => {
|
|
535
538
|
active.delete(lane.name);
|
|
@@ -539,26 +542,20 @@ async function resumeAllLanes(runDir, options) {
|
|
|
539
542
|
});
|
|
540
543
|
}
|
|
541
544
|
};
|
|
542
|
-
// Start initial batch
|
|
543
545
|
processNext();
|
|
544
|
-
// Wait for all to complete
|
|
545
546
|
while (active.size > 0 || pending.size > 0) {
|
|
546
547
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
547
|
-
// Check if we can start more (in case completion handlers haven't triggered processNext yet)
|
|
548
548
|
if (active.size < options.maxConcurrent && pending.size > 0) {
|
|
549
549
|
processNext();
|
|
550
550
|
}
|
|
551
551
|
}
|
|
552
|
-
// Summary
|
|
553
552
|
console.log('');
|
|
554
553
|
logger.section('📊 Resume Summary');
|
|
555
554
|
logger.info(`Succeeded: ${succeeded.length}`);
|
|
556
|
-
if (failed.length > 0)
|
|
555
|
+
if (failed.length > 0)
|
|
557
556
|
logger.error(`Failed: ${failed.length} (${failed.join(', ')})`);
|
|
558
|
-
|
|
559
|
-
if (skippedLanes.length > 0) {
|
|
557
|
+
if (skippedLanes.length > 0)
|
|
560
558
|
logger.warn(`Skipped: ${skippedLanes.length} (${skippedLanes.join(', ')})`);
|
|
561
|
-
}
|
|
562
559
|
return { succeeded, failed, skipped: skippedLanes };
|
|
563
560
|
}
|
|
564
561
|
async function resume(args) {
|
|
@@ -569,7 +566,6 @@ async function resume(args) {
|
|
|
569
566
|
}
|
|
570
567
|
const config = (0, config_1.loadConfig)();
|
|
571
568
|
const logsDir = (0, config_1.getLogsDir)(config);
|
|
572
|
-
// Find run directory
|
|
573
569
|
let runDir = options.runDir;
|
|
574
570
|
if (!runDir) {
|
|
575
571
|
runDir = findLatestRunDir(logsDir);
|
|
@@ -577,110 +573,66 @@ async function resume(args) {
|
|
|
577
573
|
if (!runDir || !fs.existsSync(runDir)) {
|
|
578
574
|
throw new Error(`Run directory not found: ${runDir || 'latest'}. Have you run any tasks yet?`);
|
|
579
575
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
enhancedLogConfig: config.enhancedLogging,
|
|
594
|
-
});
|
|
595
|
-
if (result.failed.length > 0) {
|
|
596
|
-
throw new Error(`${result.failed.length} lane(s) failed to complete`);
|
|
576
|
+
const allLanes = getAllLaneStatuses(runDir);
|
|
577
|
+
let lanesToResume = [];
|
|
578
|
+
// Check if the lane argument is actually a tasks directory
|
|
579
|
+
if (options.lane && fs.existsSync(options.lane) && fs.statSync(options.lane).isDirectory()) {
|
|
580
|
+
const tasksDir = path.resolve(options.lane);
|
|
581
|
+
lanesToResume = allLanes.filter(l => l.needsResume && l.state?.tasksFile && path.resolve(l.state.tasksFile).startsWith(tasksDir));
|
|
582
|
+
if (lanesToResume.length > 0) {
|
|
583
|
+
logger.info(`📂 Task directory detected: ${options.lane}`);
|
|
584
|
+
logger.info(`Resuming ${lanesToResume.length} lane(s) from this directory.`);
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
logger.warn(`No incomplete lanes found using tasks from directory: ${options.lane}`);
|
|
588
|
+
return;
|
|
597
589
|
}
|
|
598
|
-
return;
|
|
599
590
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
// Show status by default if no lane specified
|
|
603
|
-
printAllLaneStatus(runDir);
|
|
604
|
-
console.log('');
|
|
605
|
-
console.log('Usage: cursorflow resume <lane> [options]');
|
|
606
|
-
console.log(' cursorflow resume --all # Resume all incomplete lanes');
|
|
607
|
-
return;
|
|
591
|
+
else if (options.all) {
|
|
592
|
+
lanesToResume = allLanes.filter(l => l.needsResume && l.state?.tasksFile);
|
|
608
593
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
594
|
+
else if (options.lane) {
|
|
595
|
+
const lane = allLanes.find(l => l.name === options.lane);
|
|
596
|
+
if (!lane) {
|
|
597
|
+
throw new Error(`Lane '${options.lane}' not found in run directory.`);
|
|
598
|
+
}
|
|
599
|
+
if (!lane.needsResume) {
|
|
600
|
+
logger.success(`Lane '${options.lane}' is already completed.`);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
lanesToResume = [lane];
|
|
613
604
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
605
|
+
// Check for zombie lanes
|
|
606
|
+
const zombieCheck = checkAndFixZombieLanes(runDir);
|
|
607
|
+
if (zombieCheck.fixed.length > 0) {
|
|
608
|
+
logger.section('🔧 Zombie Lane Recovery');
|
|
609
|
+
logger.info(`Fixed ${zombieCheck.fixed.length} zombie lane(s): ${zombieCheck.fixed.join(', ')}`);
|
|
610
|
+
console.log('');
|
|
617
611
|
}
|
|
618
|
-
if (
|
|
619
|
-
|
|
612
|
+
if (options.status) {
|
|
613
|
+
printAllLaneStatus(runDir);
|
|
614
|
+
return;
|
|
620
615
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
logger.info('Running pre-flight checks...');
|
|
625
|
-
const report = (0, doctor_1.runDoctor)({
|
|
626
|
-
cwd: process.cwd(),
|
|
627
|
-
tasksDir,
|
|
628
|
-
includeCursorAgentChecks: false, // Skip agent checks for resume
|
|
629
|
-
});
|
|
630
|
-
// Only show blocking errors for resume
|
|
631
|
-
const blockingIssues = report.issues.filter(i => i.severity === 'error' &&
|
|
632
|
-
(i.id.startsWith('branch.') || i.id.startsWith('git.')));
|
|
633
|
-
if (blockingIssues.length > 0) {
|
|
634
|
-
logger.section('🛑 Pre-resume check found issues');
|
|
635
|
-
for (const issue of blockingIssues) {
|
|
636
|
-
logger.error(`${issue.title} (${issue.id})`, '❌');
|
|
637
|
-
console.log(` ${issue.message}`);
|
|
638
|
-
if (issue.details)
|
|
639
|
-
console.log(` Details: ${issue.details}`);
|
|
640
|
-
if (issue.fixes?.length) {
|
|
641
|
-
console.log(' Fix:');
|
|
642
|
-
for (const fix of issue.fixes)
|
|
643
|
-
console.log(` - ${fix}`);
|
|
644
|
-
}
|
|
645
|
-
console.log('');
|
|
646
|
-
}
|
|
647
|
-
throw new Error('Pre-resume checks failed. Use --skip-doctor to bypass (not recommended).');
|
|
616
|
+
if (lanesToResume.length === 0) {
|
|
617
|
+
if (options.lane || options.all) {
|
|
618
|
+
logger.success('No lanes need to be resumed.');
|
|
648
619
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (warnings.length > 0) {
|
|
652
|
-
logger.warn(`${warnings.length} warning(s) found. Run 'cursorflow doctor' for details.`);
|
|
620
|
+
else {
|
|
621
|
+
printAllLaneStatus(runDir);
|
|
653
622
|
}
|
|
623
|
+
return;
|
|
654
624
|
}
|
|
655
|
-
|
|
656
|
-
logger.info(`Run: ${path.basename(runDir)}`);
|
|
657
|
-
logger.info(`Tasks: ${state.tasksFile}`);
|
|
658
|
-
logger.info(`Starting from task index: ${options.restart ? 0 : state.currentTaskIndex}`);
|
|
659
|
-
const { child } = spawnLaneResume(options.lane, laneDir, state, {
|
|
625
|
+
const result = await resumeLanes(lanesToResume, allLanes, {
|
|
660
626
|
restart: options.restart,
|
|
627
|
+
maxConcurrent: options.maxConcurrent,
|
|
628
|
+
skipDoctor: options.skipDoctor,
|
|
661
629
|
noGit: options.noGit,
|
|
662
630
|
executor: options.executor,
|
|
663
631
|
enhancedLogConfig: config.enhancedLogging,
|
|
664
632
|
});
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (code === 0) {
|
|
669
|
-
logger.success(`Lane ${options.lane} completed successfully`);
|
|
670
|
-
resolve();
|
|
671
|
-
}
|
|
672
|
-
else if (code === 2) {
|
|
673
|
-
logger.warn(`Lane ${options.lane} blocked on dependency change`);
|
|
674
|
-
resolve();
|
|
675
|
-
}
|
|
676
|
-
else {
|
|
677
|
-
reject(new Error(`Lane ${options.lane} failed with exit code ${code}`));
|
|
678
|
-
}
|
|
679
|
-
});
|
|
680
|
-
child.on('error', (error) => {
|
|
681
|
-
reject(new Error(`Failed to start runner: ${error.message}`));
|
|
682
|
-
});
|
|
683
|
-
});
|
|
633
|
+
if (result.failed.length > 0) {
|
|
634
|
+
throw new Error(`${result.failed.length} lane(s) failed to complete`);
|
|
635
|
+
}
|
|
684
636
|
}
|
|
685
637
|
module.exports = resume;
|
|
686
638
|
//# sourceMappingURL=resume.js.map
|