@litmers/cursorflow-orchestrator 0.2.2 → 0.2.3

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/src/cli/signal.ts CHANGED
@@ -19,6 +19,9 @@ import {
19
19
  executeUserIntervention,
20
20
  isProcessAlive,
21
21
  InterventionResult,
22
+ createInterventionRequest,
23
+ InterventionType,
24
+ wrapUserIntervention,
22
25
  } from '../core/intervention';
23
26
 
24
27
  interface SignalOptions {
@@ -26,7 +29,6 @@ interface SignalOptions {
26
29
  message: string | null;
27
30
  timeout: number | null;
28
31
  runDir: string | null;
29
- force: boolean; // 프로세스 종료 없이 대기 모드로 전송
30
32
  help: boolean;
31
33
  }
32
34
 
@@ -35,8 +37,12 @@ function printHelp(): void {
35
37
  Usage: cursorflow signal <lane> "<message>" [options]
36
38
  cursorflow signal <lane> --timeout <ms>
37
39
 
38
- Directly intervene in a running lane. The agent will be interrupted immediately
39
- and resume with your intervention message.
40
+ Send an intervention message to a lane. For running lanes, the agent will be
41
+ interrupted immediately and resume with your message. For pending/waiting/failed
42
+ lanes, the message will be applied when the lane starts or resumes.
43
+
44
+ Note: Completed lanes cannot receive signals.
45
+ To re-run a completed lane, start a new run with: cursorflow run
40
46
 
41
47
  Arguments:
42
48
  <lane> Lane name to signal
@@ -45,15 +51,12 @@ Arguments:
45
51
  Options:
46
52
  --timeout <ms> Update execution timeout (in milliseconds)
47
53
  --run-dir <path> Use a specific run directory (default: latest)
48
- --force Send signal without interrupting current process
49
- (message will be picked up on next task)
50
54
  --help, -h Show help
51
55
 
52
56
  Examples:
53
57
  cursorflow signal lane-1 "Please focus on error handling first"
54
58
  cursorflow signal lane-2 "Skip the optional tasks and finish"
55
59
  cursorflow signal lane-1 --timeout 600000 # Set 10 minute timeout
56
- cursorflow signal lane-1 "Continue" --force # Don't interrupt, wait for next turn
57
60
  `);
58
61
  }
59
62
 
@@ -61,15 +64,24 @@ function parseArgs(args: string[]): SignalOptions {
61
64
  const runDirIdx = args.indexOf('--run-dir');
62
65
  const timeoutIdx = args.indexOf('--timeout');
63
66
 
67
+ // Collect indices of option values to exclude from nonOptions
68
+ const optionValueIndices = new Set<number>();
69
+ if (runDirIdx >= 0 && runDirIdx + 1 < args.length) {
70
+ optionValueIndices.add(runDirIdx + 1);
71
+ }
72
+ if (timeoutIdx >= 0 && timeoutIdx + 1 < args.length) {
73
+ optionValueIndices.add(timeoutIdx + 1);
74
+ }
75
+
64
76
  // First non-option is lane, second (or rest joined) is message
65
- const nonOptions = args.filter(a => !a.startsWith('--'));
77
+ // Exclude option flags and their values
78
+ const nonOptions = args.filter((a, i) => !a.startsWith('--') && !optionValueIndices.has(i));
66
79
 
67
80
  return {
68
81
  lane: nonOptions[0] || null,
69
82
  message: nonOptions.slice(1).join(' ') || null,
70
83
  timeout: timeoutIdx >= 0 ? parseInt(args[timeoutIdx + 1] || '0') || null : null,
71
84
  runDir: runDirIdx >= 0 ? args[runDirIdx + 1] || null : null,
72
- force: args.includes('--force'),
73
85
  help: args.includes('--help') || args.includes('-h'),
74
86
  };
75
87
  }
@@ -108,18 +120,15 @@ function getLaneStatus(laneDir: string): { state: LaneState | null; isRunning: b
108
120
  }
109
121
 
110
122
  /**
111
- * 기존 방식으로 intervention.txt만 작성 (--force 옵션용)
123
+ * 개입 요청 파일 작성 (비실행 중인 lane용)
112
124
  */
113
- function sendLegacyIntervention(laneDir: string, message: string): void {
114
- const interventionPath = safeJoin(laneDir, 'intervention.txt');
115
- const convoPath = safeJoin(laneDir, 'conversation.jsonl');
116
-
117
- fs.writeFileSync(interventionPath, message);
118
-
119
- const entry = createConversationEntry('intervention', `[HUMAN INTERVENTION]: ${message}`, {
120
- task: 'DIRECT_SIGNAL'
125
+ function sendInterventionRequest(laneDir: string, message: string): void {
126
+ createInterventionRequest(laneDir, {
127
+ type: InterventionType.USER_MESSAGE,
128
+ message: wrapUserIntervention(message),
129
+ source: 'user',
130
+ priority: 10
121
131
  });
122
- appendLog(convoPath, entry);
123
132
  }
124
133
 
125
134
  async function signal(args: string[]): Promise<void> {
@@ -167,30 +176,25 @@ async function signal(args: string[]): Promise<void> {
167
176
  logger.info(`📨 Sending intervention to lane: ${options.lane}`);
168
177
  logger.info(` Message: "${options.message.substring(0, 50)}${options.message.length > 50 ? '...' : ''}"`);
169
178
 
179
+ // Completed 레인은 signal 거부 (브랜치 충돌 방지)
180
+ if (state?.status === 'completed') {
181
+ logger.error(`❌ Cannot signal a completed lane.`);
182
+ logger.info(' To re-run this lane, start a new run with: cursorflow run');
183
+ throw new Error('Lane is already completed');
184
+ }
185
+
170
186
  // Log to conversation for history
171
187
  const entry = createConversationEntry('intervention', `[HUMAN INTERVENTION]: ${options.message}`, {
172
188
  task: 'DIRECT_SIGNAL'
173
189
  });
174
190
  appendLog(convoPath, entry);
175
191
 
176
- // --force: 기존 방식 (프로세스 중단 없이 파일만 작성)
177
- if (options.force) {
178
- sendLegacyIntervention(laneDir, options.message);
179
- logger.success('✅ Signal queued (--force mode). Message will be applied on next task.');
180
- return;
181
- }
182
-
183
- // Lane이 실행 중이 아닌 경우
192
+ // Lane이 실행 중이 아닌 경우 (pending/waiting/failed/paused)
184
193
  if (!isRunning) {
185
- if (state?.status === 'completed') {
186
- logger.warn(`⚠ Lane ${options.lane} is already completed.`);
187
- return;
188
- }
189
-
190
- // 실행 중이 아니면 다음 resume 시 적용되도록 파일만 작성
191
- sendLegacyIntervention(laneDir, options.message);
194
+ // 실행 중이 아니면 다음 시작/resume 시 적용되도록 파일만 작성
195
+ sendInterventionRequest(laneDir, options.message);
192
196
  logger.info(`ℹ Lane ${options.lane} is not currently running (status: ${state?.status || 'unknown'}).`);
193
- logger.success('✅ Signal queued. Message will be applied when lane resumes.');
197
+ logger.success('✅ Signal queued. Message will be applied when lane starts or resumes.');
194
198
  return;
195
199
  }
196
200