@litmers/cursorflow-orchestrator 0.1.40 → 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/CHANGELOG.md +0 -2
- package/README.md +8 -3
- package/commands/cursorflow-init.md +0 -4
- package/dist/cli/index.js +0 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/logs.js +108 -9
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/models.js +20 -3
- package/dist/cli/models.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -10
- package/dist/cli/monitor.js +1103 -1239
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/resume.js +21 -1
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +28 -9
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/signal.d.ts +6 -1
- package/dist/cli/signal.js +99 -13
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/tasks.js +3 -46
- package/dist/cli/tasks.js.map +1 -1
- package/dist/core/agent-supervisor.d.ts +23 -0
- package/dist/core/agent-supervisor.js +42 -0
- package/dist/core/agent-supervisor.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +3 -117
- package/dist/core/auto-recovery.js +4 -482
- package/dist/core/auto-recovery.js.map +1 -1
- package/dist/core/failure-policy.d.ts +0 -53
- package/dist/core/failure-policy.js +7 -175
- package/dist/core/failure-policy.js.map +1 -1
- package/dist/core/git-lifecycle-manager.d.ts +284 -0
- package/dist/core/git-lifecycle-manager.js +778 -0
- package/dist/core/git-lifecycle-manager.js.map +1 -0
- package/dist/core/git-pipeline-coordinator.d.ts +21 -0
- package/dist/core/git-pipeline-coordinator.js +205 -0
- package/dist/core/git-pipeline-coordinator.js.map +1 -0
- package/dist/core/intervention.d.ts +170 -0
- package/dist/core/intervention.js +408 -0
- package/dist/core/intervention.js.map +1 -0
- package/dist/core/lane-state-machine.d.ts +423 -0
- package/dist/core/lane-state-machine.js +890 -0
- package/dist/core/lane-state-machine.js.map +1 -0
- package/dist/core/orchestrator.d.ts +4 -1
- package/dist/core/orchestrator.js +39 -65
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.d.ts +7 -1
- package/dist/core/runner/agent.js +54 -36
- package/dist/core/runner/agent.js.map +1 -1
- package/dist/core/runner/pipeline.js +283 -123
- package/dist/core/runner/pipeline.js.map +1 -1
- package/dist/core/runner/task.d.ts +4 -5
- package/dist/core/runner/task.js +6 -80
- package/dist/core/runner/task.js.map +1 -1
- package/dist/core/runner.js +8 -2
- package/dist/core/runner.js.map +1 -1
- package/dist/core/stall-detection.d.ts +11 -4
- package/dist/core/stall-detection.js +64 -27
- package/dist/core/stall-detection.js.map +1 -1
- package/dist/hooks/contexts/index.d.ts +104 -0
- package/dist/hooks/contexts/index.js +134 -0
- package/dist/hooks/contexts/index.js.map +1 -0
- package/dist/hooks/data-accessor.d.ts +86 -0
- package/dist/hooks/data-accessor.js +410 -0
- package/dist/hooks/data-accessor.js.map +1 -0
- package/dist/hooks/flow-controller.d.ts +136 -0
- package/dist/hooks/flow-controller.js +351 -0
- package/dist/hooks/flow-controller.js.map +1 -0
- package/dist/hooks/index.d.ts +68 -0
- package/dist/hooks/index.js +105 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/manager.d.ts +129 -0
- package/dist/hooks/manager.js +389 -0
- package/dist/hooks/manager.js.map +1 -0
- package/dist/hooks/types.d.ts +463 -0
- package/dist/hooks/types.js +45 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/services/logging/buffer.d.ts +2 -2
- package/dist/services/logging/buffer.js +95 -42
- package/dist/services/logging/buffer.js.map +1 -1
- package/dist/services/logging/console.js +6 -1
- package/dist/services/logging/console.js.map +1 -1
- package/dist/services/logging/formatter.d.ts +9 -4
- package/dist/services/logging/formatter.js +64 -18
- package/dist/services/logging/formatter.js.map +1 -1
- package/dist/services/logging/index.d.ts +0 -1
- package/dist/services/logging/index.js +0 -1
- package/dist/services/logging/index.js.map +1 -1
- package/dist/services/logging/paths.d.ts +8 -0
- package/dist/services/logging/paths.js +48 -0
- package/dist/services/logging/paths.js.map +1 -0
- package/dist/services/logging/raw-log.d.ts +6 -0
- package/dist/services/logging/raw-log.js +37 -0
- package/dist/services/logging/raw-log.js.map +1 -0
- package/dist/services/process/index.js +1 -1
- package/dist/services/process/index.js.map +1 -1
- package/dist/types/agent.d.ts +15 -0
- package/dist/types/config.d.ts +22 -1
- package/dist/types/event-categories.d.ts +601 -0
- package/dist/types/event-categories.js +233 -0
- package/dist/types/event-categories.js.map +1 -0
- package/dist/types/events.d.ts +0 -20
- package/dist/types/flow.d.ts +10 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +17 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/lane.d.ts +1 -1
- package/dist/types/logging.d.ts +1 -1
- package/dist/types/task.d.ts +12 -1
- package/dist/ui/log-viewer.d.ts +3 -0
- package/dist/ui/log-viewer.js +3 -0
- package/dist/ui/log-viewer.js.map +1 -1
- package/dist/utils/config.js +10 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.d.ts +11 -1
- package/dist/utils/cursor-agent.js +63 -16
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +5 -1
- package/dist/utils/enhanced-logger.js +98 -19
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/event-registry.d.ts +222 -0
- package/dist/utils/event-registry.js +463 -0
- package/dist/utils/event-registry.js.map +1 -0
- package/dist/utils/events.d.ts +1 -13
- package/dist/utils/events.js.map +1 -1
- package/dist/utils/flow.d.ts +10 -0
- package/dist/utils/flow.js +75 -0
- package/dist/utils/flow.js.map +1 -1
- package/dist/utils/log-constants.d.ts +1 -0
- package/dist/utils/log-constants.js +2 -1
- package/dist/utils/log-constants.js.map +1 -1
- package/dist/utils/log-formatter.d.ts +2 -1
- package/dist/utils/log-formatter.js +10 -10
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.js +82 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/repro-thinking-logs.js +0 -13
- package/dist/utils/repro-thinking-logs.js.map +1 -1
- package/dist/utils/run-service.js +1 -1
- package/dist/utils/run-service.js.map +1 -1
- package/examples/README.md +0 -2
- package/examples/demo-project/README.md +1 -2
- package/package.json +13 -34
- package/scripts/setup-security.sh +0 -1
- package/scripts/test-log-parser.ts +171 -0
- package/scripts/verify-change.sh +272 -0
- package/src/cli/index.ts +0 -6
- package/src/cli/logs.ts +121 -10
- package/src/cli/models.ts +20 -3
- package/src/cli/monitor.ts +1273 -1342
- package/src/cli/resume.ts +27 -1
- package/src/cli/run.ts +29 -11
- package/src/cli/signal.ts +120 -18
- package/src/cli/tasks.ts +2 -59
- package/src/core/agent-supervisor.ts +64 -0
- package/src/core/auto-recovery.ts +14 -590
- package/src/core/failure-policy.ts +7 -229
- package/src/core/git-lifecycle-manager.ts +1011 -0
- package/src/core/git-pipeline-coordinator.ts +221 -0
- package/src/core/intervention.ts +463 -0
- package/src/core/lane-state-machine.ts +1097 -0
- package/src/core/orchestrator.ts +48 -64
- package/src/core/runner/agent.ts +77 -39
- package/src/core/runner/pipeline.ts +318 -138
- package/src/core/runner/task.ts +12 -97
- package/src/core/runner.ts +8 -2
- package/src/core/stall-detection.ts +74 -27
- package/src/hooks/contexts/index.ts +256 -0
- package/src/hooks/data-accessor.ts +488 -0
- package/src/hooks/flow-controller.ts +425 -0
- package/src/hooks/index.ts +154 -0
- package/src/hooks/manager.ts +434 -0
- package/src/hooks/types.ts +544 -0
- package/src/services/logging/buffer.ts +104 -43
- package/src/services/logging/console.ts +7 -1
- package/src/services/logging/formatter.ts +74 -18
- package/src/services/logging/index.ts +0 -2
- package/src/services/logging/paths.ts +14 -0
- package/src/services/logging/raw-log.ts +43 -0
- package/src/services/process/index.ts +1 -1
- package/src/types/agent.ts +15 -0
- package/src/types/config.ts +23 -1
- package/src/types/event-categories.ts +663 -0
- package/src/types/events.ts +0 -25
- package/src/types/flow.ts +10 -6
- package/src/types/index.ts +50 -4
- package/src/types/lane.ts +1 -2
- package/src/types/logging.ts +2 -1
- package/src/types/task.ts +12 -1
- package/src/ui/log-viewer.ts +3 -0
- package/src/utils/config.ts +11 -1
- package/src/utils/cursor-agent.ts +68 -16
- package/src/utils/enhanced-logger.ts +105 -19
- package/src/utils/event-registry.ts +595 -0
- package/src/utils/events.ts +0 -16
- package/src/utils/flow.ts +83 -0
- package/src/utils/log-constants.ts +2 -1
- package/src/utils/log-formatter.ts +10 -11
- package/src/utils/logger.ts +49 -3
- package/src/utils/repro-thinking-logs.ts +0 -15
- package/src/utils/run-service.ts +1 -1
- package/dist/cli/prepare.d.ts +0 -7
- package/dist/cli/prepare.js +0 -690
- package/dist/cli/prepare.js.map +0 -1
- package/dist/services/logging/file-writer.d.ts +0 -71
- package/dist/services/logging/file-writer.js +0 -516
- package/dist/services/logging/file-writer.js.map +0 -1
- package/dist/types/review.d.ts +0 -17
- package/dist/types/review.js +0 -6
- package/dist/types/review.js.map +0 -1
- package/scripts/ai-security-check.js +0 -233
- package/src/cli/prepare.ts +0 -777
- package/src/services/logging/file-writer.ts +0 -526
- package/src/types/review.ts +0 -20
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CursorFlow Hook System - Flow Controller Implementation
|
|
3
|
+
*
|
|
4
|
+
* Hook 핸들러에서 실행 플로우를 제어하기 위한 구현체입니다.
|
|
5
|
+
* pause, resume, injectTask 등의 기능을 제공합니다.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import { safeJoin } from '../utils/path';
|
|
10
|
+
import * as logger from '../utils/logger';
|
|
11
|
+
import { events } from '../utils/events';
|
|
12
|
+
import {
|
|
13
|
+
FlowController,
|
|
14
|
+
TaskDefinition,
|
|
15
|
+
AICallOptions,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Flow Controller Options
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface FlowControllerOptions {
|
|
23
|
+
/** Lane 이름 */
|
|
24
|
+
laneName: string;
|
|
25
|
+
/** Run 디렉토리 */
|
|
26
|
+
runDir: string;
|
|
27
|
+
/** Worktree 디렉토리 */
|
|
28
|
+
worktreeDir: string;
|
|
29
|
+
/** 현재 태스크 인덱스 */
|
|
30
|
+
currentTaskIndex: number;
|
|
31
|
+
/** 태스크 목록 (수정 가능) */
|
|
32
|
+
tasks: TaskDefinition[];
|
|
33
|
+
/** 태스크 파일 경로 */
|
|
34
|
+
tasksFile: string;
|
|
35
|
+
/** Chat ID */
|
|
36
|
+
chatId: string;
|
|
37
|
+
/** AgentSupervisor 인스턴스 (AI 호출용) */
|
|
38
|
+
agentSupervisor?: any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Flow Control State
|
|
43
|
+
// ============================================================================
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 플로우 제어 상태
|
|
47
|
+
*/
|
|
48
|
+
export interface FlowControlState {
|
|
49
|
+
/** 일시 중지 여부 */
|
|
50
|
+
isPaused: boolean;
|
|
51
|
+
/** 중단 여부 */
|
|
52
|
+
isAborted: boolean;
|
|
53
|
+
/** 재시도 요청 여부 */
|
|
54
|
+
shouldRetry: boolean;
|
|
55
|
+
/** 재시도 시 사용할 수정된 프롬프트 */
|
|
56
|
+
retryPrompt?: string;
|
|
57
|
+
/** 수정된 현재 프롬프트 */
|
|
58
|
+
modifiedCurrentPrompt?: string;
|
|
59
|
+
/** 중지/중단 사유 */
|
|
60
|
+
reason?: string;
|
|
61
|
+
/** 재개 시 전달할 데이터 */
|
|
62
|
+
resumeData?: any;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Flow Controller Implementation
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* FlowController 구현체
|
|
71
|
+
*/
|
|
72
|
+
export class FlowControllerImpl implements FlowController {
|
|
73
|
+
private options: FlowControllerOptions;
|
|
74
|
+
private state: FlowControlState;
|
|
75
|
+
private pauseResolver: ((data?: any) => void) | null = null;
|
|
76
|
+
|
|
77
|
+
constructor(options: FlowControllerOptions) {
|
|
78
|
+
this.options = options;
|
|
79
|
+
this.state = {
|
|
80
|
+
isPaused: false,
|
|
81
|
+
isAborted: false,
|
|
82
|
+
shouldRetry: false,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ==========================================================================
|
|
87
|
+
// Flow Control Methods
|
|
88
|
+
// ==========================================================================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 플로우 일시 중지
|
|
92
|
+
*/
|
|
93
|
+
async pause(reason: string): Promise<void> {
|
|
94
|
+
if (this.state.isPaused) {
|
|
95
|
+
logger.warn(`[Hook] Already paused, ignoring duplicate pause request`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.state.isPaused = true;
|
|
100
|
+
this.state.reason = reason;
|
|
101
|
+
|
|
102
|
+
logger.info(`[Hook] Flow paused: ${reason}`);
|
|
103
|
+
|
|
104
|
+
// 이벤트 발행
|
|
105
|
+
events.emit('lane.paused' as any, {
|
|
106
|
+
laneName: this.options.laneName,
|
|
107
|
+
reason,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Pause 상태 파일 생성 (외부에서 확인 가능)
|
|
111
|
+
const pauseFile = safeJoin(this.options.runDir, 'paused.json');
|
|
112
|
+
fs.writeFileSync(pauseFile, JSON.stringify({
|
|
113
|
+
paused: true,
|
|
114
|
+
reason,
|
|
115
|
+
timestamp: Date.now(),
|
|
116
|
+
}, null, 2));
|
|
117
|
+
|
|
118
|
+
// resume이 호출될 때까지 대기
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
this.pauseResolver = (data) => {
|
|
121
|
+
this.state.isPaused = false;
|
|
122
|
+
this.state.resumeData = data;
|
|
123
|
+
|
|
124
|
+
// Pause 파일 삭제
|
|
125
|
+
try {
|
|
126
|
+
fs.unlinkSync(pauseFile);
|
|
127
|
+
} catch {
|
|
128
|
+
// Ignore
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
resolve();
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 플로우 재개
|
|
138
|
+
*/
|
|
139
|
+
resume(data?: any): void {
|
|
140
|
+
if (!this.state.isPaused) {
|
|
141
|
+
logger.warn(`[Hook] Not paused, ignoring resume request`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
logger.info(`[Hook] Flow resumed`);
|
|
146
|
+
|
|
147
|
+
// 이벤트 발행
|
|
148
|
+
events.emit('lane.resumed' as any, {
|
|
149
|
+
laneName: this.options.laneName,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (this.pauseResolver) {
|
|
153
|
+
this.pauseResolver(data);
|
|
154
|
+
this.pauseResolver = null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Lane 중단
|
|
160
|
+
*/
|
|
161
|
+
abort(reason: string): void {
|
|
162
|
+
this.state.isAborted = true;
|
|
163
|
+
this.state.reason = reason;
|
|
164
|
+
|
|
165
|
+
logger.error(`[Hook] Flow aborted: ${reason}`);
|
|
166
|
+
|
|
167
|
+
// 이벤트 발행
|
|
168
|
+
events.emit('lane.failed' as any, {
|
|
169
|
+
laneName: this.options.laneName,
|
|
170
|
+
exitCode: 1,
|
|
171
|
+
error: reason,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Abort 예외 던지기 (Runner에서 catch)
|
|
175
|
+
throw new FlowAbortError(reason);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 현재 태스크 재시도
|
|
180
|
+
*/
|
|
181
|
+
retry(options?: { modifiedPrompt?: string }): void {
|
|
182
|
+
this.state.shouldRetry = true;
|
|
183
|
+
this.state.retryPrompt = options?.modifiedPrompt;
|
|
184
|
+
|
|
185
|
+
logger.info(`[Hook] Task retry requested${options?.modifiedPrompt ? ' with modified prompt' : ''}`);
|
|
186
|
+
|
|
187
|
+
throw new FlowRetryError(options?.modifiedPrompt);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ==========================================================================
|
|
191
|
+
// Task Manipulation Methods
|
|
192
|
+
// ==========================================================================
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 다음에 실행할 태스크 삽입
|
|
196
|
+
*/
|
|
197
|
+
injectTask(task: TaskDefinition): void {
|
|
198
|
+
const insertIndex = this.options.currentTaskIndex + 1;
|
|
199
|
+
|
|
200
|
+
// 태스크 배열에 삽입
|
|
201
|
+
this.options.tasks.splice(insertIndex, 0, task);
|
|
202
|
+
|
|
203
|
+
logger.info(`[Hook] Injected task "${task.name}" at index ${insertIndex}`);
|
|
204
|
+
|
|
205
|
+
// tasks.json 파일 업데이트
|
|
206
|
+
this.persistTasks();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 현재 태스크 프롬프트 수정 (beforeTask에서만 유효)
|
|
211
|
+
*/
|
|
212
|
+
modifyCurrentPrompt(newPrompt: string): void {
|
|
213
|
+
this.state.modifiedCurrentPrompt = newPrompt;
|
|
214
|
+
|
|
215
|
+
const currentTask = this.options.tasks[this.options.currentTaskIndex];
|
|
216
|
+
if (currentTask) {
|
|
217
|
+
logger.info(`[Hook] Modified prompt for task "${currentTask.name}"`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 다음 태스크 수정
|
|
223
|
+
*/
|
|
224
|
+
modifyNextTask(modifier: (task: TaskDefinition) => TaskDefinition): void {
|
|
225
|
+
const nextIndex = this.options.currentTaskIndex + 1;
|
|
226
|
+
|
|
227
|
+
if (nextIndex >= this.options.tasks.length) {
|
|
228
|
+
logger.warn(`[Hook] No next task to modify`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const nextTask = this.options.tasks[nextIndex];
|
|
233
|
+
const modifiedTask = modifier(nextTask);
|
|
234
|
+
this.options.tasks[nextIndex] = modifiedTask;
|
|
235
|
+
|
|
236
|
+
logger.info(`[Hook] Modified next task "${modifiedTask.name}"`);
|
|
237
|
+
|
|
238
|
+
// tasks.json 파일 업데이트
|
|
239
|
+
this.persistTasks();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* 남은 태스크 전체 교체
|
|
244
|
+
*/
|
|
245
|
+
replaceRemainingTasks(tasks: TaskDefinition[]): void {
|
|
246
|
+
const currentIndex = this.options.currentTaskIndex;
|
|
247
|
+
|
|
248
|
+
// 현재까지의 태스크 유지, 이후 교체
|
|
249
|
+
const completedTasks = this.options.tasks.slice(0, currentIndex + 1);
|
|
250
|
+
this.options.tasks.length = 0;
|
|
251
|
+
this.options.tasks.push(...completedTasks, ...tasks);
|
|
252
|
+
|
|
253
|
+
logger.info(`[Hook] Replaced remaining tasks with ${tasks.length} new tasks`);
|
|
254
|
+
|
|
255
|
+
// tasks.json 파일 업데이트
|
|
256
|
+
this.persistTasks();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* tasks.json 파일에 변경사항 저장
|
|
261
|
+
*/
|
|
262
|
+
private persistTasks(): void {
|
|
263
|
+
try {
|
|
264
|
+
const tasksFile = this.options.tasksFile;
|
|
265
|
+
|
|
266
|
+
if (fs.existsSync(tasksFile)) {
|
|
267
|
+
const config = JSON.parse(fs.readFileSync(tasksFile, 'utf8'));
|
|
268
|
+
config.tasks = this.options.tasks;
|
|
269
|
+
fs.writeFileSync(tasksFile, JSON.stringify(config, null, 2));
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
logger.warn(`[Hook] Failed to persist tasks: ${error}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ==========================================================================
|
|
277
|
+
// Agent Communication Methods
|
|
278
|
+
// ==========================================================================
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* AI 에이전트에게 메시지 전송 (현재 세션)
|
|
282
|
+
*/
|
|
283
|
+
async sendMessage(message: string): Promise<string> {
|
|
284
|
+
const { agentSupervisor, worktreeDir, chatId, laneName, runDir } = this.options;
|
|
285
|
+
|
|
286
|
+
if (!agentSupervisor) {
|
|
287
|
+
throw new Error('AgentSupervisor not available');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
logger.info(`[Hook] Sending message to agent`);
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const result = await agentSupervisor.sendTaskPrompt({
|
|
294
|
+
workspaceDir: worktreeDir,
|
|
295
|
+
chatId,
|
|
296
|
+
prompt: message,
|
|
297
|
+
laneName,
|
|
298
|
+
signalDir: runDir,
|
|
299
|
+
taskName: 'hook-intervention',
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
return result.resultText || '';
|
|
303
|
+
} catch (error: any) {
|
|
304
|
+
logger.error(`[Hook] Failed to send message: ${error.message}`);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 별도 AI 호출 (새 세션)
|
|
311
|
+
*/
|
|
312
|
+
async callAI(prompt: string, options?: AICallOptions): Promise<string> {
|
|
313
|
+
const { agentSupervisor, worktreeDir, laneName, runDir } = this.options;
|
|
314
|
+
|
|
315
|
+
if (!agentSupervisor) {
|
|
316
|
+
throw new Error('AgentSupervisor not available');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
logger.info(`[Hook] Calling AI with new session`);
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
// 새 채팅 세션 생성
|
|
323
|
+
const newChatId = agentSupervisor.createChat(worktreeDir);
|
|
324
|
+
|
|
325
|
+
const result = await agentSupervisor.sendTaskPrompt({
|
|
326
|
+
workspaceDir: worktreeDir,
|
|
327
|
+
chatId: newChatId,
|
|
328
|
+
prompt,
|
|
329
|
+
model: options?.model,
|
|
330
|
+
laneName,
|
|
331
|
+
signalDir: runDir,
|
|
332
|
+
timeout: options?.timeout,
|
|
333
|
+
taskName: 'hook-ai-call',
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
return result.resultText || '';
|
|
337
|
+
} catch (error: any) {
|
|
338
|
+
logger.error(`[Hook] Failed to call AI: ${error.message}`);
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ==========================================================================
|
|
344
|
+
// State Access Methods
|
|
345
|
+
// ==========================================================================
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* 현재 상태 조회
|
|
349
|
+
*/
|
|
350
|
+
getState(): FlowControlState {
|
|
351
|
+
return { ...this.state };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 수정된 프롬프트 조회 (beforeTask에서 설정된 경우)
|
|
356
|
+
*/
|
|
357
|
+
getModifiedPrompt(): string | undefined {
|
|
358
|
+
return this.state.modifiedCurrentPrompt;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 재개 데이터 조회
|
|
363
|
+
*/
|
|
364
|
+
getResumeData(): any {
|
|
365
|
+
return this.state.resumeData;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 상태 리셋
|
|
370
|
+
*/
|
|
371
|
+
resetState(): void {
|
|
372
|
+
this.state = {
|
|
373
|
+
isPaused: false,
|
|
374
|
+
isAborted: false,
|
|
375
|
+
shouldRetry: false,
|
|
376
|
+
};
|
|
377
|
+
this.pauseResolver = null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* 옵션 업데이트
|
|
382
|
+
*/
|
|
383
|
+
updateOptions(updates: Partial<FlowControllerOptions>): void {
|
|
384
|
+
this.options = { ...this.options, ...updates };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ============================================================================
|
|
389
|
+
// Custom Errors
|
|
390
|
+
// ============================================================================
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Flow 중단 에러
|
|
394
|
+
*/
|
|
395
|
+
export class FlowAbortError extends Error {
|
|
396
|
+
constructor(reason: string) {
|
|
397
|
+
super(`Flow aborted: ${reason}`);
|
|
398
|
+
this.name = 'FlowAbortError';
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Flow 재시도 에러
|
|
404
|
+
*/
|
|
405
|
+
export class FlowRetryError extends Error {
|
|
406
|
+
public modifiedPrompt?: string;
|
|
407
|
+
|
|
408
|
+
constructor(modifiedPrompt?: string) {
|
|
409
|
+
super('Flow retry requested');
|
|
410
|
+
this.name = 'FlowRetryError';
|
|
411
|
+
this.modifiedPrompt = modifiedPrompt;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ============================================================================
|
|
416
|
+
// Factory Function
|
|
417
|
+
// ============================================================================
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* FlowController 인스턴스 생성
|
|
421
|
+
*/
|
|
422
|
+
export function createFlowController(options: FlowControllerOptions): FlowControllerImpl {
|
|
423
|
+
return new FlowControllerImpl(options);
|
|
424
|
+
}
|
|
425
|
+
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CursorFlow Hook System
|
|
3
|
+
*
|
|
4
|
+
* 외부 개발자가 Supervisor AI, 모니터링 시스템, 커스텀 로직 등을
|
|
5
|
+
* 구현할 수 있도록 제공하는 Hook API입니다.
|
|
6
|
+
*
|
|
7
|
+
* ## Quick Start
|
|
8
|
+
*
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { hooks, HookPoint } from '@litmers/cursorflow-orchestrator';
|
|
11
|
+
*
|
|
12
|
+
* // 태스크 완료 후 결과 리뷰
|
|
13
|
+
* hooks.register({
|
|
14
|
+
* point: HookPoint.AFTER_TASK,
|
|
15
|
+
* mode: 'sync',
|
|
16
|
+
* name: 'my-reviewer',
|
|
17
|
+
* handler: async (ctx) => {
|
|
18
|
+
* // 수정된 파일 확인
|
|
19
|
+
* const files = await ctx.getData.git.getChangedFiles();
|
|
20
|
+
* console.log('Modified files:', files.map(f => f.path));
|
|
21
|
+
*
|
|
22
|
+
* // AI 응답 분석
|
|
23
|
+
* const response = await ctx.getData.conversation.getLastResponse();
|
|
24
|
+
*
|
|
25
|
+
* // 필요시 추가 태스크 삽입
|
|
26
|
+
* if (needsFix(response)) {
|
|
27
|
+
* ctx.flow.injectTask({
|
|
28
|
+
* name: 'Fix: ' + ctx.task.name,
|
|
29
|
+
* prompt: 'Fix the issues...',
|
|
30
|
+
* });
|
|
31
|
+
* }
|
|
32
|
+
* },
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* ## Hook Points
|
|
37
|
+
*
|
|
38
|
+
* | Hook Point | 트리거 시점 | 주요 용도 |
|
|
39
|
+
* |------------|------------|----------|
|
|
40
|
+
* | `beforeTask` | 태스크 실행 직전 | 프롬프트 검토/수정 |
|
|
41
|
+
* | `afterTask` | 태스크 완료 직후 | 결과 리뷰, 태스크 삽입 |
|
|
42
|
+
* | `onError` | 에러 발생 시 | 에러 분석, 복구 |
|
|
43
|
+
* | `onStall` | 응답 없음 감지 시 | 상황 분석, 개입 |
|
|
44
|
+
* | `onLaneEnd` | Lane 종료 시 | 최종 리뷰, 보고서 |
|
|
45
|
+
*
|
|
46
|
+
* ## Configuration
|
|
47
|
+
*
|
|
48
|
+
* `.cursorflow.json`에서 Hook 설정:
|
|
49
|
+
*
|
|
50
|
+
* ```json
|
|
51
|
+
* {
|
|
52
|
+
* "hooks": {
|
|
53
|
+
* "file": "./cursorflow.hooks.ts",
|
|
54
|
+
* "timeout": 30000,
|
|
55
|
+
* "continueOnError": false
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @module hooks
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Type Exports
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
// Hook Points & Modes
|
|
69
|
+
HookPoint,
|
|
70
|
+
HookMode,
|
|
71
|
+
|
|
72
|
+
// Context Types
|
|
73
|
+
HookContext,
|
|
74
|
+
BeforeTaskContext,
|
|
75
|
+
AfterTaskContext,
|
|
76
|
+
OnErrorContext,
|
|
77
|
+
OnStallContext,
|
|
78
|
+
OnLaneEndContext,
|
|
79
|
+
|
|
80
|
+
// Data Types
|
|
81
|
+
ChangedFile,
|
|
82
|
+
Commit,
|
|
83
|
+
Message,
|
|
84
|
+
TaskResult,
|
|
85
|
+
TaskDefinition,
|
|
86
|
+
ToolCall,
|
|
87
|
+
ErrorLog,
|
|
88
|
+
DependencyResult,
|
|
89
|
+
|
|
90
|
+
// Interface Types
|
|
91
|
+
HookDataAccessor,
|
|
92
|
+
FlowController,
|
|
93
|
+
AICallOptions,
|
|
94
|
+
|
|
95
|
+
// Registration Types
|
|
96
|
+
HookRegistration,
|
|
97
|
+
HookExecutionResult,
|
|
98
|
+
HookHandlerMap,
|
|
99
|
+
|
|
100
|
+
// Config Types
|
|
101
|
+
HooksConfig,
|
|
102
|
+
} from './types';
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Manager Exports
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
HookManager,
|
|
110
|
+
hooks,
|
|
111
|
+
getHookManager,
|
|
112
|
+
resetHookManager,
|
|
113
|
+
} from './manager';
|
|
114
|
+
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Implementation Exports (for advanced usage)
|
|
117
|
+
// ============================================================================
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
HookDataAccessorImpl,
|
|
121
|
+
DataAccessorOptions,
|
|
122
|
+
createDataAccessor,
|
|
123
|
+
} from './data-accessor';
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
FlowControllerImpl,
|
|
127
|
+
FlowControllerOptions,
|
|
128
|
+
FlowControlState,
|
|
129
|
+
FlowAbortError,
|
|
130
|
+
FlowRetryError,
|
|
131
|
+
createFlowController,
|
|
132
|
+
} from './flow-controller';
|
|
133
|
+
|
|
134
|
+
// ============================================================================
|
|
135
|
+
// Context Builder Exports (for internal/advanced usage)
|
|
136
|
+
// ============================================================================
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
BaseContextOptions,
|
|
140
|
+
createBeforeTaskContext,
|
|
141
|
+
createAfterTaskContext,
|
|
142
|
+
createOnErrorContext,
|
|
143
|
+
createOnStallContext,
|
|
144
|
+
createOnLaneEndContext,
|
|
145
|
+
} from './contexts';
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// Re-export for convenience
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
// Default export: hooks singleton
|
|
152
|
+
import { hooks as hooksSingleton } from './manager';
|
|
153
|
+
export default hooksSingleton;
|
|
154
|
+
|