@litmers/cursorflow-orchestrator 0.1.40 → 0.2.3
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 +8 -3
- package/commands/cursorflow-init.md +0 -4
- package/dist/cli/index.js +0 -6
- package/dist/cli/index.js.map +1 -1
- 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 +1103 -1239
- 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 +99 -13
- 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 +3 -117
- package/dist/core/auto-recovery.js +4 -482
- package/dist/core/auto-recovery.js.map +1 -1
- package/dist/core/failure-policy.d.ts +0 -53
- package/dist/core/failure-policy.js +7 -175
- 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 +170 -0
- package/dist/core/intervention.js +408 -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 +39 -65
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.d.ts +7 -1
- package/dist/core/runner/agent.js +54 -36
- 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 +64 -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 +13 -34
- 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/index.ts +0 -6
- package/src/cli/logs.ts +121 -10
- package/src/cli/models.ts +20 -3
- package/src/cli/monitor.ts +1273 -1342
- package/src/cli/resume.ts +27 -1
- package/src/cli/run.ts +29 -11
- package/src/cli/signal.ts +120 -18
- package/src/cli/tasks.ts +2 -59
- package/src/core/agent-supervisor.ts +64 -0
- package/src/core/auto-recovery.ts +14 -590
- package/src/core/failure-policy.ts +7 -229
- package/src/core/git-lifecycle-manager.ts +1011 -0
- package/src/core/git-pipeline-coordinator.ts +221 -0
- package/src/core/intervention.ts +463 -0
- package/src/core/lane-state-machine.ts +1097 -0
- package/src/core/orchestrator.ts +48 -64
- package/src/core/runner/agent.ts +77 -39
- 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 +74 -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/cli/prepare.d.ts +0 -7
- package/dist/cli/prepare.js +0 -690
- package/dist/cli/prepare.js.map +0 -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/cli/prepare.ts +0 -777
- 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,20 +1,33 @@
|
|
|
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
|
+
createInterventionRequest,
|
|
23
|
+
InterventionType,
|
|
24
|
+
wrapUserIntervention,
|
|
25
|
+
} from '../core/intervention';
|
|
13
26
|
|
|
14
27
|
interface SignalOptions {
|
|
15
28
|
lane: string | null;
|
|
16
29
|
message: string | null;
|
|
17
|
-
timeout: number | null;
|
|
30
|
+
timeout: number | null;
|
|
18
31
|
runDir: string | null;
|
|
19
32
|
help: boolean;
|
|
20
33
|
}
|
|
@@ -24,14 +37,26 @@ function printHelp(): void {
|
|
|
24
37
|
Usage: cursorflow signal <lane> "<message>" [options]
|
|
25
38
|
cursorflow signal <lane> --timeout <ms>
|
|
26
39
|
|
|
27
|
-
|
|
40
|
+
Send an intervention message to a lane. For running lanes, the agent will be
|
|
41
|
+
interrupted immediately and resume with your message. For pending/waiting/failed
|
|
42
|
+
lanes, the message will be applied when the lane starts or resumes.
|
|
28
43
|
|
|
29
|
-
|
|
44
|
+
Note: Completed lanes cannot receive signals.
|
|
45
|
+
To re-run a completed lane, start a new run with: cursorflow run
|
|
46
|
+
|
|
47
|
+
Arguments:
|
|
30
48
|
<lane> Lane name to signal
|
|
31
49
|
"<message>" Message text to send to the agent
|
|
50
|
+
|
|
51
|
+
Options:
|
|
32
52
|
--timeout <ms> Update execution timeout (in milliseconds)
|
|
33
53
|
--run-dir <path> Use a specific run directory (default: latest)
|
|
34
54
|
--help, -h Show help
|
|
55
|
+
|
|
56
|
+
Examples:
|
|
57
|
+
cursorflow signal lane-1 "Please focus on error handling first"
|
|
58
|
+
cursorflow signal lane-2 "Skip the optional tasks and finish"
|
|
59
|
+
cursorflow signal lane-1 --timeout 600000 # Set 10 minute timeout
|
|
35
60
|
`);
|
|
36
61
|
}
|
|
37
62
|
|
|
@@ -39,8 +64,18 @@ function parseArgs(args: string[]): SignalOptions {
|
|
|
39
64
|
const runDirIdx = args.indexOf('--run-dir');
|
|
40
65
|
const timeoutIdx = args.indexOf('--timeout');
|
|
41
66
|
|
|
67
|
+
// Collect indices of option values to exclude from nonOptions
|
|
68
|
+
const optionValueIndices = new Set<number>();
|
|
69
|
+
if (runDirIdx >= 0 && runDirIdx + 1 < args.length) {
|
|
70
|
+
optionValueIndices.add(runDirIdx + 1);
|
|
71
|
+
}
|
|
72
|
+
if (timeoutIdx >= 0 && timeoutIdx + 1 < args.length) {
|
|
73
|
+
optionValueIndices.add(timeoutIdx + 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
42
76
|
// First non-option is lane, second (or rest joined) is message
|
|
43
|
-
|
|
77
|
+
// Exclude option flags and their values
|
|
78
|
+
const nonOptions = args.filter((a, i) => !a.startsWith('--') && !optionValueIndices.has(i));
|
|
44
79
|
|
|
45
80
|
return {
|
|
46
81
|
lane: nonOptions[0] || null,
|
|
@@ -63,6 +98,39 @@ function findLatestRunDir(logsDir: string): string | null {
|
|
|
63
98
|
return runs.length > 0 ? safeJoin(runsDir, runs[0]!) : null;
|
|
64
99
|
}
|
|
65
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Lane 상태 및 PID 확인
|
|
103
|
+
*/
|
|
104
|
+
function getLaneStatus(laneDir: string): { state: LaneState | null; isRunning: boolean; pid?: number } {
|
|
105
|
+
const statePath = safeJoin(laneDir, 'state.json');
|
|
106
|
+
|
|
107
|
+
if (!fs.existsSync(statePath)) {
|
|
108
|
+
return { state: null, isRunning: false };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const state = loadState<LaneState>(statePath);
|
|
112
|
+
if (!state) {
|
|
113
|
+
return { state: null, isRunning: false };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const pid = state.pid;
|
|
117
|
+
const isRunning = pid ? isProcessAlive(pid) : false;
|
|
118
|
+
|
|
119
|
+
return { state, isRunning, pid };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 개입 요청 파일 작성 (비실행 중인 lane용)
|
|
124
|
+
*/
|
|
125
|
+
function sendInterventionRequest(laneDir: string, message: string): void {
|
|
126
|
+
createInterventionRequest(laneDir, {
|
|
127
|
+
type: InterventionType.USER_MESSAGE,
|
|
128
|
+
message: wrapUserIntervention(message),
|
|
129
|
+
source: 'user',
|
|
130
|
+
priority: 10
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
66
134
|
async function signal(args: string[]): Promise<void> {
|
|
67
135
|
const options = parseArgs(args);
|
|
68
136
|
|
|
@@ -92,32 +160,67 @@ async function signal(args: string[]): Promise<void> {
|
|
|
92
160
|
throw new Error(`Lane directory not found: ${laneDir}`);
|
|
93
161
|
}
|
|
94
162
|
|
|
95
|
-
// Case 1: Timeout update
|
|
163
|
+
// Case 1: Timeout update (기존 로직 유지)
|
|
96
164
|
if (options.timeout !== null) {
|
|
97
165
|
const timeoutPath = safeJoin(laneDir, 'timeout.txt');
|
|
98
166
|
fs.writeFileSync(timeoutPath, String(options.timeout));
|
|
99
|
-
logger.success(
|
|
167
|
+
logger.success(`⏱ Timeout update signal sent to ${options.lane}: ${options.timeout}ms`);
|
|
100
168
|
return;
|
|
101
169
|
}
|
|
102
170
|
|
|
103
171
|
// Case 2: Intervention message
|
|
104
172
|
if (options.message) {
|
|
105
|
-
const
|
|
173
|
+
const { state, isRunning, pid } = getLaneStatus(laneDir);
|
|
106
174
|
const convoPath = safeJoin(laneDir, 'conversation.jsonl');
|
|
107
175
|
|
|
108
|
-
logger.info(
|
|
109
|
-
logger.info(`Message: "${options.message}"`);
|
|
176
|
+
logger.info(`📨 Sending intervention to lane: ${options.lane}`);
|
|
177
|
+
logger.info(` Message: "${options.message.substring(0, 50)}${options.message.length > 50 ? '...' : ''}"`);
|
|
110
178
|
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
179
|
+
// Completed 레인은 signal 거부 (브랜치 충돌 방지)
|
|
180
|
+
if (state?.status === 'completed') {
|
|
181
|
+
logger.error(`❌ Cannot signal a completed lane.`);
|
|
182
|
+
logger.info(' To re-run this lane, start a new run with: cursorflow run');
|
|
183
|
+
throw new Error('Lane is already completed');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Log to conversation for history
|
|
115
187
|
const entry = createConversationEntry('intervention', `[HUMAN INTERVENTION]: ${options.message}`, {
|
|
116
188
|
task: 'DIRECT_SIGNAL'
|
|
117
189
|
});
|
|
118
190
|
appendLog(convoPath, entry);
|
|
191
|
+
|
|
192
|
+
// Lane이 실행 중이 아닌 경우 (pending/waiting/failed/paused)
|
|
193
|
+
if (!isRunning) {
|
|
194
|
+
// 실행 중이 아니면 다음 시작/resume 시 적용되도록 파일만 작성
|
|
195
|
+
sendInterventionRequest(laneDir, options.message);
|
|
196
|
+
logger.info(`ℹ Lane ${options.lane} is not currently running (status: ${state?.status || 'unknown'}).`);
|
|
197
|
+
logger.success('✅ Signal queued. Message will be applied when lane starts or resumes.');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 즉각 개입 실행: 프로세스 종료 + pending-intervention.json 생성
|
|
202
|
+
logger.info(`🛑 Interrupting running process (PID: ${pid})...`);
|
|
203
|
+
|
|
204
|
+
const result: InterventionResult = await executeUserIntervention(laneDir, options.message, pid);
|
|
205
|
+
|
|
206
|
+
if (result.success) {
|
|
207
|
+
if (result.killedPid) {
|
|
208
|
+
logger.success(`✅ Process ${result.killedPid} interrupted successfully.`);
|
|
209
|
+
logger.info(' The agent will resume with your intervention message.');
|
|
210
|
+
logger.info(' Monitor progress with: cursorflow monitor');
|
|
211
|
+
} else {
|
|
212
|
+
logger.success('✅ Intervention request created.');
|
|
213
|
+
logger.info(' Message will be applied on next agent turn.');
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
logger.error(`❌ Failed to send intervention: ${result.error}`);
|
|
217
|
+
|
|
218
|
+
// 실패해도 파일은 작성되었으므로 다음 기회에 적용됨
|
|
219
|
+
if (result.pendingFile) {
|
|
220
|
+
logger.info(' Intervention file was created and will be applied on next opportunity.');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
119
223
|
|
|
120
|
-
logger.success('Signal sent successfully. The agent will see this message in its current turn or next step.');
|
|
121
224
|
return;
|
|
122
225
|
}
|
|
123
226
|
|
|
@@ -125,4 +228,3 @@ async function signal(args: string[]): Promise<void> {
|
|
|
125
228
|
}
|
|
126
229
|
|
|
127
230
|
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
|
+
}
|