@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.
Files changed (207) hide show
  1. package/CHANGELOG.md +0 -2
  2. package/README.md +7 -3
  3. package/commands/cursorflow-init.md +0 -4
  4. package/dist/cli/logs.js +108 -9
  5. package/dist/cli/logs.js.map +1 -1
  6. package/dist/cli/models.js +20 -3
  7. package/dist/cli/models.js.map +1 -1
  8. package/dist/cli/monitor.d.ts +7 -10
  9. package/dist/cli/monitor.js +1088 -1240
  10. package/dist/cli/monitor.js.map +1 -1
  11. package/dist/cli/resume.js +21 -1
  12. package/dist/cli/resume.js.map +1 -1
  13. package/dist/cli/run.js +28 -9
  14. package/dist/cli/run.js.map +1 -1
  15. package/dist/cli/signal.d.ts +6 -1
  16. package/dist/cli/signal.js +94 -12
  17. package/dist/cli/signal.js.map +1 -1
  18. package/dist/cli/tasks.js +3 -46
  19. package/dist/cli/tasks.js.map +1 -1
  20. package/dist/core/agent-supervisor.d.ts +23 -0
  21. package/dist/core/agent-supervisor.js +42 -0
  22. package/dist/core/agent-supervisor.js.map +1 -0
  23. package/dist/core/auto-recovery.d.ts +2 -1
  24. package/dist/core/auto-recovery.js +6 -1
  25. package/dist/core/auto-recovery.js.map +1 -1
  26. package/dist/core/failure-policy.d.ts +0 -1
  27. package/dist/core/failure-policy.js +0 -1
  28. package/dist/core/failure-policy.js.map +1 -1
  29. package/dist/core/git-lifecycle-manager.d.ts +284 -0
  30. package/dist/core/git-lifecycle-manager.js +778 -0
  31. package/dist/core/git-lifecycle-manager.js.map +1 -0
  32. package/dist/core/git-pipeline-coordinator.d.ts +21 -0
  33. package/dist/core/git-pipeline-coordinator.js +205 -0
  34. package/dist/core/git-pipeline-coordinator.js.map +1 -0
  35. package/dist/core/intervention.d.ts +176 -0
  36. package/dist/core/intervention.js +424 -0
  37. package/dist/core/intervention.js.map +1 -0
  38. package/dist/core/lane-state-machine.d.ts +423 -0
  39. package/dist/core/lane-state-machine.js +890 -0
  40. package/dist/core/lane-state-machine.js.map +1 -0
  41. package/dist/core/orchestrator.d.ts +4 -1
  42. package/dist/core/orchestrator.js +29 -62
  43. package/dist/core/orchestrator.js.map +1 -1
  44. package/dist/core/runner/agent.d.ts +7 -1
  45. package/dist/core/runner/agent.js +45 -30
  46. package/dist/core/runner/agent.js.map +1 -1
  47. package/dist/core/runner/pipeline.js +283 -123
  48. package/dist/core/runner/pipeline.js.map +1 -1
  49. package/dist/core/runner/task.d.ts +4 -5
  50. package/dist/core/runner/task.js +6 -80
  51. package/dist/core/runner/task.js.map +1 -1
  52. package/dist/core/runner.js +8 -2
  53. package/dist/core/runner.js.map +1 -1
  54. package/dist/core/stall-detection.d.ts +11 -4
  55. package/dist/core/stall-detection.js +62 -27
  56. package/dist/core/stall-detection.js.map +1 -1
  57. package/dist/hooks/contexts/index.d.ts +104 -0
  58. package/dist/hooks/contexts/index.js +134 -0
  59. package/dist/hooks/contexts/index.js.map +1 -0
  60. package/dist/hooks/data-accessor.d.ts +86 -0
  61. package/dist/hooks/data-accessor.js +410 -0
  62. package/dist/hooks/data-accessor.js.map +1 -0
  63. package/dist/hooks/flow-controller.d.ts +136 -0
  64. package/dist/hooks/flow-controller.js +351 -0
  65. package/dist/hooks/flow-controller.js.map +1 -0
  66. package/dist/hooks/index.d.ts +68 -0
  67. package/dist/hooks/index.js +105 -0
  68. package/dist/hooks/index.js.map +1 -0
  69. package/dist/hooks/manager.d.ts +129 -0
  70. package/dist/hooks/manager.js +389 -0
  71. package/dist/hooks/manager.js.map +1 -0
  72. package/dist/hooks/types.d.ts +463 -0
  73. package/dist/hooks/types.js +45 -0
  74. package/dist/hooks/types.js.map +1 -0
  75. package/dist/services/logging/buffer.d.ts +2 -2
  76. package/dist/services/logging/buffer.js +95 -42
  77. package/dist/services/logging/buffer.js.map +1 -1
  78. package/dist/services/logging/console.js +6 -1
  79. package/dist/services/logging/console.js.map +1 -1
  80. package/dist/services/logging/formatter.d.ts +9 -4
  81. package/dist/services/logging/formatter.js +64 -18
  82. package/dist/services/logging/formatter.js.map +1 -1
  83. package/dist/services/logging/index.d.ts +0 -1
  84. package/dist/services/logging/index.js +0 -1
  85. package/dist/services/logging/index.js.map +1 -1
  86. package/dist/services/logging/paths.d.ts +8 -0
  87. package/dist/services/logging/paths.js +48 -0
  88. package/dist/services/logging/paths.js.map +1 -0
  89. package/dist/services/logging/raw-log.d.ts +6 -0
  90. package/dist/services/logging/raw-log.js +37 -0
  91. package/dist/services/logging/raw-log.js.map +1 -0
  92. package/dist/services/process/index.js +1 -1
  93. package/dist/services/process/index.js.map +1 -1
  94. package/dist/types/agent.d.ts +15 -0
  95. package/dist/types/config.d.ts +22 -1
  96. package/dist/types/event-categories.d.ts +601 -0
  97. package/dist/types/event-categories.js +233 -0
  98. package/dist/types/event-categories.js.map +1 -0
  99. package/dist/types/events.d.ts +0 -20
  100. package/dist/types/flow.d.ts +10 -6
  101. package/dist/types/index.d.ts +1 -1
  102. package/dist/types/index.js +17 -3
  103. package/dist/types/index.js.map +1 -1
  104. package/dist/types/lane.d.ts +1 -1
  105. package/dist/types/logging.d.ts +1 -1
  106. package/dist/types/task.d.ts +12 -1
  107. package/dist/ui/log-viewer.d.ts +3 -0
  108. package/dist/ui/log-viewer.js +3 -0
  109. package/dist/ui/log-viewer.js.map +1 -1
  110. package/dist/utils/config.js +10 -1
  111. package/dist/utils/config.js.map +1 -1
  112. package/dist/utils/cursor-agent.d.ts +11 -1
  113. package/dist/utils/cursor-agent.js +63 -16
  114. package/dist/utils/cursor-agent.js.map +1 -1
  115. package/dist/utils/enhanced-logger.d.ts +5 -1
  116. package/dist/utils/enhanced-logger.js +98 -19
  117. package/dist/utils/enhanced-logger.js.map +1 -1
  118. package/dist/utils/event-registry.d.ts +222 -0
  119. package/dist/utils/event-registry.js +463 -0
  120. package/dist/utils/event-registry.js.map +1 -0
  121. package/dist/utils/events.d.ts +1 -13
  122. package/dist/utils/events.js.map +1 -1
  123. package/dist/utils/flow.d.ts +10 -0
  124. package/dist/utils/flow.js +75 -0
  125. package/dist/utils/flow.js.map +1 -1
  126. package/dist/utils/log-constants.d.ts +1 -0
  127. package/dist/utils/log-constants.js +2 -1
  128. package/dist/utils/log-constants.js.map +1 -1
  129. package/dist/utils/log-formatter.d.ts +2 -1
  130. package/dist/utils/log-formatter.js +10 -10
  131. package/dist/utils/log-formatter.js.map +1 -1
  132. package/dist/utils/logger.d.ts +11 -0
  133. package/dist/utils/logger.js +82 -3
  134. package/dist/utils/logger.js.map +1 -1
  135. package/dist/utils/repro-thinking-logs.js +0 -13
  136. package/dist/utils/repro-thinking-logs.js.map +1 -1
  137. package/dist/utils/run-service.js +1 -1
  138. package/dist/utils/run-service.js.map +1 -1
  139. package/examples/README.md +0 -2
  140. package/examples/demo-project/README.md +1 -2
  141. package/package.json +18 -28
  142. package/scripts/setup-security.sh +0 -1
  143. package/scripts/test-log-parser.ts +171 -0
  144. package/scripts/verify-change.sh +272 -0
  145. package/src/cli/logs.ts +121 -10
  146. package/src/cli/models.ts +20 -3
  147. package/src/cli/monitor.ts +1257 -1342
  148. package/src/cli/resume.ts +27 -1
  149. package/src/cli/run.ts +29 -11
  150. package/src/cli/signal.ts +115 -17
  151. package/src/cli/tasks.ts +2 -59
  152. package/src/core/agent-supervisor.ts +64 -0
  153. package/src/core/auto-recovery.ts +7 -1
  154. package/src/core/failure-policy.ts +0 -1
  155. package/src/core/git-lifecycle-manager.ts +1011 -0
  156. package/src/core/git-pipeline-coordinator.ts +221 -0
  157. package/src/core/intervention.ts +481 -0
  158. package/src/core/lane-state-machine.ts +1097 -0
  159. package/src/core/orchestrator.ts +35 -61
  160. package/src/core/runner/agent.ts +66 -33
  161. package/src/core/runner/pipeline.ts +318 -138
  162. package/src/core/runner/task.ts +12 -97
  163. package/src/core/runner.ts +8 -2
  164. package/src/core/stall-detection.ts +72 -27
  165. package/src/hooks/contexts/index.ts +256 -0
  166. package/src/hooks/data-accessor.ts +488 -0
  167. package/src/hooks/flow-controller.ts +425 -0
  168. package/src/hooks/index.ts +154 -0
  169. package/src/hooks/manager.ts +434 -0
  170. package/src/hooks/types.ts +544 -0
  171. package/src/services/logging/buffer.ts +104 -43
  172. package/src/services/logging/console.ts +7 -1
  173. package/src/services/logging/formatter.ts +74 -18
  174. package/src/services/logging/index.ts +0 -2
  175. package/src/services/logging/paths.ts +14 -0
  176. package/src/services/logging/raw-log.ts +43 -0
  177. package/src/services/process/index.ts +1 -1
  178. package/src/types/agent.ts +15 -0
  179. package/src/types/config.ts +23 -1
  180. package/src/types/event-categories.ts +663 -0
  181. package/src/types/events.ts +0 -25
  182. package/src/types/flow.ts +10 -6
  183. package/src/types/index.ts +50 -4
  184. package/src/types/lane.ts +1 -2
  185. package/src/types/logging.ts +2 -1
  186. package/src/types/task.ts +12 -1
  187. package/src/ui/log-viewer.ts +3 -0
  188. package/src/utils/config.ts +11 -1
  189. package/src/utils/cursor-agent.ts +68 -16
  190. package/src/utils/enhanced-logger.ts +105 -19
  191. package/src/utils/event-registry.ts +595 -0
  192. package/src/utils/events.ts +0 -16
  193. package/src/utils/flow.ts +83 -0
  194. package/src/utils/log-constants.ts +2 -1
  195. package/src/utils/log-formatter.ts +10 -11
  196. package/src/utils/logger.ts +49 -3
  197. package/src/utils/repro-thinking-logs.ts +0 -15
  198. package/src/utils/run-service.ts +1 -1
  199. package/dist/services/logging/file-writer.d.ts +0 -71
  200. package/dist/services/logging/file-writer.js +0 -516
  201. package/dist/services/logging/file-writer.js.map +0 -1
  202. package/dist/types/review.d.ts +0 -17
  203. package/dist/types/review.js +0 -6
  204. package/dist/types/review.js.map +0 -1
  205. package/scripts/ai-security-check.js +0 -233
  206. package/src/services/logging/file-writer.ts +0 -526
  207. package/src/types/review.ts +0 -20
