@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
@@ -1,17 +1,12 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
3
2
  import * as git from '../../utils/git';
4
3
  import * as logger from '../../utils/logger';
5
4
  import { events } from '../../utils/events';
6
5
  import { safeJoin } from '../../utils/path';
7
6
  import { appendLog, createConversationEntry } from '../../utils/state';
8
- import { Task, RunnerConfig, TaskExecutionResult, LaneState } from '../../types';
9
- import { loadState } from '../../utils/state';
7
+ import { Task, RunnerConfig, TaskExecutionResult } from '../../types';
10
8
  import { waitForTaskDependencies as waitForDeps, DependencyWaitOptions } from '../../utils/dependency';
11
- import {
12
- cursorAgentSend,
13
- extractDependencyRequest
14
- } from './agent';
9
+ import { extractDependencyRequest } from './agent';
15
10
  import {
16
11
  wrapPrompt,
17
12
  applyDependencyFilePermissions
@@ -23,6 +18,7 @@ import {
23
18
  clearDependencyRequestFile,
24
19
  DependencyResult
25
20
  } from './utils';
21
+ import { AgentSupervisor } from '../agent-supervisor';
26
22
 
27
23
  /**
28
24
  * Wait for task-level dependencies to be completed by other lanes
@@ -56,74 +52,6 @@ export async function waitForTaskDependencies(
56
52
  }
57
53
  }
58
54
 
59
- /**
60
- * Merge branches from dependency lanes with safe merge and conflict pre-check
61
- */
62
- export async function mergeDependencyBranches(deps: string[], runDir: string, worktreeDir: string, pipelineBranch: string): Promise<void> {
63
- if (!deps || deps.length === 0) return;
64
-
65
- const lanesRoot = path.dirname(runDir);
66
- const lanesToMerge = new Set(deps.map(d => d.split(':')[0]!));
67
-
68
- // Ensure we are on the pipeline branch before merging dependencies
69
- logger.info(`🔄 Syncing with ${pipelineBranch} before merging dependencies`);
70
- git.runGit(['checkout', pipelineBranch], { cwd: worktreeDir });
71
-
72
- for (const laneName of lanesToMerge) {
73
- const depStatePath = safeJoin(lanesRoot, laneName, 'state.json');
74
- if (!fs.existsSync(depStatePath)) continue;
75
-
76
- try {
77
- const state = loadState<LaneState>(depStatePath);
78
- if (!state?.pipelineBranch) continue;
79
-
80
- logger.info(`Merging branch from ${laneName}: ${state.pipelineBranch}`);
81
-
82
- // Ensure we have the latest
83
- git.runGit(['fetch', 'origin', state.pipelineBranch], { cwd: worktreeDir, silent: true });
84
-
85
- // Pre-check for conflicts before attempting merge
86
- const conflictCheck = git.checkMergeConflict(state.pipelineBranch, { cwd: worktreeDir });
87
-
88
- if (conflictCheck.willConflict) {
89
- logger.warn(`⚠️ Pre-check: Merge conflict detected with ${laneName}`);
90
- logger.warn(` Conflicting files: ${conflictCheck.conflictingFiles.join(', ')}`);
91
-
92
- // Emit event for potential auto-recovery or notification
93
- events.emit('merge.conflict_detected', {
94
- laneName,
95
- targetBranch: state.pipelineBranch,
96
- conflictingFiles: conflictCheck.conflictingFiles,
97
- preCheck: true,
98
- });
99
-
100
- throw new Error(`Pre-merge conflict check failed: ${conflictCheck.conflictingFiles.join(', ')}. Consider rebasing or resolving conflicts manually.`);
101
- }
102
-
103
- // Use safe merge with conflict detection
104
- const mergeResult = git.safeMerge(state.pipelineBranch, {
105
- cwd: worktreeDir,
106
- noFf: true,
107
- message: `chore: merge task dependency from ${laneName}`,
108
- abortOnConflict: true,
109
- });
110
-
111
- if (!mergeResult.success) {
112
- if (mergeResult.conflict) {
113
- logger.error(`Merge conflict with ${laneName}: ${mergeResult.conflictingFiles.join(', ')}`);
114
- throw new Error(`Merge conflict: ${mergeResult.conflictingFiles.join(', ')}`);
115
- }
116
- throw new Error(mergeResult.error || 'Merge failed');
117
- }
118
-
119
- logger.success(`✓ Merged ${laneName}`);
120
- } catch (e) {
121
- logger.error(`Failed to merge branch from ${laneName}: ${e}`);
122
- throw e;
123
- }
124
- }
125
- }
126
-
127
55
  /**
128
56
  * Run a single task
129
57
  */
@@ -137,6 +65,8 @@ export async function runTask({
137
65
  chatId,
138
66
  runDir,
139
67
  runRoot,
68
+ agentSupervisor,
69
+ laneName,
140
70
  }: {
141
71
  task: Task;
142
72
  config: RunnerConfig;
@@ -147,6 +77,8 @@ export async function runTask({
147
77
  chatId: string;
148
78
  runDir: string;
149
79
  runRoot?: string;
80
+ agentSupervisor: AgentSupervisor;
81
+ laneName: string;
150
82
  }): Promise<TaskExecutionResult> {
151
83
  // Calculate runRoot if not provided (runDir is lanes/{laneName}/, runRoot is parent of lanes/)
152
84
  const calculatedRunRoot = runRoot || path.dirname(path.dirname(runDir));
@@ -212,32 +144,20 @@ export async function runTask({
212
144
  }));
213
145
 
214
146
  logger.info('Sending prompt to agent...');
215
- const startTime = Date.now();
216
- events.emit('agent.prompt_sent', {
217
- taskName: task.name,
218
- model,
219
- promptLength: wrappedPrompt.length,
220
- });
221
-
222
- const r1 = await cursorAgentSend({
147
+ const r1 = await agentSupervisor.sendTaskPrompt({
223
148
  workspaceDir: worktreeDir,
224
149
  chatId,
225
150
  prompt: wrappedPrompt,
226
151
  model,
152
+ laneName,
227
153
  signalDir: runDir,
228
154
  timeout,
229
155
  enableIntervention: config.enableIntervention,
230
156
  outputFormat: config.agentOutputFormat,
231
157
  taskName: task.name,
232
- });
233
-
234
- const duration = Date.now() - startTime;
235
- events.emit('agent.response_received', {
236
- taskName: task.name,
237
- ok: r1.ok,
238
- duration,
239
- responseLength: r1.resultText?.length || 0,
240
- error: r1.error,
158
+ browser: task.browser || config.browser,
159
+ autoApproveCommands: config.autoApproveCommands,
160
+ autoApproveMcps: config.autoApproveMcps,
241
161
  });
242
162
 
243
163
  appendLog(convoPath, createConversationEntry('assistant', r1.resultText || r1.error || 'No response', {
@@ -304,4 +224,3 @@ export async function runTask({
304
224
  status: 'FINISHED',
305
225
  };
306
226
  }
307
-
@@ -36,6 +36,8 @@ if (require.main === module) {
36
36
  const startIdxIdx = args.indexOf('--start-index');
37
37
  const pipelineBranchIdx = args.indexOf('--pipeline-branch');
38
38
  const worktreeDirIdx = args.indexOf('--worktree-dir');
39
+ const browser = args.includes('--browser');
40
+ const skipPreflight = args.includes('--skip-preflight');
39
41
 
40
42
  const runDir = runDirIdx >= 0 ? args[runDirIdx + 1]! : '.';
41
43
  const startIndex = startIdxIdx >= 0 ? parseInt(args[startIdxIdx + 1] || '0') : 0;
@@ -75,6 +77,9 @@ if (require.main === module) {
75
77
  if (forcedWorktreeDir) {
76
78
  config.worktreeDir = forcedWorktreeDir;
77
79
  }
80
+ if (browser) {
81
+ config.browser = true;
82
+ }
78
83
  } catch (error: any) {
79
84
  console.error(`Failed to load tasks file: ${error.message}`);
80
85
  process.exit(1);
@@ -87,7 +92,12 @@ if (require.main === module) {
87
92
  };
88
93
 
89
94
  // Add agent output format default
90
- config.agentOutputFormat = config.agentOutputFormat || globalConfig?.agentOutputFormat || 'json';
95
+ config.agentOutputFormat = config.agentOutputFormat || globalConfig?.agentOutputFormat || 'stream-json';
96
+
97
+ // Merge intervention and logging settings
98
+ config.enableIntervention = config.enableIntervention ?? globalConfig?.enableIntervention ?? true;
99
+ config.verboseGit = config.verboseGit ?? globalConfig?.verboseGit ?? false;
100
+ config.browser = config.browser ?? globalConfig?.browser ?? false;
91
101
 
92
102
  // Handle process interruption to ensure cleanup
93
103
  const handleSignal = (signal: string) => {
@@ -101,7 +111,7 @@ if (require.main === module) {
101
111
  process.on('SIGTERM', () => handleSignal('SIGTERM'));
102
112
 
103
113
  // Run tasks
104
- runTasks(tasksFile, config, runDir, { startIndex })
114
+ runTasks(tasksFile, config, runDir, { startIndex, skipPreflight })
105
115
  .then(() => {
106
116
  process.exit(0);
107
117
  })
@@ -18,6 +18,14 @@ import { ChildProcess } from 'child_process';
18
18
  import * as logger from '../utils/logger';
19
19
  import { events } from '../utils/events';
20
20
  import { safeJoin } from '../utils/path';
21
+ import {
22
+ createInterventionRequest,
23
+ InterventionType,
24
+ createContinueMessage,
25
+ createStrongerPromptMessage,
26
+ createRestartMessage,
27
+ killAndWait,
28
+ } from './intervention';
21
29
 
22
30
  // ============================================================================
23
31
  // 설정 (Configuration)
@@ -142,6 +150,10 @@ export interface LaneStallState {
142
150
  laneName: string;
143
151
  /** 현재 복구 단계 */
144
152
  phase: StallPhase;
153
+ /** Lane의 현재 상태 (waiting, running 등) - waiting 시 stall 분석 스킵 */
154
+ laneStatus?: string;
155
+ /** Intervention 활성화 여부 - false면 continue 신호 스킵 */
156
+ interventionEnabled?: boolean;
145
157
  /** 마지막 실제 활동 시간 (bytes > 0) */
146
158
  lastRealActivityTime: number;
147
159
  /** 마지막 상태 변경 시간 (phase 변경) */
@@ -294,6 +306,7 @@ export class StallDetectionService {
294
306
  laneRunDir?: string;
295
307
  childProcess?: ChildProcess;
296
308
  startIndex?: number;
309
+ interventionEnabled?: boolean;
297
310
  } = {}
298
311
  ): void {
299
312
  const now = Date.now();
@@ -301,6 +314,7 @@ export class StallDetectionService {
301
314
  this.laneStates.set(laneName, {
302
315
  laneName,
303
316
  phase: StallPhase.NORMAL,
317
+ interventionEnabled: options.interventionEnabled ?? true, // default to true
304
318
  lastRealActivityTime: now,
305
319
  lastPhaseChangeTime: now,
306
320
  lastStateUpdateTime: now,
@@ -318,7 +332,7 @@ export class StallDetectionService {
318
332
  });
319
333
 
320
334
  if (this.config.verbose) {
321
- logger.debug(`[StallService] Lane registered: ${laneName}`);
335
+ logger.debug(`[StallService] Lane registered: ${laneName} (intervention: ${options.interventionEnabled ?? true})`);
322
336
  }
323
337
  }
324
338
 
@@ -360,6 +374,44 @@ export class StallDetectionService {
360
374
  }
361
375
  }
362
376
 
377
+ /**
378
+ * Lane 상태 업데이트 (waiting, running 등)
379
+ * waiting 상태일 때는 stall 분석을 스킵함
380
+ */
381
+ setLaneStatus(laneName: string, status: string): void {
382
+ const state = this.laneStates.get(laneName);
383
+ if (state) {
384
+ state.laneStatus = status;
385
+
386
+ // waiting 상태로 전환 시 타이머 리셋 (의존성 대기 시간을 stall로 간주하지 않음)
387
+ if (status === 'waiting') {
388
+ const now = Date.now();
389
+ state.lastRealActivityTime = now;
390
+ state.lastStateUpdateTime = now;
391
+ state.taskStartTime = now;
392
+ }
393
+
394
+ if (this.config.verbose) {
395
+ logger.debug(`[StallService] [${laneName}] Lane status updated: ${status}`);
396
+ }
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Intervention 활성화 상태 설정
402
+ * false로 설정하면 continue 신호를 보내지 않음
403
+ */
404
+ setInterventionEnabled(laneName: string, enabled: boolean): void {
405
+ const state = this.laneStates.get(laneName);
406
+ if (state) {
407
+ state.interventionEnabled = enabled;
408
+
409
+ if (this.config.verbose) {
410
+ logger.debug(`[StallService] [${laneName}] Intervention ${enabled ? 'enabled' : 'disabled'}`);
411
+ }
412
+ }
413
+ }
414
+
363
415
  // --------------------------------------------------------------------------
364
416
  // 활동 기록 (Activity Recording)
365
417
  // --------------------------------------------------------------------------
@@ -450,6 +502,7 @@ export class StallDetectionService {
450
502
  * Stall 상태 분석 - 현재 상태에서 필요한 액션 결정
451
503
  *
452
504
  * 분석 우선순위:
505
+ * 0. waiting 상태면 스킵 (의존성 대기 중)
453
506
  * 1. Task timeout (30분) → RESTART/DOCTOR
454
507
  * 2. Zero bytes + idle → phase별 에스컬레이션
455
508
  * 3. No progress (10분) → 단계별 에스컬레이션
@@ -461,6 +514,17 @@ export class StallDetectionService {
461
514
  return this.buildAnalysis(StallType.IDLE, RecoveryAction.NONE, 'Lane not found', false);
462
515
  }
463
516
 
517
+ // 0. waiting 상태는 stall 분석 스킵 (의존성 대기 중이므로 정상)
518
+ if (state.laneStatus === 'waiting') {
519
+ return this.buildAnalysis(
520
+ StallType.IDLE,
521
+ RecoveryAction.NONE,
522
+ 'Waiting for dependencies',
523
+ true,
524
+ { laneStatus: 'waiting' }
525
+ );
526
+ }
527
+
464
528
  const ctx = this.buildAnalysisContext(state);
465
529
 
466
530
  // 1. Task timeout (최우선)
@@ -676,8 +740,10 @@ export class StallDetectionService {
676
740
  * Stall 체크 및 복구 액션 실행
677
741
  *
678
742
  * @returns 실행된 분석 결과 (orchestrator에서 추가 처리 필요시 사용)
743
+ *
744
+ * 새로운 방식에서는 복구 액션이 프로세스 중단을 포함하므로 async
679
745
  */
680
- checkAndRecover(laneName: string): StallAnalysis {
746
+ async checkAndRecover(laneName: string): Promise<StallAnalysis> {
681
747
  const state = this.laneStates.get(laneName);
682
748
  if (!state) {
683
749
  return {
@@ -704,18 +770,18 @@ export class StallDetectionService {
704
770
  // 실패 이력 기록
705
771
  this.recordFailure(state, analysis);
706
772
 
707
- // 액션 실행
773
+ // 액션 실행 (프로세스 중단 포함 - await 필요)
708
774
  switch (analysis.action) {
709
775
  case RecoveryAction.SEND_CONTINUE:
710
- this.sendContinueSignal(state);
776
+ await this.sendContinueSignal(state);
711
777
  break;
712
778
 
713
779
  case RecoveryAction.SEND_STRONGER_PROMPT:
714
- this.sendStrongerPrompt(state);
780
+ await this.sendStrongerPrompt(state);
715
781
  break;
716
782
 
717
783
  case RecoveryAction.REQUEST_RESTART:
718
- this.requestRestart(state);
784
+ await this.requestRestart(state);
719
785
  break;
720
786
 
721
787
  case RecoveryAction.RUN_DOCTOR:
@@ -731,24 +797,47 @@ export class StallDetectionService {
731
797
  }
732
798
 
733
799
  /**
734
- * Continue 신호 발송
800
+ * Continue 신호 발송 - 프로세스 중단 및 개입 메시지와 함께 resume
801
+ *
802
+ * 새로운 방식:
803
+ * 1. pending-intervention.json 생성
804
+ * 2. 현재 프로세스 SIGTERM으로 종료
805
+ * 3. Orchestrator가 감지하여 개입 메시지와 함께 resume
735
806
  */
736
- private sendContinueSignal(state: LaneStallState): void {
807
+ private async sendContinueSignal(state: LaneStallState): Promise<void> {
808
+ // Intervention이 비활성화된 경우 신호를 보내지 않고 phase만 업데이트
809
+ if (state.interventionEnabled === false) {
810
+ logger.warn(`[${state.laneName}] Continue signal skipped (intervention disabled). Stall will escalate on next check.`);
811
+ state.phase = StallPhase.CONTINUE_SENT;
812
+ state.lastPhaseChangeTime = Date.now();
813
+ return;
814
+ }
815
+
737
816
  if (!state.laneRunDir) {
738
817
  logger.error(`[StallService] [${state.laneName}] Cannot send continue signal: laneRunDir not set`);
739
818
  return;
740
819
  }
741
820
 
742
- const interventionPath = safeJoin(state.laneRunDir, 'intervention.txt');
743
-
744
821
  try {
745
- fs.writeFileSync(interventionPath, 'continue');
822
+ // 1. 개입 요청 생성
823
+ createInterventionRequest(state.laneRunDir, {
824
+ type: InterventionType.CONTINUE_SIGNAL,
825
+ message: createContinueMessage(),
826
+ source: 'stall-detector',
827
+ priority: 5,
828
+ });
829
+
830
+ // 2. 프로세스 종료 (있는 경우)
831
+ if (state.childProcess?.pid && !state.childProcess.killed) {
832
+ logger.info(`[${state.laneName}] Interrupting process ${state.childProcess.pid} for continue signal`);
833
+ await killAndWait(state.childProcess.pid);
834
+ }
746
835
 
747
836
  state.phase = StallPhase.CONTINUE_SENT;
748
837
  state.lastPhaseChangeTime = Date.now();
749
838
  state.continueSignalCount++;
750
839
 
751
- logger.info(`[${state.laneName}] Sent continue signal (#${state.continueSignalCount})`);
840
+ logger.info(`[${state.laneName}] Continue signal queued (#${state.continueSignalCount}) - agent will resume with intervention`);
752
841
 
753
842
  events.emit('recovery.continue_signal', {
754
843
  laneName: state.laneName,
@@ -761,28 +850,41 @@ export class StallDetectionService {
761
850
  }
762
851
 
763
852
  /**
764
- * Stronger prompt 발송
853
+ * Stronger prompt 발송 - 프로세스 중단 및 강력한 개입 메시지와 함께 resume
765
854
  */
766
- private sendStrongerPrompt(state: LaneStallState): void {
855
+ private async sendStrongerPrompt(state: LaneStallState): Promise<void> {
856
+ // Intervention이 비활성화된 경우 신호를 보내지 않고 phase만 업데이트
857
+ if (state.interventionEnabled === false) {
858
+ logger.warn(`[${state.laneName}] Stronger prompt skipped (intervention disabled). Will escalate to restart.`);
859
+ state.phase = StallPhase.STRONGER_PROMPT_SENT;
860
+ state.lastPhaseChangeTime = Date.now();
861
+ return;
862
+ }
863
+
767
864
  if (!state.laneRunDir) {
768
865
  logger.error(`[StallService] [${state.laneName}] Cannot send stronger prompt: laneRunDir not set`);
769
866
  return;
770
867
  }
771
868
 
772
- const interventionPath = safeJoin(state.laneRunDir, 'intervention.txt');
773
- const prompt = `[SYSTEM INTERVENTION] You seem to be stuck or waiting.
774
- Please continue with your current task immediately.
775
- If you're waiting for something, explain what you need and proceed with what you can do now.
776
- If you've completed the task, please summarize your work and finish.
777
- If you encountered a git error, resolve it and continue.`;
778
-
779
869
  try {
780
- fs.writeFileSync(interventionPath, prompt);
870
+ // 1. 개입 요청 생성
871
+ createInterventionRequest(state.laneRunDir, {
872
+ type: InterventionType.STRONGER_PROMPT,
873
+ message: createStrongerPromptMessage(),
874
+ source: 'stall-detector',
875
+ priority: 7,
876
+ });
877
+
878
+ // 2. 프로세스 종료 (있는 경우)
879
+ if (state.childProcess?.pid && !state.childProcess.killed) {
880
+ logger.warn(`[${state.laneName}] Interrupting process ${state.childProcess.pid} for stronger prompt`);
881
+ await killAndWait(state.childProcess.pid);
882
+ }
781
883
 
782
884
  state.phase = StallPhase.STRONGER_PROMPT_SENT;
783
885
  state.lastPhaseChangeTime = Date.now();
784
886
 
785
- logger.warn(`[${state.laneName}] Sent stronger prompt after continue signal failed`);
887
+ logger.warn(`[${state.laneName}] Stronger prompt queued - agent will resume with intervention`);
786
888
 
787
889
  events.emit('recovery.stronger_prompt', {
788
890
  laneName: state.laneName,
@@ -793,16 +895,31 @@ If you encountered a git error, resolve it and continue.`;
793
895
  }
794
896
 
795
897
  /**
796
- * 재시작 요청 (프로세스 종료)
898
+ * 재시작 요청 - 프로세스 종료 및 재시작 메시지와 함께 resume
797
899
  */
798
- private requestRestart(state: LaneStallState): void {
900
+ private async requestRestart(state: LaneStallState): Promise<void> {
799
901
  state.restartCount++;
800
902
  state.phase = StallPhase.RESTART_REQUESTED;
801
903
  state.lastPhaseChangeTime = Date.now();
802
904
 
803
- // 프로세스 종료
804
- if (state.childProcess && !state.childProcess.killed) {
905
+ // 1. 개입 요청 생성 (재시작 메시지)
906
+ if (state.laneRunDir) {
907
+ createInterventionRequest(state.laneRunDir, {
908
+ type: InterventionType.SYSTEM_RESTART,
909
+ message: createRestartMessage('Agent became unresponsive after multiple intervention attempts'),
910
+ source: 'stall-detector',
911
+ priority: 9,
912
+ metadata: {
913
+ restartCount: state.restartCount,
914
+ maxRestarts: this.config.maxRestarts,
915
+ },
916
+ });
917
+ }
918
+
919
+ // 2. 프로세스 종료 (SIGKILL 사용 - 강제 종료)
920
+ if (state.childProcess?.pid && !state.childProcess.killed) {
805
921
  try {
922
+ // SIGKILL로 즉시 종료 (SIGTERM이 안 먹힐 수 있으므로)
806
923
  state.childProcess.kill('SIGKILL');
807
924
  logger.info(`[StallService] [${state.laneName}] Killed process ${state.childProcess.pid}`);
808
925
  } catch (error: any) {
@@ -810,7 +927,7 @@ If you encountered a git error, resolve it and continue.`;
810
927
  }
811
928
  }
812
929
 
813
- logger.warn(`[${state.laneName}] Killing and restarting lane (restart #${state.restartCount})`);
930
+ logger.warn(`[${state.laneName}] Restart requested (restart #${state.restartCount}/${this.config.maxRestarts})`);
814
931
 
815
932
  events.emit('recovery.restart', {
816
933
  laneName: state.laneName,