@litmers/cursorflow-orchestrator 0.1.39 → 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 +20 -16
- 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/prepare.js +0 -1
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +23 -5
- 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 +38 -63
- 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 -109
- 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 -77
- package/dist/core/runner/task.js.map +1 -1
- package/dist/core/runner.js +11 -2
- package/dist/core/runner.js.map +1 -1
- package/dist/core/stall-detection.d.ts +27 -4
- package/dist/core/stall-detection.js +116 -28
- 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 +8 -5
- package/dist/services/logging/console.js.map +1 -1
- package/dist/services/logging/formatter.d.ts +9 -3
- package/dist/services/logging/formatter.js +64 -17
- 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 +24 -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 +13 -2
- 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 +15 -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 +99 -20
- 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/git.d.ts +12 -1
- package/dist/utils/git.js +54 -1
- package/dist/utils/git.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 +3 -2
- package/dist/utils/log-formatter.js +11 -11
- 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/prepare.ts +0 -1
- package/src/cli/resume.ts +29 -5
- 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 +45 -62
- package/src/core/runner/agent.ts +66 -33
- package/src/core/runner/pipeline.ts +318 -122
- package/src/core/runner/task.ts +12 -93
- package/src/core/runner.ts +12 -2
- package/src/core/stall-detection.ts +145 -28
- 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 +9 -5
- package/src/services/logging/formatter.ts +74 -17
- 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 +25 -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 +13 -2
- package/src/ui/log-viewer.ts +3 -0
- package/src/utils/config.ts +17 -1
- package/src/utils/cursor-agent.ts +68 -16
- package/src/utils/enhanced-logger.ts +106 -20
- package/src/utils/event-registry.ts +595 -0
- package/src/utils/events.ts +0 -16
- package/src/utils/flow.ts +84 -0
- package/src/utils/git.ts +59 -1
- package/src/utils/log-constants.ts +2 -1
- package/src/utils/log-formatter.ts +11 -12
- 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/core/orchestrator.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { loadConfig, getLogsDir } from '../utils/config';
|
|
|
21
21
|
import * as git from '../utils/git';
|
|
22
22
|
import { execSync } from 'child_process';
|
|
23
23
|
import { safeJoin } from '../utils/path';
|
|
24
|
+
import { getLaneLogPath } from '../services/logging/paths';
|
|
24
25
|
import {
|
|
25
26
|
EnhancedLogManager,
|
|
26
27
|
createLogManager,
|
|
@@ -28,6 +29,7 @@ import {
|
|
|
28
29
|
ParsedMessage,
|
|
29
30
|
stripAnsi
|
|
30
31
|
} from '../utils/enhanced-logger';
|
|
32
|
+
import { MAIN_LOG_FILENAME } from '../utils/log-constants';
|
|
31
33
|
import { formatMessageForConsole } from '../utils/log-formatter';
|
|
32
34
|
import { FailureType, analyzeFailure as analyzeFailureFromPolicy } from './failure-policy';
|
|
33
35
|
import {
|
|
@@ -169,6 +171,7 @@ async function handleDoctorDiagnostics(
|
|
|
169
171
|
// Note: StallPhase and RecoveryStage have compatible numeric values (0-5)
|
|
170
172
|
const recoveryState: LaneRecoveryState = {
|
|
171
173
|
laneName,
|
|
174
|
+
runId,
|
|
172
175
|
stage: stallState.phase as unknown as number, // Both enums use 0-5
|
|
173
176
|
lastActivityTime: stallState.lastRealActivityTime,
|
|
174
177
|
lastBytesReceived: stallState.bytesSinceLastCheck,
|
|
@@ -226,8 +229,10 @@ export function spawnLane({
|
|
|
226
229
|
worktreeDir,
|
|
227
230
|
enhancedLogConfig,
|
|
228
231
|
noGit = false,
|
|
232
|
+
skipPreflight = false,
|
|
229
233
|
onActivity,
|
|
230
234
|
laneIndex = 0,
|
|
235
|
+
browser,
|
|
231
236
|
}: {
|
|
232
237
|
laneName: string;
|
|
233
238
|
tasksFile: string;
|
|
@@ -238,8 +243,10 @@ export function spawnLane({
|
|
|
238
243
|
worktreeDir?: string;
|
|
239
244
|
enhancedLogConfig?: Partial<EnhancedLogConfig>;
|
|
240
245
|
noGit?: boolean;
|
|
246
|
+
skipPreflight?: boolean;
|
|
241
247
|
onActivity?: () => void;
|
|
242
248
|
laneIndex?: number;
|
|
249
|
+
browser?: boolean;
|
|
243
250
|
}): SpawnLaneResult {
|
|
244
251
|
fs.mkdirSync(laneRunDir, { recursive: true});
|
|
245
252
|
|
|
@@ -265,6 +272,14 @@ export function spawnLane({
|
|
|
265
272
|
if (noGit) {
|
|
266
273
|
args.push('--no-git');
|
|
267
274
|
}
|
|
275
|
+
|
|
276
|
+
if (skipPreflight) {
|
|
277
|
+
args.push('--skip-preflight');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (browser) {
|
|
281
|
+
args.push('--browser');
|
|
282
|
+
}
|
|
268
283
|
|
|
269
284
|
// Create enhanced log manager if enabled
|
|
270
285
|
const logConfig = { ...DEFAULT_LOG_CONFIG, ...enhancedLogConfig };
|
|
@@ -278,11 +293,11 @@ export function spawnLane({
|
|
|
278
293
|
};
|
|
279
294
|
|
|
280
295
|
if (logConfig.enabled) {
|
|
281
|
-
// Helper to get dynamic lane label like [
|
|
296
|
+
// Helper to get dynamic lane label like [1-1-refactor]
|
|
282
297
|
const getDynamicLabel = () => {
|
|
283
|
-
const laneNum =
|
|
284
|
-
const taskPart = info.currentTaskIndex
|
|
285
|
-
const shortLaneName = laneName.substring(0,
|
|
298
|
+
const laneNum = `${laneIndex + 1}`;
|
|
299
|
+
const taskPart = `-${info.currentTaskIndex || 1}`;
|
|
300
|
+
const shortLaneName = laneName.substring(0, 8);
|
|
286
301
|
return `[${laneNum}${taskPart}-${shortLaneName}]`;
|
|
287
302
|
};
|
|
288
303
|
|
|
@@ -316,15 +331,17 @@ export function spawnLane({
|
|
|
316
331
|
currentTaskIndex: startIndex > 0 ? startIndex + 1 : 0
|
|
317
332
|
};
|
|
318
333
|
|
|
319
|
-
// Buffer for
|
|
334
|
+
// Buffer for task progress detection
|
|
320
335
|
let lineBuffer = '';
|
|
321
336
|
|
|
322
337
|
// Pipe stdout and stderr through enhanced logger
|
|
338
|
+
// Note: Console output is handled by onParsedMessage callback via logManager
|
|
339
|
+
// Do NOT write to stdout/stderr directly here to avoid duplicate output
|
|
323
340
|
if (child.stdout) {
|
|
324
341
|
child.stdout.on('data', (data: Buffer) => {
|
|
325
342
|
logManager!.writeStdout(data);
|
|
326
343
|
|
|
327
|
-
//
|
|
344
|
+
// Track task progress for label updates (but don't output here)
|
|
328
345
|
const str = data.toString();
|
|
329
346
|
lineBuffer += str;
|
|
330
347
|
const lines = lineBuffer.split('\n');
|
|
@@ -346,39 +363,12 @@ export function spawnLane({
|
|
|
346
363
|
}
|
|
347
364
|
}
|
|
348
365
|
|
|
349
|
-
//
|
|
350
|
-
// or if it's NOT a noisy JSON line
|
|
366
|
+
// Track activity for stall detection (non-heartbeat lines only)
|
|
351
367
|
const isJson = trimmed.startsWith('{') || trimmed.includes('{"type"');
|
|
352
|
-
// Filter out heartbeats - they should NOT reset the idle timer
|
|
353
368
|
const isHeartbeat = trimmed.includes('Heartbeat') && trimmed.includes('bytes received');
|
|
354
369
|
|
|
355
|
-
if (!isJson) {
|
|
356
|
-
|
|
357
|
-
if (onActivity && !isHeartbeat) onActivity();
|
|
358
|
-
|
|
359
|
-
const currentLabel = getDynamicLabel();
|
|
360
|
-
const coloredLabel = `${logger.COLORS.magenta}${currentLabel}${logger.COLORS.reset}`;
|
|
361
|
-
|
|
362
|
-
// Regex that matches timestamp even if it has ANSI color codes
|
|
363
|
-
// Matches: [24:39:14] or \x1b[90m[24:39:14]\x1b[0m
|
|
364
|
-
const timestampRegex = /^((?:\x1b\[[0-9;]*m)*)\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/;
|
|
365
|
-
const tsMatch = trimmed.match(timestampRegex);
|
|
366
|
-
|
|
367
|
-
if (tsMatch) {
|
|
368
|
-
// If line already has timestamp format, just add lane prefix
|
|
369
|
-
// Check if lane label is already present to avoid triple duplication
|
|
370
|
-
if (!trimmed.includes(currentLabel)) {
|
|
371
|
-
// Insert label after the timestamp part
|
|
372
|
-
const tsPart = tsMatch[0];
|
|
373
|
-
const formatted = trimmed.replace(tsPart, `${tsPart} ${coloredLabel}`);
|
|
374
|
-
process.stdout.write(formatted + '\n');
|
|
375
|
-
} else {
|
|
376
|
-
process.stdout.write(trimmed + '\n');
|
|
377
|
-
}
|
|
378
|
-
} else {
|
|
379
|
-
// Add full prefix: timestamp + lane
|
|
380
|
-
process.stdout.write(`${logger.COLORS.gray}[${new Date().toLocaleTimeString('en-US', { hour12: false })}]${logger.COLORS.reset} ${coloredLabel} ${line}\n`);
|
|
381
|
-
}
|
|
370
|
+
if (!isJson && !isHeartbeat && onActivity) {
|
|
371
|
+
onActivity();
|
|
382
372
|
}
|
|
383
373
|
}
|
|
384
374
|
});
|
|
@@ -386,30 +376,8 @@ export function spawnLane({
|
|
|
386
376
|
|
|
387
377
|
if (child.stderr) {
|
|
388
378
|
child.stderr.on('data', (data: Buffer) => {
|
|
379
|
+
// Console output is handled by logManager's onParsedMessage callback
|
|
389
380
|
logManager!.writeStderr(data);
|
|
390
|
-
const str = data.toString();
|
|
391
|
-
const lines = str.split('\n');
|
|
392
|
-
for (const line of lines) {
|
|
393
|
-
const trimmed = line.trim();
|
|
394
|
-
if (trimmed) {
|
|
395
|
-
// Check if it's a real error or just git/status output on stderr
|
|
396
|
-
const isStatus = trimmed.startsWith('Preparing worktree') ||
|
|
397
|
-
trimmed.startsWith('Switched to a new branch') ||
|
|
398
|
-
trimmed.startsWith('HEAD is now at') ||
|
|
399
|
-
trimmed.includes('actual output');
|
|
400
|
-
|
|
401
|
-
const ts = new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
402
|
-
const currentLabel = getDynamicLabel();
|
|
403
|
-
const coloredLabel = `${logger.COLORS.magenta}${currentLabel}${logger.COLORS.reset}`;
|
|
404
|
-
|
|
405
|
-
if (isStatus) {
|
|
406
|
-
process.stdout.write(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${coloredLabel} ${trimmed}\n`);
|
|
407
|
-
} else {
|
|
408
|
-
if (onActivity) onActivity();
|
|
409
|
-
process.stderr.write(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${coloredLabel} ${logger.COLORS.red}❌ ERR ${trimmed}${logger.COLORS.reset}\n`);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
381
|
});
|
|
414
382
|
}
|
|
415
383
|
|
|
@@ -421,7 +389,7 @@ export function spawnLane({
|
|
|
421
389
|
return { child, logPath, logManager, info };
|
|
422
390
|
} else {
|
|
423
391
|
// Fallback to simple file logging
|
|
424
|
-
logPath =
|
|
392
|
+
logPath = getLaneLogPath(laneRunDir, 'raw');
|
|
425
393
|
const logFd = fs.openSync(logPath, 'a');
|
|
426
394
|
|
|
427
395
|
child = spawn('node', args, {
|
|
@@ -634,6 +602,7 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
634
602
|
noGit?: boolean;
|
|
635
603
|
skipPreflight?: boolean;
|
|
636
604
|
stallConfig?: Partial<StallDetectionConfig>;
|
|
605
|
+
browser?: boolean;
|
|
637
606
|
} = {}): Promise<{ lanes: LaneInfo[]; exitCodes: Record<string, number>; runRoot: string }> {
|
|
638
607
|
const lanes = listLaneFiles(tasksDir);
|
|
639
608
|
|
|
@@ -682,6 +651,9 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
682
651
|
: safeJoin(logsDir, 'runs', runId);
|
|
683
652
|
|
|
684
653
|
fs.mkdirSync(runRoot, { recursive: true });
|
|
654
|
+
// Main process logs live at the run root; lane/subprocess logs stay in per-lane directories.
|
|
655
|
+
logger.setDefaultContext('MAIN');
|
|
656
|
+
logger.setLogFile(safeJoin(runRoot, MAIN_LOG_FILENAME));
|
|
685
657
|
|
|
686
658
|
// Clean stale locks before starting
|
|
687
659
|
try {
|
|
@@ -838,8 +810,10 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
838
810
|
const now = Date.now();
|
|
839
811
|
|
|
840
812
|
// Register lane with unified stall detection service FIRST
|
|
813
|
+
// Pass intervention capability so stall service knows if continue signals will work
|
|
841
814
|
stallService.registerLane(lane.name, {
|
|
842
815
|
laneRunDir: laneRunDirs[lane.name]!,
|
|
816
|
+
interventionEnabled: config.enableIntervention ?? true,
|
|
843
817
|
});
|
|
844
818
|
|
|
845
819
|
const laneIdx = lanes.findIndex(l => l.name === lane.name);
|
|
@@ -863,7 +837,9 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
863
837
|
worktreeDir: laneWorktreeDirs[lane.name],
|
|
864
838
|
enhancedLogConfig: options.enhancedLogging,
|
|
865
839
|
noGit: options.noGit,
|
|
840
|
+
skipPreflight: options.skipPreflight,
|
|
866
841
|
laneIndex: laneIdx >= 0 ? laneIdx : 0,
|
|
842
|
+
browser: options.browser,
|
|
867
843
|
onActivity: () => {
|
|
868
844
|
// Record state file update activity
|
|
869
845
|
stallService.recordStateUpdate(lane.name);
|
|
@@ -931,13 +907,19 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
931
907
|
for (const [laneName, info] of running.entries()) {
|
|
932
908
|
const lane = lanes.find(l => l.name === laneName)!;
|
|
933
909
|
|
|
934
|
-
// Check state file for progress updates
|
|
910
|
+
// Check state file for progress updates and sync lane status
|
|
935
911
|
try {
|
|
936
912
|
const stateStat = fs.statSync(info.statePath);
|
|
937
913
|
const stallState = stallService.getState(laneName);
|
|
938
914
|
if (stallState && stateStat.mtimeMs > stallState.lastStateUpdateTime) {
|
|
939
915
|
stallService.recordStateUpdate(laneName);
|
|
940
916
|
}
|
|
917
|
+
|
|
918
|
+
// Sync lane status to stall service (skips stall detection when 'waiting' for dependencies)
|
|
919
|
+
const laneState = loadState<LaneState>(info.statePath);
|
|
920
|
+
if (laneState && stallState && laneState.status !== stallState.laneStatus) {
|
|
921
|
+
stallService.setLaneStatus(laneName, laneState.status || 'running');
|
|
922
|
+
}
|
|
941
923
|
} catch {
|
|
942
924
|
// State file might not exist yet
|
|
943
925
|
}
|
|
@@ -948,7 +930,8 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
948
930
|
}
|
|
949
931
|
|
|
950
932
|
// Run stall analysis and recovery (all logic is in StallDetectionService)
|
|
951
|
-
|
|
933
|
+
// Note: checkAndRecover is now async as it may kill processes
|
|
934
|
+
const analysis = await stallService.checkAndRecover(laneName);
|
|
952
935
|
|
|
953
936
|
// Log to lane log manager if there was an action
|
|
954
937
|
if (analysis.action !== RecoveryAction.NONE) {
|
package/src/core/runner/agent.ts
CHANGED
|
@@ -131,7 +131,7 @@ function parseJsonFromStdout(stdout: string): any {
|
|
|
131
131
|
/**
|
|
132
132
|
* Execute cursor-agent command with timeout and better error handling
|
|
133
133
|
*/
|
|
134
|
-
async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalDir, timeout, enableIntervention, outputFormat, taskName }: {
|
|
134
|
+
async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalDir, timeout, enableIntervention, outputFormat, taskName, browser, autoApproveCommands, autoApproveMcps }: {
|
|
135
135
|
workspaceDir: string;
|
|
136
136
|
chatId: string;
|
|
137
137
|
prompt: string;
|
|
@@ -139,50 +139,79 @@ async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalD
|
|
|
139
139
|
signalDir?: string;
|
|
140
140
|
timeout?: number;
|
|
141
141
|
enableIntervention?: boolean;
|
|
142
|
-
outputFormat?: 'json' | '
|
|
142
|
+
outputFormat?: 'json' | 'stream-json';
|
|
143
143
|
taskName?: string;
|
|
144
|
+
/** Enable browser automation (--browser flag) */
|
|
145
|
+
browser?: boolean;
|
|
146
|
+
/** Auto-approve commands (--force flag). Default: true */
|
|
147
|
+
autoApproveCommands?: boolean;
|
|
148
|
+
/** Auto-approve MCP servers (--approve-mcps flag). Default: true */
|
|
149
|
+
autoApproveMcps?: boolean;
|
|
144
150
|
}): Promise<AgentSendResult> {
|
|
145
151
|
const timeoutMs = timeout || 10 * 60 * 1000; // 10 minutes default
|
|
146
|
-
|
|
152
|
+
|
|
153
|
+
// Build args: cursor-agent [options] [prompt]
|
|
154
|
+
// Note: 'send' command no longer exists in cursor-agent CLI
|
|
155
|
+
// Use --resume <chatId> to continue an existing chat session
|
|
156
|
+
const args: string[] = [];
|
|
157
|
+
|
|
158
|
+
// Resume existing chat session
|
|
159
|
+
args.push('--resume', chatId);
|
|
147
160
|
|
|
148
161
|
if (model) {
|
|
149
162
|
args.push('--model', model);
|
|
150
163
|
}
|
|
151
164
|
|
|
152
165
|
if (outputFormat === 'json' || outputFormat === 'stream-json') {
|
|
153
|
-
args.push('--print', '--output-format',
|
|
166
|
+
args.push('--print', '--output-format', outputFormat);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Auto-approve commands if enabled (default: true for automation)
|
|
170
|
+
if (autoApproveCommands !== false) {
|
|
171
|
+
args.push('--force');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Auto-approve MCP servers if enabled (default: true for automation)
|
|
175
|
+
if (autoApproveMcps !== false) {
|
|
176
|
+
args.push('--approve-mcps');
|
|
154
177
|
}
|
|
155
178
|
|
|
156
|
-
//
|
|
157
|
-
|
|
179
|
+
// Enable browser automation if requested (required for web testing/scraping)
|
|
180
|
+
if (browser) {
|
|
181
|
+
args.push('--browser');
|
|
182
|
+
}
|
|
158
183
|
|
|
159
184
|
// Add worktree context if provided
|
|
160
185
|
if (workspaceDir) {
|
|
161
186
|
args.push('--workspace', workspaceDir);
|
|
162
187
|
}
|
|
188
|
+
|
|
189
|
+
// NOTE: In latest cursor-agent, prompt must be sent via stdin, not as positional argument
|
|
163
190
|
|
|
164
191
|
return new Promise((resolve) => {
|
|
165
192
|
logger.info(`Sending prompt to cursor-agent (timeout: ${Math.round(timeoutMs / 1000)}s)...`);
|
|
166
193
|
|
|
194
|
+
// stdin must always be 'pipe' to send prompt via stdin (required by latest cursor-agent)
|
|
167
195
|
const child = spawn('cursor-agent', args, {
|
|
168
196
|
cwd: workspaceDir || process.cwd(),
|
|
169
|
-
stdio:
|
|
197
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
170
198
|
});
|
|
171
199
|
|
|
172
200
|
activeChildren.add(child);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
201
|
+
|
|
202
|
+
// Send prompt via stdin (latest cursor-agent requires this)
|
|
203
|
+
// Note: stdin is closed after sending prompt, so intervention via stdin is not supported
|
|
204
|
+
if (child.stdin) {
|
|
205
|
+
child.stdin.write(prompt);
|
|
206
|
+
child.stdin.end();
|
|
176
207
|
}
|
|
177
208
|
|
|
178
209
|
let fullStdout = '';
|
|
179
210
|
let fullStderr = '';
|
|
180
211
|
let timeoutHandle: NodeJS.Timeout;
|
|
181
|
-
let heartbeatInterval: NodeJS.Timeout | undefined;
|
|
182
|
-
let lastActivity = Date.now();
|
|
183
|
-
let bytesReceived = 0;
|
|
184
212
|
|
|
185
|
-
// Signal watching for
|
|
213
|
+
// Signal watching for dynamic timeout adjustment
|
|
214
|
+
// Note: Intervention via stdin is no longer supported (stdin is closed after prompt)
|
|
186
215
|
let signalWatcher: fs.FSWatcher | null = null;
|
|
187
216
|
if (signalDir) {
|
|
188
217
|
if (!fs.existsSync(signalDir)) {
|
|
@@ -192,25 +221,21 @@ async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalD
|
|
|
192
221
|
const interventionPath = path.join(signalDir, 'intervention.txt');
|
|
193
222
|
const timeoutPath = path.join(signalDir, 'timeout.txt');
|
|
194
223
|
|
|
195
|
-
// Watch for
|
|
224
|
+
// Watch for timeout signals from UI (intervention via stdin no longer works)
|
|
196
225
|
signalWatcher = fs.watch(signalDir, (event, filename) => {
|
|
197
226
|
if (filename === 'intervention.txt' && fs.existsSync(interventionPath)) {
|
|
198
227
|
try {
|
|
199
228
|
const message = fs.readFileSync(interventionPath, 'utf8').trim();
|
|
200
229
|
if (message) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}));
|
|
211
|
-
}
|
|
212
|
-
} else {
|
|
213
|
-
logger.warn(`Intervention requested but stdin not available: ${message}`);
|
|
230
|
+
// Log intervention but cannot send via stdin (already closed)
|
|
231
|
+
logger.warn(`👋 Intervention received but stdin is closed (cursor-agent CLI limitation): ${message.substring(0, 50)}...`);
|
|
232
|
+
|
|
233
|
+
if (signalDir) {
|
|
234
|
+
const convoPath = path.join(signalDir, 'conversation.jsonl');
|
|
235
|
+
appendLog(convoPath, createConversationEntry('intervention', `[INTERVENTION IGNORED - stdin closed]: ${message}`, {
|
|
236
|
+
task: taskName || 'AGENT_TURN',
|
|
237
|
+
model: 'manual'
|
|
238
|
+
}));
|
|
214
239
|
}
|
|
215
240
|
fs.unlinkSync(interventionPath);
|
|
216
241
|
}
|
|
@@ -227,7 +252,6 @@ async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalD
|
|
|
227
252
|
const elapsed = Date.now() - startTime;
|
|
228
253
|
const remaining = Math.max(1000, newTimeoutMs - elapsed);
|
|
229
254
|
timeoutHandle = setTimeout(() => {
|
|
230
|
-
clearInterval(heartbeatInterval);
|
|
231
255
|
child.kill();
|
|
232
256
|
resolve({ ok: false, exitCode: -1, error: `cursor-agent timed out after updated limit.` });
|
|
233
257
|
}, remaining);
|
|
@@ -241,7 +265,6 @@ async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalD
|
|
|
241
265
|
if (child.stdout) {
|
|
242
266
|
child.stdout.on('data', (data) => {
|
|
243
267
|
fullStdout += data.toString();
|
|
244
|
-
bytesReceived += data.length;
|
|
245
268
|
process.stdout.write(data);
|
|
246
269
|
});
|
|
247
270
|
}
|
|
@@ -255,7 +278,6 @@ async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalD
|
|
|
255
278
|
|
|
256
279
|
const startTime = Date.now();
|
|
257
280
|
timeoutHandle = setTimeout(() => {
|
|
258
|
-
clearInterval(heartbeatInterval);
|
|
259
281
|
child.kill();
|
|
260
282
|
resolve({
|
|
261
283
|
ok: false,
|
|
@@ -267,7 +289,6 @@ async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalD
|
|
|
267
289
|
child.on('close', (code) => {
|
|
268
290
|
activeChildren.delete(child);
|
|
269
291
|
clearTimeout(timeoutHandle);
|
|
270
|
-
clearInterval(heartbeatInterval);
|
|
271
292
|
if (signalWatcher) signalWatcher.close();
|
|
272
293
|
|
|
273
294
|
const json = parseJsonFromStdout(fullStdout);
|
|
@@ -281,6 +302,12 @@ async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalD
|
|
|
281
302
|
exitCode: code ?? 0,
|
|
282
303
|
sessionId: json.session_id || chatId,
|
|
283
304
|
resultText: json.result || '',
|
|
305
|
+
// Extended fields from cursor-agent result
|
|
306
|
+
durationMs: json.duration_ms,
|
|
307
|
+
durationApiMs: json.duration_api_ms,
|
|
308
|
+
requestId: json.request_id,
|
|
309
|
+
subtype: json.subtype,
|
|
310
|
+
model: json.model,
|
|
284
311
|
});
|
|
285
312
|
}
|
|
286
313
|
});
|
|
@@ -305,8 +332,14 @@ export async function cursorAgentSend(options: {
|
|
|
305
332
|
signalDir?: string;
|
|
306
333
|
timeout?: number;
|
|
307
334
|
enableIntervention?: boolean;
|
|
308
|
-
outputFormat?: 'json' | '
|
|
335
|
+
outputFormat?: 'json' | 'stream-json';
|
|
309
336
|
taskName?: string;
|
|
337
|
+
/** Enable browser automation (--browser flag) */
|
|
338
|
+
browser?: boolean;
|
|
339
|
+
/** Auto-approve commands (--force flag). Default: true */
|
|
340
|
+
autoApproveCommands?: boolean;
|
|
341
|
+
/** Auto-approve MCP servers (--approve-mcps flag). Default: true */
|
|
342
|
+
autoApproveMcps?: boolean;
|
|
310
343
|
}): Promise<AgentSendResult> {
|
|
311
344
|
const laneName = options.signalDir ? path.basename(path.dirname(options.signalDir)) : 'agent';
|
|
312
345
|
|