@@ -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-lanename10]
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, 10);
286
- const combined = `${laneNum}${taskPart}-${shortLaneName}`.substring(0, 18).padEnd(18);
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 non-JSON lines
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
- // Filter out JSON lines from console output to keep it clean
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
- // Show if it's a timestamped log line (starts with [YYYY-MM-DD... or [HH:MM:SS])
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
- // Only trigger activity for non-heartbeat lines
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 = safeJoin(laneRunDir, 'terminal-readable.log');
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
- const analysis = stallService.checkAndRecover(laneName);
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) {
@@ -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' | 'plain' | 'stream-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
- const args = ['send', chatId, prompt];
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', 'json');
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
- // Ensure non-interactive execution with automatic approvals
157
- args.push('--force', '--approve-mcps');
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: enableIntervention ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
197
+ stdio: ['pipe', 'pipe', 'pipe'],
170
198
  });
171
199
 
172
200
  activeChildren.add(child);
173
-
174
- if (!enableIntervention) {
175
- logger.info('ℹ️ Intervention is disabled. Stall recovery using "continue" will not be available for this agent session.');
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 intervention and timeout
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 intervention or timeout signals from UI
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
- logger.info(`👋 Human intervention received: ${message.substring(0, 50)}...`);
202
- if (child.stdin && child.stdin.writable) {
203
- child.stdin.write(message + '\n');
204
-
205
- if (signalDir) {
206
- const convoPath = path.join(signalDir, 'conversation.jsonl');
207
- appendLog(convoPath, createConversationEntry('intervention', `[HUMAN INTERVENTION]: ${message}`, {
208
- task: taskName || 'AGENT_TURN',
209
- model: 'manual'
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' | 'plain' | 'stream-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