@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/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,13 +293,12 @@ export function spawnLane({
|
|
|
278
293
|
};
|
|
279
294
|
|
|
280
295
|
if (logConfig.enabled) {
|
|
281
|
-
// Helper to get dynamic lane label like [1-1-
|
|
296
|
+
// Helper to get dynamic lane label like [1-1-refactor]
|
|
282
297
|
const getDynamicLabel = () => {
|
|
283
298
|
const laneNum = `${laneIndex + 1}`;
|
|
284
299
|
const taskPart = `-${info.currentTaskIndex || 1}`;
|
|
285
|
-
const shortLaneName = laneName.substring(0,
|
|
286
|
-
|
|
287
|
-
return `[${combined}]`;
|
|
300
|
+
const shortLaneName = laneName.substring(0, 8);
|
|
301
|
+
return `[${laneNum}${taskPart}-${shortLaneName}]`;
|
|
288
302
|
};
|
|
289
303
|
|
|
290
304
|
// Create callback for clean console output
|
|
@@ -317,15 +331,17 @@ export function spawnLane({
|
|
|
317
331
|
currentTaskIndex: startIndex > 0 ? startIndex + 1 : 0
|
|
318
332
|
};
|
|
319
333
|
|
|
320
|
-
// Buffer for
|
|
334
|
+
// Buffer for task progress detection
|
|
321
335
|
let lineBuffer = '';
|
|
322
336
|
|
|
323
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
|
|
324
340
|
if (child.stdout) {
|
|
325
341
|
child.stdout.on('data', (data: Buffer) => {
|
|
326
342
|
logManager!.writeStdout(data);
|
|
327
343
|
|
|
328
|
-
//
|
|
344
|
+
// Track task progress for label updates (but don't output here)
|
|
329
345
|
const str = data.toString();
|
|
330
346
|
lineBuffer += str;
|
|
331
347
|
const lines = lineBuffer.split('\n');
|
|
@@ -347,39 +363,12 @@ export function spawnLane({
|
|
|
347
363
|
}
|
|
348
364
|
}
|
|
349
365
|
|
|
350
|
-
//
|
|
351
|
-
// or if it's NOT a noisy JSON line
|
|
366
|
+
// Track activity for stall detection (non-heartbeat lines only)
|
|
352
367
|
const isJson = trimmed.startsWith('{') || trimmed.includes('{"type"');
|
|
353
|
-
// Filter out heartbeats - they should NOT reset the idle timer
|
|
354
368
|
const isHeartbeat = trimmed.includes('Heartbeat') && trimmed.includes('bytes received');
|
|
355
369
|
|
|
356
|
-
if (!isJson) {
|
|
357
|
-
|
|
358
|
-
if (onActivity && !isHeartbeat) onActivity();
|
|
359
|
-
|
|
360
|
-
const currentLabel = getDynamicLabel();
|
|
361
|
-
const coloredLabel = `${logger.COLORS.magenta}${currentLabel}${logger.COLORS.reset}`;
|
|
362
|
-
|
|
363
|
-
// Regex that matches timestamp even if it has ANSI color codes
|
|
364
|
-
// Matches: [24:39:14] or \x1b[90m[24:39:14]\x1b[0m
|
|
365
|
-
const timestampRegex = /^((?:\x1b\[[0-9;]*m)*)\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/;
|
|
366
|
-
const tsMatch = trimmed.match(timestampRegex);
|
|
367
|
-
|
|
368
|
-
if (tsMatch) {
|
|
369
|
-
// If line already has timestamp format, just add lane prefix
|
|
370
|
-
// Check if lane label is already present to avoid triple duplication
|
|
371
|
-
if (!trimmed.includes(currentLabel)) {
|
|
372
|
-
// Insert label after the timestamp part
|
|
373
|
-
const tsPart = tsMatch[0];
|
|
374
|
-
const formatted = trimmed.replace(tsPart, `${tsPart} ${coloredLabel}`);
|
|
375
|
-
process.stdout.write(formatted + '\n');
|
|
376
|
-
} else {
|
|
377
|
-
process.stdout.write(trimmed + '\n');
|
|
378
|
-
}
|
|
379
|
-
} else {
|
|
380
|
-
// Add full prefix: timestamp + lane
|
|
381
|
-
process.stdout.write(`${logger.COLORS.gray}[${new Date().toLocaleTimeString('en-US', { hour12: false })}]${logger.COLORS.reset} ${coloredLabel} ${line}\n`);
|
|
382
|
-
}
|
|
370
|
+
if (!isJson && !isHeartbeat && onActivity) {
|
|
371
|
+
onActivity();
|
|
383
372
|
}
|
|
384
373
|
}
|
|
385
374
|
});
|
|
@@ -387,30 +376,8 @@ export function spawnLane({
|
|
|
387
376
|
|
|
388
377
|
if (child.stderr) {
|
|
389
378
|
child.stderr.on('data', (data: Buffer) => {
|
|
379
|
+
// Console output is handled by logManager's onParsedMessage callback
|
|
390
380
|
logManager!.writeStderr(data);
|
|
391
|
-
const str = data.toString();
|
|
392
|
-
const lines = str.split('\n');
|
|
393
|
-
for (const line of lines) {
|
|
394
|
-
const trimmed = line.trim();
|
|
395
|
-
if (trimmed) {
|
|
396
|
-
// Check if it's a real error or just git/status output on stderr
|
|
397
|
-
const isStatus = trimmed.startsWith('Preparing worktree') ||
|
|
398
|
-
trimmed.startsWith('Switched to a new branch') ||
|
|
399
|
-
trimmed.startsWith('HEAD is now at') ||
|
|
400
|
-
trimmed.includes('actual output');
|
|
401
|
-
|
|
402
|
-
const ts = new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
403
|
-
const currentLabel = getDynamicLabel();
|
|
404
|
-
const coloredLabel = `${logger.COLORS.magenta}${currentLabel}${logger.COLORS.reset}`;
|
|
405
|
-
|
|
406
|
-
if (isStatus) {
|
|
407
|
-
process.stdout.write(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${coloredLabel} ${trimmed}\n`);
|
|
408
|
-
} else {
|
|
409
|
-
if (onActivity) onActivity();
|
|
410
|
-
process.stderr.write(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${coloredLabel} ${logger.COLORS.red}❌ ERR ${trimmed}${logger.COLORS.reset}\n`);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
381
|
});
|
|
415
382
|
}
|
|
416
383
|
|
|
@@ -422,7 +389,7 @@ export function spawnLane({
|
|
|
422
389
|
return { child, logPath, logManager, info };
|
|
423
390
|
} else {
|
|
424
391
|
// Fallback to simple file logging
|
|
425
|
-
logPath =
|
|
392
|
+
logPath = getLaneLogPath(laneRunDir, 'raw');
|
|
426
393
|
const logFd = fs.openSync(logPath, 'a');
|
|
427
394
|
|
|
428
395
|
child = spawn('node', args, {
|
|
@@ -635,6 +602,7 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
635
602
|
noGit?: boolean;
|
|
636
603
|
skipPreflight?: boolean;
|
|
637
604
|
stallConfig?: Partial<StallDetectionConfig>;
|
|
605
|
+
browser?: boolean;
|
|
638
606
|
} = {}): Promise<{ lanes: LaneInfo[]; exitCodes: Record<string, number>; runRoot: string }> {
|
|
639
607
|
const lanes = listLaneFiles(tasksDir);
|
|
640
608
|
|
|
@@ -683,6 +651,9 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
683
651
|
: safeJoin(logsDir, 'runs', runId);
|
|
684
652
|
|
|
685
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));
|
|
686
657
|
|
|
687
658
|
// Clean stale locks before starting
|
|
688
659
|
try {
|
|
@@ -866,7 +837,9 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
866
837
|
worktreeDir: laneWorktreeDirs[lane.name],
|
|
867
838
|
enhancedLogConfig: options.enhancedLogging,
|
|
868
839
|
noGit: options.noGit,
|
|
840
|
+
skipPreflight: options.skipPreflight,
|
|
869
841
|
laneIndex: laneIdx >= 0 ? laneIdx : 0,
|
|
842
|
+
browser: options.browser,
|
|
870
843
|
onActivity: () => {
|
|
871
844
|
// Record state file update activity
|
|
872
845
|
stallService.recordStateUpdate(lane.name);
|
|
@@ -957,7 +930,8 @@ export async function orchestrate(tasksDir: string, options: {
|
|
|
957
930
|
}
|
|
958
931
|
|
|
959
932
|
// Run stall analysis and recovery (all logic is in StallDetectionService)
|
|
960
|
-
|
|
933
|
+
// Note: checkAndRecover is now async as it may kill processes
|
|
934
|
+
const analysis = await stallService.checkAndRecover(laneName);
|
|
961
935
|
|
|
962
936
|
// Log to lane log manager if there was an action
|
|
963
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
|
|