@litmers/cursorflow-orchestrator 0.1.40 → 0.2.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/CHANGELOG.md +0 -2
- package/README.md +7 -3
- package/commands/cursorflow-init.md +0 -4
- package/dist/cli/logs.js +108 -9
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/models.js +20 -3
- package/dist/cli/models.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -10
- package/dist/cli/monitor.js +1088 -1240
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/resume.js +21 -1
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +28 -9
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/signal.d.ts +6 -1
- package/dist/cli/signal.js +94 -12
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/tasks.js +3 -46
- package/dist/cli/tasks.js.map +1 -1
- package/dist/core/agent-supervisor.d.ts +23 -0
- package/dist/core/agent-supervisor.js +42 -0
- package/dist/core/agent-supervisor.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +2 -1
- package/dist/core/auto-recovery.js +6 -1
- package/dist/core/auto-recovery.js.map +1 -1
- package/dist/core/failure-policy.d.ts +0 -1
- package/dist/core/failure-policy.js +0 -1
- package/dist/core/failure-policy.js.map +1 -1
- package/dist/core/git-lifecycle-manager.d.ts +284 -0
- package/dist/core/git-lifecycle-manager.js +778 -0
- package/dist/core/git-lifecycle-manager.js.map +1 -0
- package/dist/core/git-pipeline-coordinator.d.ts +21 -0
- package/dist/core/git-pipeline-coordinator.js +205 -0
- package/dist/core/git-pipeline-coordinator.js.map +1 -0
- package/dist/core/intervention.d.ts +176 -0
- package/dist/core/intervention.js +424 -0
- package/dist/core/intervention.js.map +1 -0
- package/dist/core/lane-state-machine.d.ts +423 -0
- package/dist/core/lane-state-machine.js +890 -0
- package/dist/core/lane-state-machine.js.map +1 -0
- package/dist/core/orchestrator.d.ts +4 -1
- package/dist/core/orchestrator.js +29 -62
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.d.ts +7 -1
- package/dist/core/runner/agent.js +45 -30
- package/dist/core/runner/agent.js.map +1 -1
- package/dist/core/runner/pipeline.js +283 -123
- package/dist/core/runner/pipeline.js.map +1 -1
- package/dist/core/runner/task.d.ts +4 -5
- package/dist/core/runner/task.js +6 -80
- package/dist/core/runner/task.js.map +1 -1
- package/dist/core/runner.js +8 -2
- package/dist/core/runner.js.map +1 -1
- package/dist/core/stall-detection.d.ts +11 -4
- package/dist/core/stall-detection.js +62 -27
- package/dist/core/stall-detection.js.map +1 -1
- package/dist/hooks/contexts/index.d.ts +104 -0
- package/dist/hooks/contexts/index.js +134 -0
- package/dist/hooks/contexts/index.js.map +1 -0
- package/dist/hooks/data-accessor.d.ts +86 -0
- package/dist/hooks/data-accessor.js +410 -0
- package/dist/hooks/data-accessor.js.map +1 -0
- package/dist/hooks/flow-controller.d.ts +136 -0
- package/dist/hooks/flow-controller.js +351 -0
- package/dist/hooks/flow-controller.js.map +1 -0
- package/dist/hooks/index.d.ts +68 -0
- package/dist/hooks/index.js +105 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/manager.d.ts +129 -0
- package/dist/hooks/manager.js +389 -0
- package/dist/hooks/manager.js.map +1 -0
- package/dist/hooks/types.d.ts +463 -0
- package/dist/hooks/types.js +45 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/services/logging/buffer.d.ts +2 -2
- package/dist/services/logging/buffer.js +95 -42
- package/dist/services/logging/buffer.js.map +1 -1
- package/dist/services/logging/console.js +6 -1
- package/dist/services/logging/console.js.map +1 -1
- package/dist/services/logging/formatter.d.ts +9 -4
- package/dist/services/logging/formatter.js +64 -18
- package/dist/services/logging/formatter.js.map +1 -1
- package/dist/services/logging/index.d.ts +0 -1
- package/dist/services/logging/index.js +0 -1
- package/dist/services/logging/index.js.map +1 -1
- package/dist/services/logging/paths.d.ts +8 -0
- package/dist/services/logging/paths.js +48 -0
- package/dist/services/logging/paths.js.map +1 -0
- package/dist/services/logging/raw-log.d.ts +6 -0
- package/dist/services/logging/raw-log.js +37 -0
- package/dist/services/logging/raw-log.js.map +1 -0
- package/dist/services/process/index.js +1 -1
- package/dist/services/process/index.js.map +1 -1
- package/dist/types/agent.d.ts +15 -0
- package/dist/types/config.d.ts +22 -1
- package/dist/types/event-categories.d.ts +601 -0
- package/dist/types/event-categories.js +233 -0
- package/dist/types/event-categories.js.map +1 -0
- package/dist/types/events.d.ts +0 -20
- package/dist/types/flow.d.ts +10 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +17 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/lane.d.ts +1 -1
- package/dist/types/logging.d.ts +1 -1
- package/dist/types/task.d.ts +12 -1
- package/dist/ui/log-viewer.d.ts +3 -0
- package/dist/ui/log-viewer.js +3 -0
- package/dist/ui/log-viewer.js.map +1 -1
- package/dist/utils/config.js +10 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.d.ts +11 -1
- package/dist/utils/cursor-agent.js +63 -16
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +5 -1
- package/dist/utils/enhanced-logger.js +98 -19
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/event-registry.d.ts +222 -0
- package/dist/utils/event-registry.js +463 -0
- package/dist/utils/event-registry.js.map +1 -0
- package/dist/utils/events.d.ts +1 -13
- package/dist/utils/events.js.map +1 -1
- package/dist/utils/flow.d.ts +10 -0
- package/dist/utils/flow.js +75 -0
- package/dist/utils/flow.js.map +1 -1
- package/dist/utils/log-constants.d.ts +1 -0
- package/dist/utils/log-constants.js +2 -1
- package/dist/utils/log-constants.js.map +1 -1
- package/dist/utils/log-formatter.d.ts +2 -1
- package/dist/utils/log-formatter.js +10 -10
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.js +82 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/repro-thinking-logs.js +0 -13
- package/dist/utils/repro-thinking-logs.js.map +1 -1
- package/dist/utils/run-service.js +1 -1
- package/dist/utils/run-service.js.map +1 -1
- package/examples/README.md +0 -2
- package/examples/demo-project/README.md +1 -2
- package/package.json +18 -28
- package/scripts/setup-security.sh +0 -1
- package/scripts/test-log-parser.ts +171 -0
- package/scripts/verify-change.sh +272 -0
- package/src/cli/logs.ts +121 -10
- package/src/cli/models.ts +20 -3
- package/src/cli/monitor.ts +1257 -1342
- package/src/cli/resume.ts +27 -1
- package/src/cli/run.ts +29 -11
- package/src/cli/signal.ts +115 -17
- package/src/cli/tasks.ts +2 -59
- package/src/core/agent-supervisor.ts +64 -0
- package/src/core/auto-recovery.ts +7 -1
- package/src/core/failure-policy.ts +0 -1
- package/src/core/git-lifecycle-manager.ts +1011 -0
- package/src/core/git-pipeline-coordinator.ts +221 -0
- package/src/core/intervention.ts +481 -0
- package/src/core/lane-state-machine.ts +1097 -0
- package/src/core/orchestrator.ts +35 -61
- package/src/core/runner/agent.ts +66 -33
- package/src/core/runner/pipeline.ts +318 -138
- package/src/core/runner/task.ts +12 -97
- package/src/core/runner.ts +8 -2
- package/src/core/stall-detection.ts +72 -27
- package/src/hooks/contexts/index.ts +256 -0
- package/src/hooks/data-accessor.ts +488 -0
- package/src/hooks/flow-controller.ts +425 -0
- package/src/hooks/index.ts +154 -0
- package/src/hooks/manager.ts +434 -0
- package/src/hooks/types.ts +544 -0
- package/src/services/logging/buffer.ts +104 -43
- package/src/services/logging/console.ts +7 -1
- package/src/services/logging/formatter.ts +74 -18
- package/src/services/logging/index.ts +0 -2
- package/src/services/logging/paths.ts +14 -0
- package/src/services/logging/raw-log.ts +43 -0
- package/src/services/process/index.ts +1 -1
- package/src/types/agent.ts +15 -0
- package/src/types/config.ts +23 -1
- package/src/types/event-categories.ts +663 -0
- package/src/types/events.ts +0 -25
- package/src/types/flow.ts +10 -6
- package/src/types/index.ts +50 -4
- package/src/types/lane.ts +1 -2
- package/src/types/logging.ts +2 -1
- package/src/types/task.ts +12 -1
- package/src/ui/log-viewer.ts +3 -0
- package/src/utils/config.ts +11 -1
- package/src/utils/cursor-agent.ts +68 -16
- package/src/utils/enhanced-logger.ts +105 -19
- package/src/utils/event-registry.ts +595 -0
- package/src/utils/events.ts +0 -16
- package/src/utils/flow.ts +83 -0
- package/src/utils/log-constants.ts +2 -1
- package/src/utils/log-formatter.ts +10 -11
- package/src/utils/logger.ts +49 -3
- package/src/utils/repro-thinking-logs.ts +0 -15
- package/src/utils/run-service.ts +1 -1
- package/dist/services/logging/file-writer.d.ts +0 -71
- package/dist/services/logging/file-writer.js +0 -516
- package/dist/services/logging/file-writer.js.map +0 -1
- package/dist/types/review.d.ts +0 -17
- package/dist/types/review.js +0 -6
- package/dist/types/review.js.map +0 -1
- package/scripts/ai-security-check.js +0 -233
- package/src/services/logging/file-writer.ts +0 -526
- package/src/types/review.ts +0 -20
package/src/cli/resume.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
ParsedMessage
|
|
19
19
|
} from '../utils/enhanced-logger';
|
|
20
20
|
import { formatMessageForConsole } from '../utils/log-formatter';
|
|
21
|
+
import { MAIN_LOG_FILENAME } from '../utils/log-constants';
|
|
21
22
|
|
|
22
23
|
interface ResumeOptions {
|
|
23
24
|
lane: string | null;
|
|
@@ -30,6 +31,7 @@ interface ResumeOptions {
|
|
|
30
31
|
maxConcurrent: number;
|
|
31
32
|
help: boolean;
|
|
32
33
|
executor: string | null;
|
|
34
|
+
browser: boolean;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
function printHelp(): void {
|
|
@@ -48,6 +50,7 @@ Options:
|
|
|
48
50
|
--restart Restart from the first task (index 0)
|
|
49
51
|
--skip-doctor Skip environment/branch checks (not recommended)
|
|
50
52
|
--executor <type> Override executor (default: cursor-agent)
|
|
53
|
+
--browser Enable browser automation for all resumed tasks
|
|
51
54
|
--help, -h Show help
|
|
52
55
|
|
|
53
56
|
Examples:
|
|
@@ -56,6 +59,7 @@ Examples:
|
|
|
56
59
|
cursorflow resume lane-1 # Resume single lane
|
|
57
60
|
cursorflow resume _cursorflow/flows/MyFeature # Resume all lanes in flow
|
|
58
61
|
cursorflow resume --all --restart # Restart all incomplete lanes from task 0
|
|
62
|
+
cursorflow resume --all --browser # Resume all lanes with browser mode enabled
|
|
59
63
|
`);
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -75,6 +79,7 @@ function parseArgs(args: string[]): ResumeOptions {
|
|
|
75
79
|
maxConcurrent: maxConcurrentIdx >= 0 ? parseInt(args[maxConcurrentIdx + 1] || '3') : 3,
|
|
76
80
|
help: args.includes('--help') || args.includes('-h'),
|
|
77
81
|
executor: executorIdx >= 0 ? args[executorIdx + 1] || null : null,
|
|
82
|
+
browser: args.includes('--browser'),
|
|
78
83
|
};
|
|
79
84
|
}
|
|
80
85
|
|
|
@@ -103,7 +108,6 @@ const STATUS_COLORS: Record<string, string> = {
|
|
|
103
108
|
failed: '\x1b[31m', // red
|
|
104
109
|
paused: '\x1b[35m', // magenta
|
|
105
110
|
waiting: '\x1b[33m', // yellow
|
|
106
|
-
reviewing: '\x1b[36m', // cyan
|
|
107
111
|
unknown: '\x1b[90m', // gray
|
|
108
112
|
};
|
|
109
113
|
const RESET = '\x1b[0m';
|
|
@@ -396,6 +400,7 @@ function spawnLaneResume(
|
|
|
396
400
|
restart: boolean;
|
|
397
401
|
pipelineBranch?: string;
|
|
398
402
|
executor?: string | null;
|
|
403
|
+
browser?: boolean;
|
|
399
404
|
enhancedLogConfig?: any;
|
|
400
405
|
laneIndex?: number;
|
|
401
406
|
}
|
|
@@ -425,6 +430,11 @@ function spawnLaneResume(
|
|
|
425
430
|
runnerArgs.push('--executor', options.executor);
|
|
426
431
|
}
|
|
427
432
|
|
|
433
|
+
// Pass browser flag if provided
|
|
434
|
+
if (options.browser) {
|
|
435
|
+
runnerArgs.push('--browser');
|
|
436
|
+
}
|
|
437
|
+
|
|
428
438
|
// Generate lane label: [laneIdx-taskIdx-laneName] format, padded to 18 chars
|
|
429
439
|
// Note: taskName will be updated dynamically via logManager.setTask()
|
|
430
440
|
let currentTaskName = '';
|
|
@@ -493,6 +503,7 @@ async function resumeLanes(
|
|
|
493
503
|
maxConcurrent: number;
|
|
494
504
|
skipDoctor: boolean;
|
|
495
505
|
executor: string | null;
|
|
506
|
+
browser: boolean;
|
|
496
507
|
enhancedLogConfig?: any;
|
|
497
508
|
}
|
|
498
509
|
): Promise<{ succeeded: string[]; failed: string[]; skipped: string[] }> {
|
|
@@ -587,10 +598,21 @@ async function resumeLanes(
|
|
|
587
598
|
const { child } = spawnLaneResume(lane.name, lane.dir, lane.state!, {
|
|
588
599
|
restart: options.restart,
|
|
589
600
|
executor: options.executor,
|
|
601
|
+
browser: options.browser,
|
|
590
602
|
enhancedLogConfig: options.enhancedLogConfig,
|
|
591
603
|
laneIndex: laneIndexMap.get(lane.name) ?? 1,
|
|
592
604
|
});
|
|
593
605
|
|
|
606
|
+
// Update state.json with the new PID for monitor tracking
|
|
607
|
+
const statePath = safeJoin(lane.dir, 'state.json');
|
|
608
|
+
const updatedState: LaneState = {
|
|
609
|
+
...lane.state!,
|
|
610
|
+
status: 'running',
|
|
611
|
+
pid: child.pid,
|
|
612
|
+
error: null,
|
|
613
|
+
};
|
|
614
|
+
saveState(statePath, updatedState);
|
|
615
|
+
|
|
594
616
|
active.set(lane.name, child);
|
|
595
617
|
|
|
596
618
|
waitForChild(child).then(code => {
|
|
@@ -664,6 +686,9 @@ async function resume(args: string[]): Promise<void> {
|
|
|
664
686
|
if (!runDir || !fs.existsSync(runDir)) {
|
|
665
687
|
throw new Error(`Run directory not found: ${runDir || 'latest'}. Have you run any tasks yet?`);
|
|
666
688
|
}
|
|
689
|
+
|
|
690
|
+
logger.setDefaultContext('MAIN');
|
|
691
|
+
logger.setLogFile(safeJoin(runDir, MAIN_LOG_FILENAME));
|
|
667
692
|
|
|
668
693
|
const allLanes = getAllLaneStatuses(runDir);
|
|
669
694
|
let lanesToResume: LaneInfo[] = [];
|
|
@@ -736,6 +761,7 @@ async function resume(args: string[]): Promise<void> {
|
|
|
736
761
|
maxConcurrent: options.maxConcurrent,
|
|
737
762
|
skipDoctor: options.skipDoctor,
|
|
738
763
|
executor: options.executor,
|
|
764
|
+
browser: options.browser,
|
|
739
765
|
enhancedLogConfig: config.enhancedLogging,
|
|
740
766
|
});
|
|
741
767
|
|
package/src/cli/run.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { getLogsDir, loadConfig } from '../utils/config';
|
|
|
10
10
|
import { runDoctor, getDoctorStatus } from '../utils/doctor';
|
|
11
11
|
import { areCommandsInstalled, setupCommands } from './setup-commands';
|
|
12
12
|
import { safeJoin } from '../utils/path';
|
|
13
|
-
import { findFlowDir } from '../utils/flow';
|
|
13
|
+
import { findFlowDir, findLatestFlowOrTask } from '../utils/flow';
|
|
14
14
|
import { loadState } from '../utils/state';
|
|
15
15
|
import { LaneState } from '../types';
|
|
16
16
|
|
|
@@ -119,6 +119,7 @@ interface RunOptions {
|
|
|
119
119
|
skipDoctor: boolean;
|
|
120
120
|
skipPreflight: boolean;
|
|
121
121
|
raw: boolean;
|
|
122
|
+
browser: boolean;
|
|
122
123
|
help: boolean;
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -138,6 +139,7 @@ Options:
|
|
|
138
139
|
--skip-doctor Skip environment checks (not recommended)
|
|
139
140
|
--skip-preflight Skip preflight checks (Git remote, etc.)
|
|
140
141
|
--raw Save raw logs (absolute raw, no processing)
|
|
142
|
+
--browser Enable browser automation for all tasks
|
|
141
143
|
--dry-run Show execution plan without starting agents
|
|
142
144
|
--help, -h Show help
|
|
143
145
|
|
|
@@ -160,6 +162,7 @@ function parseArgs(args: string[]): RunOptions {
|
|
|
160
162
|
skipDoctor: args.includes('--skip-doctor') || args.includes('--no-doctor'),
|
|
161
163
|
skipPreflight: args.includes('--skip-preflight'),
|
|
162
164
|
raw: args.includes('--raw'),
|
|
165
|
+
browser: args.includes('--browser'),
|
|
163
166
|
help: args.includes('--help') || args.includes('-h'),
|
|
164
167
|
};
|
|
165
168
|
}
|
|
@@ -182,11 +185,6 @@ async function run(args: string[]): Promise<void> {
|
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
|
|
185
|
-
if (!options.tasksDir) {
|
|
186
|
-
console.log('\nUsage: cursorflow run <tasks-dir> [options]');
|
|
187
|
-
throw new Error('Tasks directory required');
|
|
188
|
-
}
|
|
189
|
-
|
|
190
188
|
const config = loadConfig();
|
|
191
189
|
const logsDir = getLogsDir(config);
|
|
192
190
|
const originalCwd = process.cwd();
|
|
@@ -198,11 +196,22 @@ async function run(args: string[]): Promise<void> {
|
|
|
198
196
|
}
|
|
199
197
|
|
|
200
198
|
// Resolve tasks dir:
|
|
201
|
-
// 1.
|
|
202
|
-
// 2.
|
|
203
|
-
// 3.
|
|
199
|
+
// 1. If not provided, use the most recent flow or legacy task
|
|
200
|
+
// 2. Prefer the exact path if it exists relative to original cwd
|
|
201
|
+
// 3. Search in flowsDir by name
|
|
202
|
+
// 4. Fall back to projectRoot-relative tasksDir for backward compatibility
|
|
204
203
|
let tasksDir = '';
|
|
205
|
-
|
|
204
|
+
|
|
205
|
+
if (!options.tasksDir) {
|
|
206
|
+
const latestPath = findLatestFlowOrTask(config);
|
|
207
|
+
if (latestPath) {
|
|
208
|
+
logger.info(`No flow specified. Using the latest: ${path.basename(latestPath)}`);
|
|
209
|
+
tasksDir = latestPath;
|
|
210
|
+
} else {
|
|
211
|
+
console.log('\nUsage: cursorflow run <tasks-dir> [options]');
|
|
212
|
+
throw new Error('Tasks directory required (none found in flows or tasks directories)');
|
|
213
|
+
}
|
|
214
|
+
} else if (path.isAbsolute(options.tasksDir)) {
|
|
206
215
|
tasksDir = options.tasksDir;
|
|
207
216
|
} else {
|
|
208
217
|
const relPath = path.resolve(originalCwd, options.tasksDir);
|
|
@@ -216,7 +225,15 @@ async function run(args: string[]): Promise<void> {
|
|
|
216
225
|
tasksDir = foundFlow;
|
|
217
226
|
} else {
|
|
218
227
|
// Fallback to legacy tasksDir
|
|
219
|
-
|
|
228
|
+
const legacyTasksDir = safeJoin(config.projectRoot, config.tasksDir);
|
|
229
|
+
const foundLegacy = findFlowDir(legacyTasksDir, options.tasksDir);
|
|
230
|
+
|
|
231
|
+
if (foundLegacy) {
|
|
232
|
+
tasksDir = foundLegacy;
|
|
233
|
+
} else {
|
|
234
|
+
// Fallback to joining with project root
|
|
235
|
+
tasksDir = safeJoin(config.projectRoot, options.tasksDir);
|
|
236
|
+
}
|
|
220
237
|
}
|
|
221
238
|
}
|
|
222
239
|
}
|
|
@@ -318,6 +335,7 @@ async function run(args: string[]): Promise<void> {
|
|
|
318
335
|
...(options.raw ? { raw: true } : {}),
|
|
319
336
|
},
|
|
320
337
|
skipPreflight: options.skipPreflight,
|
|
338
|
+
browser: options.browser,
|
|
321
339
|
});
|
|
322
340
|
} catch (error: any) {
|
|
323
341
|
// Re-throw to be handled by the main entry point
|
package/src/cli/signal.ts
CHANGED
|
@@ -1,21 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CursorFlow signal command
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 실행 중인 lane에 즉각적인 개입 메시지 전송
|
|
5
|
+
*
|
|
6
|
+
* 동작 방식:
|
|
7
|
+
* - 현재 실행 중인 cursor-agent 프로세스를 중단 (SIGTERM)
|
|
8
|
+
* - 개입 메시지를 pending-intervention.json에 저장
|
|
9
|
+
* - Orchestrator가 프로세스 종료를 감지하고 개입 메시지와 함께 resume
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
import * as path from 'path';
|
|
8
12
|
import * as fs from 'fs';
|
|
9
13
|
import * as logger from '../utils/logger';
|
|
10
14
|
import { loadConfig, getLogsDir } from '../utils/config';
|
|
11
|
-
import { appendLog, createConversationEntry } from '../utils/state';
|
|
15
|
+
import { appendLog, createConversationEntry, loadState } from '../utils/state';
|
|
12
16
|
import { safeJoin } from '../utils/path';
|
|
17
|
+
import { LaneState } from '../types';
|
|
18
|
+
import {
|
|
19
|
+
executeUserIntervention,
|
|
20
|
+
isProcessAlive,
|
|
21
|
+
InterventionResult,
|
|
22
|
+
} from '../core/intervention';
|
|
13
23
|
|
|
14
24
|
interface SignalOptions {
|
|
15
25
|
lane: string | null;
|
|
16
26
|
message: string | null;
|
|
17
|
-
timeout: number | null;
|
|
27
|
+
timeout: number | null;
|
|
18
28
|
runDir: string | null;
|
|
29
|
+
force: boolean; // 프로세스 종료 없이 대기 모드로 전송
|
|
19
30
|
help: boolean;
|
|
20
31
|
}
|
|
21
32
|
|
|
@@ -24,14 +35,25 @@ function printHelp(): void {
|
|
|
24
35
|
Usage: cursorflow signal <lane> "<message>" [options]
|
|
25
36
|
cursorflow signal <lane> --timeout <ms>
|
|
26
37
|
|
|
27
|
-
Directly intervene in a running lane.
|
|
38
|
+
Directly intervene in a running lane. The agent will be interrupted immediately
|
|
39
|
+
and resume with your intervention message.
|
|
28
40
|
|
|
29
|
-
|
|
41
|
+
Arguments:
|
|
30
42
|
<lane> Lane name to signal
|
|
31
43
|
"<message>" Message text to send to the agent
|
|
44
|
+
|
|
45
|
+
Options:
|
|
32
46
|
--timeout <ms> Update execution timeout (in milliseconds)
|
|
33
47
|
--run-dir <path> Use a specific run directory (default: latest)
|
|
48
|
+
--force Send signal without interrupting current process
|
|
49
|
+
(message will be picked up on next task)
|
|
34
50
|
--help, -h Show help
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
cursorflow signal lane-1 "Please focus on error handling first"
|
|
54
|
+
cursorflow signal lane-2 "Skip the optional tasks and finish"
|
|
55
|
+
cursorflow signal lane-1 --timeout 600000 # Set 10 minute timeout
|
|
56
|
+
cursorflow signal lane-1 "Continue" --force # Don't interrupt, wait for next turn
|
|
35
57
|
`);
|
|
36
58
|
}
|
|
37
59
|
|
|
@@ -47,6 +69,7 @@ function parseArgs(args: string[]): SignalOptions {
|
|
|
47
69
|
message: nonOptions.slice(1).join(' ') || null,
|
|
48
70
|
timeout: timeoutIdx >= 0 ? parseInt(args[timeoutIdx + 1] || '0') || null : null,
|
|
49
71
|
runDir: runDirIdx >= 0 ? args[runDirIdx + 1] || null : null,
|
|
72
|
+
force: args.includes('--force'),
|
|
50
73
|
help: args.includes('--help') || args.includes('-h'),
|
|
51
74
|
};
|
|
52
75
|
}
|
|
@@ -63,6 +86,42 @@ function findLatestRunDir(logsDir: string): string | null {
|
|
|
63
86
|
return runs.length > 0 ? safeJoin(runsDir, runs[0]!) : null;
|
|
64
87
|
}
|
|
65
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Lane 상태 및 PID 확인
|
|
91
|
+
*/
|
|
92
|
+
function getLaneStatus(laneDir: string): { state: LaneState | null; isRunning: boolean; pid?: number } {
|
|
93
|
+
const statePath = safeJoin(laneDir, 'state.json');
|
|
94
|
+
|
|
95
|
+
if (!fs.existsSync(statePath)) {
|
|
96
|
+
return { state: null, isRunning: false };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const state = loadState<LaneState>(statePath);
|
|
100
|
+
if (!state) {
|
|
101
|
+
return { state: null, isRunning: false };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const pid = state.pid;
|
|
105
|
+
const isRunning = pid ? isProcessAlive(pid) : false;
|
|
106
|
+
|
|
107
|
+
return { state, isRunning, pid };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 기존 방식으로 intervention.txt만 작성 (--force 옵션용)
|
|
112
|
+
*/
|
|
113
|
+
function sendLegacyIntervention(laneDir: string, message: string): void {
|
|
114
|
+
const interventionPath = safeJoin(laneDir, 'intervention.txt');
|
|
115
|
+
const convoPath = safeJoin(laneDir, 'conversation.jsonl');
|
|
116
|
+
|
|
117
|
+
fs.writeFileSync(interventionPath, message);
|
|
118
|
+
|
|
119
|
+
const entry = createConversationEntry('intervention', `[HUMAN INTERVENTION]: ${message}`, {
|
|
120
|
+
task: 'DIRECT_SIGNAL'
|
|
121
|
+
});
|
|
122
|
+
appendLog(convoPath, entry);
|
|
123
|
+
}
|
|
124
|
+
|
|
66
125
|
async function signal(args: string[]): Promise<void> {
|
|
67
126
|
const options = parseArgs(args);
|
|
68
127
|
|
|
@@ -92,32 +151,72 @@ async function signal(args: string[]): Promise<void> {
|
|
|
92
151
|
throw new Error(`Lane directory not found: ${laneDir}`);
|
|
93
152
|
}
|
|
94
153
|
|
|
95
|
-
// Case 1: Timeout update
|
|
154
|
+
// Case 1: Timeout update (기존 로직 유지)
|
|
96
155
|
if (options.timeout !== null) {
|
|
97
156
|
const timeoutPath = safeJoin(laneDir, 'timeout.txt');
|
|
98
157
|
fs.writeFileSync(timeoutPath, String(options.timeout));
|
|
99
|
-
logger.success(
|
|
158
|
+
logger.success(`⏱ Timeout update signal sent to ${options.lane}: ${options.timeout}ms`);
|
|
100
159
|
return;
|
|
101
160
|
}
|
|
102
161
|
|
|
103
162
|
// Case 2: Intervention message
|
|
104
163
|
if (options.message) {
|
|
105
|
-
const
|
|
164
|
+
const { state, isRunning, pid } = getLaneStatus(laneDir);
|
|
106
165
|
const convoPath = safeJoin(laneDir, 'conversation.jsonl');
|
|
107
166
|
|
|
108
|
-
logger.info(
|
|
109
|
-
logger.info(`Message: "${options.message}"`);
|
|
167
|
+
logger.info(`📨 Sending intervention to lane: ${options.lane}`);
|
|
168
|
+
logger.info(` Message: "${options.message.substring(0, 50)}${options.message.length > 50 ? '...' : ''}"`);
|
|
110
169
|
|
|
111
|
-
//
|
|
112
|
-
fs.writeFileSync(interventionPath, options.message);
|
|
113
|
-
|
|
114
|
-
// 2. Also append to conversation log for visibility and history
|
|
170
|
+
// Log to conversation for history
|
|
115
171
|
const entry = createConversationEntry('intervention', `[HUMAN INTERVENTION]: ${options.message}`, {
|
|
116
172
|
task: 'DIRECT_SIGNAL'
|
|
117
173
|
});
|
|
118
174
|
appendLog(convoPath, entry);
|
|
175
|
+
|
|
176
|
+
// --force: 기존 방식 (프로세스 중단 없이 파일만 작성)
|
|
177
|
+
if (options.force) {
|
|
178
|
+
sendLegacyIntervention(laneDir, options.message);
|
|
179
|
+
logger.success('✅ Signal queued (--force mode). Message will be applied on next task.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Lane이 실행 중이 아닌 경우
|
|
184
|
+
if (!isRunning) {
|
|
185
|
+
if (state?.status === 'completed') {
|
|
186
|
+
logger.warn(`⚠ Lane ${options.lane} is already completed.`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 실행 중이 아니면 다음 resume 시 적용되도록 파일만 작성
|
|
191
|
+
sendLegacyIntervention(laneDir, options.message);
|
|
192
|
+
logger.info(`ℹ Lane ${options.lane} is not currently running (status: ${state?.status || 'unknown'}).`);
|
|
193
|
+
logger.success('✅ Signal queued. Message will be applied when lane resumes.');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 즉각 개입 실행: 프로세스 종료 + pending-intervention.json 생성
|
|
198
|
+
logger.info(`🛑 Interrupting running process (PID: ${pid})...`);
|
|
199
|
+
|
|
200
|
+
const result: InterventionResult = await executeUserIntervention(laneDir, options.message, pid);
|
|
201
|
+
|
|
202
|
+
if (result.success) {
|
|
203
|
+
if (result.killedPid) {
|
|
204
|
+
logger.success(`✅ Process ${result.killedPid} interrupted successfully.`);
|
|
205
|
+
logger.info(' The agent will resume with your intervention message.');
|
|
206
|
+
logger.info(' Monitor progress with: cursorflow monitor');
|
|
207
|
+
} else {
|
|
208
|
+
logger.success('✅ Intervention request created.');
|
|
209
|
+
logger.info(' Message will be applied on next agent turn.');
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
logger.error(`❌ Failed to send intervention: ${result.error}`);
|
|
213
|
+
|
|
214
|
+
// 실패해도 파일은 작성되었으므로 다음 기회에 적용됨
|
|
215
|
+
if (result.pendingFile) {
|
|
216
|
+
logger.info(' Intervention file was created and will be applied on next opportunity.');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
119
219
|
|
|
120
|
-
logger.success('Signal sent successfully. The agent will see this message in its current turn or next step.');
|
|
121
220
|
return;
|
|
122
221
|
}
|
|
123
222
|
|
|
@@ -125,4 +224,3 @@ async function signal(args: string[]): Promise<void> {
|
|
|
125
224
|
}
|
|
126
225
|
|
|
127
226
|
export = signal;
|
|
128
|
-
|
package/src/cli/tasks.ts
CHANGED
|
@@ -15,18 +15,11 @@ import * as logger from '../utils/logger';
|
|
|
15
15
|
import { TaskService, TaskDirInfo, ValidationStatus } from '../utils/task-service';
|
|
16
16
|
import { findProjectRoot, loadConfig, getTasksDir, getFlowsDir } from '../utils/config';
|
|
17
17
|
import { safeJoin } from '../utils/path';
|
|
18
|
+
import { listFlows } from '../utils/flow';
|
|
19
|
+
import { FlowInfo } from '../types/flow';
|
|
18
20
|
|
|
19
21
|
const COLORS = logger.COLORS;
|
|
20
22
|
|
|
21
|
-
interface FlowInfo {
|
|
22
|
-
id: string;
|
|
23
|
-
name: string;
|
|
24
|
-
path: string;
|
|
25
|
-
timestamp: Date;
|
|
26
|
-
lanes: string[];
|
|
27
|
-
status: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
23
|
interface TasksCliOptions {
|
|
31
24
|
validate: boolean;
|
|
32
25
|
taskName: string | null;
|
|
@@ -75,56 +68,6 @@ function parseArgs(args: string[]): TasksCliOptions {
|
|
|
75
68
|
return options;
|
|
76
69
|
}
|
|
77
70
|
|
|
78
|
-
/**
|
|
79
|
-
* List flows from _cursorflow/flows/
|
|
80
|
-
*/
|
|
81
|
-
function listFlows(flowsDir: string): FlowInfo[] {
|
|
82
|
-
if (!fs.existsSync(flowsDir)) {
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const dirs = fs.readdirSync(flowsDir)
|
|
87
|
-
.filter(name => {
|
|
88
|
-
const dirPath = safeJoin(flowsDir, name);
|
|
89
|
-
return fs.statSync(dirPath).isDirectory() && !name.startsWith('.');
|
|
90
|
-
})
|
|
91
|
-
.sort((a, b) => b.localeCompare(a)); // Most recent first
|
|
92
|
-
|
|
93
|
-
return dirs.map(name => {
|
|
94
|
-
const flowPath = safeJoin(flowsDir, name);
|
|
95
|
-
const metaPath = safeJoin(flowPath, 'flow.meta.json');
|
|
96
|
-
|
|
97
|
-
let meta: any = {};
|
|
98
|
-
try {
|
|
99
|
-
if (fs.existsSync(metaPath)) {
|
|
100
|
-
meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
|
|
101
|
-
}
|
|
102
|
-
} catch {}
|
|
103
|
-
|
|
104
|
-
// Parse flow name from directory (e.g., "001_TestFeature" -> "TestFeature")
|
|
105
|
-
const match = name.match(/^(\d+)_(.+)$/);
|
|
106
|
-
const id = match ? match[1] : name;
|
|
107
|
-
const flowName = match ? match[2] : name;
|
|
108
|
-
|
|
109
|
-
// Get lane files
|
|
110
|
-
const laneFiles = fs.readdirSync(flowPath)
|
|
111
|
-
.filter(f => f.endsWith('.json') && f !== 'flow.meta.json')
|
|
112
|
-
.map(f => {
|
|
113
|
-
const laneMatch = f.match(/^\d+-([^.]+)\.json$/);
|
|
114
|
-
return laneMatch ? laneMatch[1] : f.replace('.json', '');
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
id,
|
|
119
|
-
name: flowName,
|
|
120
|
-
path: flowPath,
|
|
121
|
-
timestamp: meta.createdAt ? new Date(meta.createdAt) : new Date(),
|
|
122
|
-
lanes: laneFiles,
|
|
123
|
-
status: meta.status || 'pending',
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
71
|
/**
|
|
129
72
|
* Get flow info by name
|
|
130
73
|
*/
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { cursorAgentCreateChat, cursorAgentSend } from './runner/agent';
|
|
2
|
+
import { events } from '../utils/events';
|
|
3
|
+
import { AgentSendResult } from '../types';
|
|
4
|
+
import { analyzeFailure, logFailure } from './failure-policy';
|
|
5
|
+
|
|
6
|
+
export interface AgentPromptOptions {
|
|
7
|
+
workspaceDir: string;
|
|
8
|
+
chatId: string;
|
|
9
|
+
prompt: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
laneName?: string;
|
|
12
|
+
signalDir?: string;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
enableIntervention?: boolean;
|
|
15
|
+
outputFormat?: 'json' | 'stream-json';
|
|
16
|
+
taskName?: string;
|
|
17
|
+
/** Enable browser automation (--browser flag). Required for web testing/scraping. */
|
|
18
|
+
browser?: boolean;
|
|
19
|
+
/** Auto-approve commands (--force flag). Default: true */
|
|
20
|
+
autoApproveCommands?: boolean;
|
|
21
|
+
/** Auto-approve MCP servers (--approve-mcps flag). Default: true */
|
|
22
|
+
autoApproveMcps?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class AgentSupervisor {
|
|
26
|
+
createChat(workspaceDir?: string): string {
|
|
27
|
+
return cursorAgentCreateChat(workspaceDir);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async sendTaskPrompt(options: AgentPromptOptions): Promise<AgentSendResult> {
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
|
|
33
|
+
events.emit('agent.prompt_sent', {
|
|
34
|
+
taskName: options.taskName || 'task',
|
|
35
|
+
model: options.model || 'unknown',
|
|
36
|
+
promptLength: options.prompt.length,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const result = await cursorAgentSend(options);
|
|
40
|
+
const duration = Date.now() - startTime;
|
|
41
|
+
|
|
42
|
+
events.emit('agent.response_received', {
|
|
43
|
+
taskName: options.taskName || 'task',
|
|
44
|
+
ok: result.ok,
|
|
45
|
+
duration,
|
|
46
|
+
responseLength: result.resultText?.length || 0,
|
|
47
|
+
error: result.error,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!result.ok) {
|
|
51
|
+
const analysis = analyzeFailure(result.error, {
|
|
52
|
+
circuitBreakerName: options.laneName,
|
|
53
|
+
});
|
|
54
|
+
logFailure(options.laneName || options.taskName || 'agent', analysis);
|
|
55
|
+
events.emit('agent.recovery_suggested', {
|
|
56
|
+
laneName: options.laneName,
|
|
57
|
+
taskName: options.taskName || 'task',
|
|
58
|
+
analysis,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -84,6 +84,7 @@ export const DEFAULT_AUTO_RECOVERY_CONFIG: AutoRecoveryConfig = {
|
|
|
84
84
|
/** State tracking for a single lane's recovery */
|
|
85
85
|
export interface LaneRecoveryState {
|
|
86
86
|
laneName: string;
|
|
87
|
+
runId: string;
|
|
87
88
|
stage: RecoveryStage;
|
|
88
89
|
lastActivityTime: number;
|
|
89
90
|
lastBytesReceived: number;
|
|
@@ -242,10 +243,11 @@ export class AutoRecoveryManager {
|
|
|
242
243
|
/**
|
|
243
244
|
* Register a lane for recovery monitoring
|
|
244
245
|
*/
|
|
245
|
-
registerLane(laneName: string): void {
|
|
246
|
+
registerLane(laneName: string, runId: string): void {
|
|
246
247
|
const now = Date.now();
|
|
247
248
|
this.laneStates.set(laneName, {
|
|
248
249
|
laneName,
|
|
250
|
+
runId,
|
|
249
251
|
stage: RecoveryStage.NORMAL,
|
|
250
252
|
lastActivityTime: now,
|
|
251
253
|
lastBytesReceived: 0,
|
|
@@ -461,6 +463,7 @@ export class AutoRecoveryManager {
|
|
|
461
463
|
logger.warn(message);
|
|
462
464
|
|
|
463
465
|
events.emit('recovery.continue_signal', {
|
|
466
|
+
runId: state.runId,
|
|
464
467
|
laneName,
|
|
465
468
|
idleSeconds,
|
|
466
469
|
signalCount: state.continueSignalsSent,
|
|
@@ -521,6 +524,7 @@ If you encountered a git error, resolve it and continue.`;
|
|
|
521
524
|
logger.warn(message);
|
|
522
525
|
|
|
523
526
|
events.emit('recovery.stronger_prompt', {
|
|
527
|
+
runId: state.runId,
|
|
524
528
|
laneName,
|
|
525
529
|
prompt: strongerPrompt,
|
|
526
530
|
});
|
|
@@ -580,6 +584,7 @@ If you encountered a git error, resolve it and continue.`;
|
|
|
580
584
|
logger.warn(message);
|
|
581
585
|
|
|
582
586
|
events.emit('recovery.restart', {
|
|
587
|
+
runId: state.runId,
|
|
583
588
|
laneName,
|
|
584
589
|
restartCount: state.restartCount,
|
|
585
590
|
maxRestarts: this.config.maxRestarts,
|
|
@@ -676,6 +681,7 @@ If you encountered a git error, resolve it and continue.`;
|
|
|
676
681
|
logger.error(message);
|
|
677
682
|
|
|
678
683
|
events.emit('recovery.diagnosed', {
|
|
684
|
+
runId: state.runId,
|
|
679
685
|
laneName,
|
|
680
686
|
diagnostic,
|
|
681
687
|
});
|
|
@@ -23,7 +23,6 @@ export enum FailureType {
|
|
|
23
23
|
DEPENDENCY_BLOCK = 'DEPENDENCY_BLOCK',
|
|
24
24
|
DEPENDENCY_FAILED = 'DEPENDENCY_FAILED',
|
|
25
25
|
DEPENDENCY_TIMEOUT = 'DEPENDENCY_TIMEOUT',
|
|
26
|
-
REVIEW_FAIL = 'REVIEW_FAIL',
|
|
27
26
|
GIT_ERROR = 'GIT_ERROR',
|
|
28
27
|
GIT_PUSH_REJECTED = 'GIT_PUSH_REJECTED',
|
|
29
28
|
MERGE_CONFLICT = 'MERGE_CONFLICT',
|