@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.
Files changed (214) hide show
  1. package/CHANGELOG.md +0 -2
  2. package/README.md +20 -16
  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/prepare.js +0 -1
  12. package/dist/cli/prepare.js.map +1 -1
  13. package/dist/cli/resume.js +23 -5
  14. package/dist/cli/resume.js.map +1 -1
  15. package/dist/cli/run.js +28 -9
  16. package/dist/cli/run.js.map +1 -1
  17. package/dist/cli/signal.d.ts +6 -1
  18. package/dist/cli/signal.js +94 -12
  19. package/dist/cli/signal.js.map +1 -1
  20. package/dist/cli/tasks.js +3 -46
  21. package/dist/cli/tasks.js.map +1 -1
  22. package/dist/core/agent-supervisor.d.ts +23 -0
  23. package/dist/core/agent-supervisor.js +42 -0
  24. package/dist/core/agent-supervisor.js.map +1 -0
  25. package/dist/core/auto-recovery.d.ts +2 -1
  26. package/dist/core/auto-recovery.js +6 -1
  27. package/dist/core/auto-recovery.js.map +1 -1
  28. package/dist/core/failure-policy.d.ts +0 -1
  29. package/dist/core/failure-policy.js +0 -1
  30. package/dist/core/failure-policy.js.map +1 -1
  31. package/dist/core/git-lifecycle-manager.d.ts +284 -0
  32. package/dist/core/git-lifecycle-manager.js +778 -0
  33. package/dist/core/git-lifecycle-manager.js.map +1 -0
  34. package/dist/core/git-pipeline-coordinator.d.ts +21 -0
  35. package/dist/core/git-pipeline-coordinator.js +205 -0
  36. package/dist/core/git-pipeline-coordinator.js.map +1 -0
  37. package/dist/core/intervention.d.ts +176 -0
  38. package/dist/core/intervention.js +424 -0
  39. package/dist/core/intervention.js.map +1 -0
  40. package/dist/core/lane-state-machine.d.ts +423 -0
  41. package/dist/core/lane-state-machine.js +890 -0
  42. package/dist/core/lane-state-machine.js.map +1 -0
  43. package/dist/core/orchestrator.d.ts +4 -1
  44. package/dist/core/orchestrator.js +38 -63
  45. package/dist/core/orchestrator.js.map +1 -1
  46. package/dist/core/runner/agent.d.ts +7 -1
  47. package/dist/core/runner/agent.js +45 -30
  48. package/dist/core/runner/agent.js.map +1 -1
  49. package/dist/core/runner/pipeline.js +283 -109
  50. package/dist/core/runner/pipeline.js.map +1 -1
  51. package/dist/core/runner/task.d.ts +4 -5
  52. package/dist/core/runner/task.js +6 -77
  53. package/dist/core/runner/task.js.map +1 -1
  54. package/dist/core/runner.js +11 -2
  55. package/dist/core/runner.js.map +1 -1
  56. package/dist/core/stall-detection.d.ts +27 -4
  57. package/dist/core/stall-detection.js +116 -28
  58. package/dist/core/stall-detection.js.map +1 -1
  59. package/dist/hooks/contexts/index.d.ts +104 -0
  60. package/dist/hooks/contexts/index.js +134 -0
  61. package/dist/hooks/contexts/index.js.map +1 -0
  62. package/dist/hooks/data-accessor.d.ts +86 -0
  63. package/dist/hooks/data-accessor.js +410 -0
  64. package/dist/hooks/data-accessor.js.map +1 -0
  65. package/dist/hooks/flow-controller.d.ts +136 -0
  66. package/dist/hooks/flow-controller.js +351 -0
  67. package/dist/hooks/flow-controller.js.map +1 -0
  68. package/dist/hooks/index.d.ts +68 -0
  69. package/dist/hooks/index.js +105 -0
  70. package/dist/hooks/index.js.map +1 -0
  71. package/dist/hooks/manager.d.ts +129 -0
  72. package/dist/hooks/manager.js +389 -0
  73. package/dist/hooks/manager.js.map +1 -0
  74. package/dist/hooks/types.d.ts +463 -0
  75. package/dist/hooks/types.js +45 -0
  76. package/dist/hooks/types.js.map +1 -0
  77. package/dist/services/logging/buffer.d.ts +2 -2
  78. package/dist/services/logging/buffer.js +95 -42
  79. package/dist/services/logging/buffer.js.map +1 -1
  80. package/dist/services/logging/console.js +8 -5
  81. package/dist/services/logging/console.js.map +1 -1
  82. package/dist/services/logging/formatter.d.ts +9 -3
  83. package/dist/services/logging/formatter.js +64 -17
  84. package/dist/services/logging/formatter.js.map +1 -1
  85. package/dist/services/logging/index.d.ts +0 -1
  86. package/dist/services/logging/index.js +0 -1
  87. package/dist/services/logging/index.js.map +1 -1
  88. package/dist/services/logging/paths.d.ts +8 -0
  89. package/dist/services/logging/paths.js +48 -0
  90. package/dist/services/logging/paths.js.map +1 -0
  91. package/dist/services/logging/raw-log.d.ts +6 -0
  92. package/dist/services/logging/raw-log.js +37 -0
  93. package/dist/services/logging/raw-log.js.map +1 -0
  94. package/dist/services/process/index.js +1 -1
  95. package/dist/services/process/index.js.map +1 -1
  96. package/dist/types/agent.d.ts +15 -0
  97. package/dist/types/config.d.ts +24 -1
  98. package/dist/types/event-categories.d.ts +601 -0
  99. package/dist/types/event-categories.js +233 -0
  100. package/dist/types/event-categories.js.map +1 -0
  101. package/dist/types/events.d.ts +0 -20
  102. package/dist/types/flow.d.ts +10 -6
  103. package/dist/types/index.d.ts +1 -1
  104. package/dist/types/index.js +17 -3
  105. package/dist/types/index.js.map +1 -1
  106. package/dist/types/lane.d.ts +1 -1
  107. package/dist/types/logging.d.ts +1 -1
  108. package/dist/types/task.d.ts +13 -2
  109. package/dist/ui/log-viewer.d.ts +3 -0
  110. package/dist/ui/log-viewer.js +3 -0
  111. package/dist/ui/log-viewer.js.map +1 -1
  112. package/dist/utils/config.js +15 -1
  113. package/dist/utils/config.js.map +1 -1
  114. package/dist/utils/cursor-agent.d.ts +11 -1
  115. package/dist/utils/cursor-agent.js +63 -16
  116. package/dist/utils/cursor-agent.js.map +1 -1
  117. package/dist/utils/enhanced-logger.d.ts +5 -1
  118. package/dist/utils/enhanced-logger.js +99 -20
  119. package/dist/utils/enhanced-logger.js.map +1 -1
  120. package/dist/utils/event-registry.d.ts +222 -0
  121. package/dist/utils/event-registry.js +463 -0
  122. package/dist/utils/event-registry.js.map +1 -0
  123. package/dist/utils/events.d.ts +1 -13
  124. package/dist/utils/events.js.map +1 -1
  125. package/dist/utils/flow.d.ts +10 -0
  126. package/dist/utils/flow.js +75 -0
  127. package/dist/utils/flow.js.map +1 -1
  128. package/dist/utils/git.d.ts +12 -1
  129. package/dist/utils/git.js +54 -1
  130. package/dist/utils/git.js.map +1 -1
  131. package/dist/utils/log-constants.d.ts +1 -0
  132. package/dist/utils/log-constants.js +2 -1
  133. package/dist/utils/log-constants.js.map +1 -1
  134. package/dist/utils/log-formatter.d.ts +3 -2
  135. package/dist/utils/log-formatter.js +11 -11
  136. package/dist/utils/log-formatter.js.map +1 -1
  137. package/dist/utils/logger.d.ts +11 -0
  138. package/dist/utils/logger.js +82 -3
  139. package/dist/utils/logger.js.map +1 -1
  140. package/dist/utils/repro-thinking-logs.js +0 -13
  141. package/dist/utils/repro-thinking-logs.js.map +1 -1
  142. package/dist/utils/run-service.js +1 -1
  143. package/dist/utils/run-service.js.map +1 -1
  144. package/examples/README.md +0 -2
  145. package/examples/demo-project/README.md +1 -2
  146. package/package.json +18 -28
  147. package/scripts/setup-security.sh +0 -1
  148. package/scripts/test-log-parser.ts +171 -0
  149. package/scripts/verify-change.sh +272 -0
  150. package/src/cli/logs.ts +121 -10
  151. package/src/cli/models.ts +20 -3
  152. package/src/cli/monitor.ts +1257 -1342
  153. package/src/cli/prepare.ts +0 -1
  154. package/src/cli/resume.ts +29 -5
  155. package/src/cli/run.ts +29 -11
  156. package/src/cli/signal.ts +115 -17
  157. package/src/cli/tasks.ts +2 -59
  158. package/src/core/agent-supervisor.ts +64 -0
  159. package/src/core/auto-recovery.ts +7 -1
  160. package/src/core/failure-policy.ts +0 -1
  161. package/src/core/git-lifecycle-manager.ts +1011 -0
  162. package/src/core/git-pipeline-coordinator.ts +221 -0
  163. package/src/core/intervention.ts +481 -0
  164. package/src/core/lane-state-machine.ts +1097 -0
  165. package/src/core/orchestrator.ts +45 -62
  166. package/src/core/runner/agent.ts +66 -33
  167. package/src/core/runner/pipeline.ts +318 -122
  168. package/src/core/runner/task.ts +12 -93
  169. package/src/core/runner.ts +12 -2
  170. package/src/core/stall-detection.ts +145 -28
  171. package/src/hooks/contexts/index.ts +256 -0
  172. package/src/hooks/data-accessor.ts +488 -0
  173. package/src/hooks/flow-controller.ts +425 -0
  174. package/src/hooks/index.ts +154 -0
  175. package/src/hooks/manager.ts +434 -0
  176. package/src/hooks/types.ts +544 -0
  177. package/src/services/logging/buffer.ts +104 -43
  178. package/src/services/logging/console.ts +9 -5
  179. package/src/services/logging/formatter.ts +74 -17
  180. package/src/services/logging/index.ts +0 -2
  181. package/src/services/logging/paths.ts +14 -0
  182. package/src/services/logging/raw-log.ts +43 -0
  183. package/src/services/process/index.ts +1 -1
  184. package/src/types/agent.ts +15 -0
  185. package/src/types/config.ts +25 -1
  186. package/src/types/event-categories.ts +663 -0
  187. package/src/types/events.ts +0 -25
  188. package/src/types/flow.ts +10 -6
  189. package/src/types/index.ts +50 -4
  190. package/src/types/lane.ts +1 -2
  191. package/src/types/logging.ts +2 -1
  192. package/src/types/task.ts +13 -2
  193. package/src/ui/log-viewer.ts +3 -0
  194. package/src/utils/config.ts +17 -1
  195. package/src/utils/cursor-agent.ts +68 -16
  196. package/src/utils/enhanced-logger.ts +106 -20
  197. package/src/utils/event-registry.ts +595 -0
  198. package/src/utils/events.ts +0 -16
  199. package/src/utils/flow.ts +84 -0
  200. package/src/utils/git.ts +59 -1
  201. package/src/utils/log-constants.ts +2 -1
  202. package/src/utils/log-formatter.ts +11 -12
  203. package/src/utils/logger.ts +49 -3
  204. package/src/utils/repro-thinking-logs.ts +0 -15
  205. package/src/utils/run-service.ts +1 -1
  206. package/dist/services/logging/file-writer.d.ts +0 -71
  207. package/dist/services/logging/file-writer.js +0 -516
  208. package/dist/services/logging/file-writer.js.map +0 -1
  209. package/dist/types/review.d.ts +0 -17
  210. package/dist/types/review.js +0 -6
  211. package/dist/types/review.js.map +0 -1
  212. package/scripts/ai-security-check.js +0 -233
  213. package/src/services/logging/file-writer.ts +0 -526
  214. 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,11 +293,11 @@ export function spawnLane({
278
293
  };
279
294
 
280
295
  if (logConfig.enabled) {
281
- // Helper to get dynamic lane label like [L1-T1-lanename10]
296
+ // Helper to get dynamic lane label like [1-1-refactor]
282
297
  const getDynamicLabel = () => {
283
- const laneNum = `L${laneIndex + 1}`;
284
- const taskPart = info.currentTaskIndex ? `-T${info.currentTaskIndex}` : '';
285
- const shortLaneName = laneName.substring(0, 10);
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 non-JSON lines
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
- // Filter out JSON lines from console output to keep it clean
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
- // Show if it's a timestamped log line (starts with [YYYY-MM-DD... or [HH:MM:SS])
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
- // Only trigger activity for non-heartbeat lines
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 = safeJoin(laneRunDir, 'terminal-readable.log');
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
- const analysis = stallService.checkAndRecover(laneName);
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) {
@@ -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