@litmers/cursorflow-orchestrator 0.1.40 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -2
- package/README.md +7 -3
- package/commands/cursorflow-init.md +0 -4
- 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 +1088 -1240
- 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 +94 -12
- 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 +2 -1
- package/dist/core/auto-recovery.js +6 -1
- package/dist/core/auto-recovery.js.map +1 -1
- package/dist/core/failure-policy.d.ts +0 -1
- package/dist/core/failure-policy.js +0 -1
- 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 +176 -0
- package/dist/core/intervention.js +424 -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 +29 -62
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.d.ts +7 -1
- package/dist/core/runner/agent.js +45 -30
- 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 +62 -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 +18 -28
- 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/logs.ts +121 -10
- package/src/cli/models.ts +20 -3
- package/src/cli/monitor.ts +1257 -1342
- package/src/cli/resume.ts +27 -1
- package/src/cli/run.ts +29 -11
- package/src/cli/signal.ts +115 -17
- package/src/cli/tasks.ts +2 -59
- package/src/core/agent-supervisor.ts +64 -0
- package/src/core/auto-recovery.ts +7 -1
- package/src/core/failure-policy.ts +0 -1
- package/src/core/git-lifecycle-manager.ts +1011 -0
- package/src/core/git-pipeline-coordinator.ts +221 -0
- package/src/core/intervention.ts +481 -0
- package/src/core/lane-state-machine.ts +1097 -0
- package/src/core/orchestrator.ts +35 -61
- package/src/core/runner/agent.ts +66 -33
- 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 +72 -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/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/services/logging/file-writer.ts +0 -526
- package/src/types/review.ts +0 -20
package/src/core/runner/task.ts
CHANGED
|
@@ -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
|
|
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,78 +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 from remote
|
|
83
|
-
git.runGit(['fetch', 'origin', state.pipelineBranch], { cwd: worktreeDir, silent: true });
|
|
84
|
-
|
|
85
|
-
// Use the remote ref for merging (origin/<branch>) since dependency branches
|
|
86
|
-
// are pushed to remote by other lanes and may not exist as local branches
|
|
87
|
-
const remoteBranchRef = `origin/${state.pipelineBranch}`;
|
|
88
|
-
|
|
89
|
-
// Pre-check for conflicts before attempting merge
|
|
90
|
-
const conflictCheck = git.checkMergeConflict(remoteBranchRef, { cwd: worktreeDir });
|
|
91
|
-
|
|
92
|
-
if (conflictCheck.willConflict) {
|
|
93
|
-
logger.warn(`⚠️ Pre-check: Merge conflict detected with ${laneName}`);
|
|
94
|
-
logger.warn(` Conflicting files: ${conflictCheck.conflictingFiles.join(', ')}`);
|
|
95
|
-
|
|
96
|
-
// Emit event for potential auto-recovery or notification
|
|
97
|
-
events.emit('merge.conflict_detected', {
|
|
98
|
-
laneName,
|
|
99
|
-
targetBranch: state.pipelineBranch,
|
|
100
|
-
conflictingFiles: conflictCheck.conflictingFiles,
|
|
101
|
-
preCheck: true,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
throw new Error(`Pre-merge conflict check failed: ${conflictCheck.conflictingFiles.join(', ')}. Consider rebasing or resolving conflicts manually.`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Use safe merge with conflict detection - merge from remote ref
|
|
108
|
-
const mergeResult = git.safeMerge(remoteBranchRef, {
|
|
109
|
-
cwd: worktreeDir,
|
|
110
|
-
noFf: true,
|
|
111
|
-
message: `chore: merge task dependency from ${laneName}`,
|
|
112
|
-
abortOnConflict: true,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (!mergeResult.success) {
|
|
116
|
-
if (mergeResult.conflict) {
|
|
117
|
-
logger.error(`Merge conflict with ${laneName}: ${mergeResult.conflictingFiles.join(', ')}`);
|
|
118
|
-
throw new Error(`Merge conflict: ${mergeResult.conflictingFiles.join(', ')}`);
|
|
119
|
-
}
|
|
120
|
-
throw new Error(mergeResult.error || 'Merge failed');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
logger.success(`✓ Merged ${laneName}`);
|
|
124
|
-
} catch (e) {
|
|
125
|
-
logger.error(`Failed to merge branch from ${laneName}: ${e}`);
|
|
126
|
-
throw e;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
55
|
/**
|
|
132
56
|
* Run a single task
|
|
133
57
|
*/
|
|
@@ -141,6 +65,8 @@ export async function runTask({
|
|
|
141
65
|
chatId,
|
|
142
66
|
runDir,
|
|
143
67
|
runRoot,
|
|
68
|
+
agentSupervisor,
|
|
69
|
+
laneName,
|
|
144
70
|
}: {
|
|
145
71
|
task: Task;
|
|
146
72
|
config: RunnerConfig;
|
|
@@ -151,6 +77,8 @@ export async function runTask({
|
|
|
151
77
|
chatId: string;
|
|
152
78
|
runDir: string;
|
|
153
79
|
runRoot?: string;
|
|
80
|
+
agentSupervisor: AgentSupervisor;
|
|
81
|
+
laneName: string;
|
|
154
82
|
}): Promise<TaskExecutionResult> {
|
|
155
83
|
// Calculate runRoot if not provided (runDir is lanes/{laneName}/, runRoot is parent of lanes/)
|
|
156
84
|
const calculatedRunRoot = runRoot || path.dirname(path.dirname(runDir));
|
|
@@ -216,32 +144,20 @@ export async function runTask({
|
|
|
216
144
|
}));
|
|
217
145
|
|
|
218
146
|
logger.info('Sending prompt to agent...');
|
|
219
|
-
const
|
|
220
|
-
events.emit('agent.prompt_sent', {
|
|
221
|
-
taskName: task.name,
|
|
222
|
-
model,
|
|
223
|
-
promptLength: wrappedPrompt.length,
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
const r1 = await cursorAgentSend({
|
|
147
|
+
const r1 = await agentSupervisor.sendTaskPrompt({
|
|
227
148
|
workspaceDir: worktreeDir,
|
|
228
149
|
chatId,
|
|
229
150
|
prompt: wrappedPrompt,
|
|
230
151
|
model,
|
|
152
|
+
laneName,
|
|
231
153
|
signalDir: runDir,
|
|
232
154
|
timeout,
|
|
233
155
|
enableIntervention: config.enableIntervention,
|
|
234
156
|
outputFormat: config.agentOutputFormat,
|
|
235
157
|
taskName: task.name,
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
events.emit('agent.response_received', {
|
|
240
|
-
taskName: task.name,
|
|
241
|
-
ok: r1.ok,
|
|
242
|
-
duration,
|
|
243
|
-
responseLength: r1.resultText?.length || 0,
|
|
244
|
-
error: r1.error,
|
|
158
|
+
browser: task.browser || config.browser,
|
|
159
|
+
autoApproveCommands: config.autoApproveCommands,
|
|
160
|
+
autoApproveMcps: config.autoApproveMcps,
|
|
245
161
|
});
|
|
246
162
|
|
|
247
163
|
appendLog(convoPath, createConversationEntry('assistant', r1.resultText || r1.error || 'No response', {
|
|
@@ -308,4 +224,3 @@ export async function runTask({
|
|
|
308
224
|
status: 'FINISHED',
|
|
309
225
|
};
|
|
310
226
|
}
|
|
311
|
-
|
package/src/core/runner.ts
CHANGED
|
@@ -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,11 +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';
|
|
91
96
|
|
|
92
97
|
// Merge intervention and logging settings
|
|
93
98
|
config.enableIntervention = config.enableIntervention ?? globalConfig?.enableIntervention ?? true;
|
|
94
99
|
config.verboseGit = config.verboseGit ?? globalConfig?.verboseGit ?? false;
|
|
100
|
+
config.browser = config.browser ?? globalConfig?.browser ?? false;
|
|
95
101
|
|
|
96
102
|
// Handle process interruption to ensure cleanup
|
|
97
103
|
const handleSignal = (signal: string) => {
|
|
@@ -105,7 +111,7 @@ if (require.main === module) {
|
|
|
105
111
|
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
106
112
|
|
|
107
113
|
// Run tasks
|
|
108
|
-
runTasks(tasksFile, config, runDir, { startIndex })
|
|
114
|
+
runTasks(tasksFile, config, runDir, { startIndex, skipPreflight })
|
|
109
115
|
.then(() => {
|
|
110
116
|
process.exit(0);
|
|
111
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)
|
|
@@ -732,8 +740,10 @@ export class StallDetectionService {
|
|
|
732
740
|
* Stall 체크 및 복구 액션 실행
|
|
733
741
|
*
|
|
734
742
|
* @returns 실행된 분석 결과 (orchestrator에서 추가 처리 필요시 사용)
|
|
743
|
+
*
|
|
744
|
+
* 새로운 방식에서는 복구 액션이 프로세스 중단을 포함하므로 async
|
|
735
745
|
*/
|
|
736
|
-
checkAndRecover(laneName: string): StallAnalysis {
|
|
746
|
+
async checkAndRecover(laneName: string): Promise<StallAnalysis> {
|
|
737
747
|
const state = this.laneStates.get(laneName);
|
|
738
748
|
if (!state) {
|
|
739
749
|
return {
|
|
@@ -760,18 +770,18 @@ export class StallDetectionService {
|
|
|
760
770
|
// 실패 이력 기록
|
|
761
771
|
this.recordFailure(state, analysis);
|
|
762
772
|
|
|
763
|
-
// 액션 실행
|
|
773
|
+
// 액션 실행 (프로세스 중단 포함 - await 필요)
|
|
764
774
|
switch (analysis.action) {
|
|
765
775
|
case RecoveryAction.SEND_CONTINUE:
|
|
766
|
-
this.sendContinueSignal(state);
|
|
776
|
+
await this.sendContinueSignal(state);
|
|
767
777
|
break;
|
|
768
778
|
|
|
769
779
|
case RecoveryAction.SEND_STRONGER_PROMPT:
|
|
770
|
-
this.sendStrongerPrompt(state);
|
|
780
|
+
await this.sendStrongerPrompt(state);
|
|
771
781
|
break;
|
|
772
782
|
|
|
773
783
|
case RecoveryAction.REQUEST_RESTART:
|
|
774
|
-
this.requestRestart(state);
|
|
784
|
+
await this.requestRestart(state);
|
|
775
785
|
break;
|
|
776
786
|
|
|
777
787
|
case RecoveryAction.RUN_DOCTOR:
|
|
@@ -787,9 +797,14 @@ export class StallDetectionService {
|
|
|
787
797
|
}
|
|
788
798
|
|
|
789
799
|
/**
|
|
790
|
-
* Continue 신호 발송
|
|
800
|
+
* Continue 신호 발송 - 프로세스 중단 및 개입 메시지와 함께 resume
|
|
801
|
+
*
|
|
802
|
+
* 새로운 방식:
|
|
803
|
+
* 1. pending-intervention.json 생성
|
|
804
|
+
* 2. 현재 프로세스 SIGTERM으로 종료
|
|
805
|
+
* 3. Orchestrator가 감지하여 개입 메시지와 함께 resume
|
|
791
806
|
*/
|
|
792
|
-
private sendContinueSignal(state: LaneStallState): void {
|
|
807
|
+
private async sendContinueSignal(state: LaneStallState): Promise<void> {
|
|
793
808
|
// Intervention이 비활성화된 경우 신호를 보내지 않고 phase만 업데이트
|
|
794
809
|
if (state.interventionEnabled === false) {
|
|
795
810
|
logger.warn(`[${state.laneName}] Continue signal skipped (intervention disabled). Stall will escalate on next check.`);
|
|
@@ -803,16 +818,26 @@ export class StallDetectionService {
|
|
|
803
818
|
return;
|
|
804
819
|
}
|
|
805
820
|
|
|
806
|
-
const interventionPath = safeJoin(state.laneRunDir, 'intervention.txt');
|
|
807
|
-
|
|
808
821
|
try {
|
|
809
|
-
|
|
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
|
+
}
|
|
810
835
|
|
|
811
836
|
state.phase = StallPhase.CONTINUE_SENT;
|
|
812
837
|
state.lastPhaseChangeTime = Date.now();
|
|
813
838
|
state.continueSignalCount++;
|
|
814
839
|
|
|
815
|
-
logger.info(`[${state.laneName}]
|
|
840
|
+
logger.info(`[${state.laneName}] Continue signal queued (#${state.continueSignalCount}) - agent will resume with intervention`);
|
|
816
841
|
|
|
817
842
|
events.emit('recovery.continue_signal', {
|
|
818
843
|
laneName: state.laneName,
|
|
@@ -825,9 +850,9 @@ export class StallDetectionService {
|
|
|
825
850
|
}
|
|
826
851
|
|
|
827
852
|
/**
|
|
828
|
-
* Stronger prompt 발송
|
|
853
|
+
* Stronger prompt 발송 - 프로세스 중단 및 강력한 개입 메시지와 함께 resume
|
|
829
854
|
*/
|
|
830
|
-
private sendStrongerPrompt(state: LaneStallState): void {
|
|
855
|
+
private async sendStrongerPrompt(state: LaneStallState): Promise<void> {
|
|
831
856
|
// Intervention이 비활성화된 경우 신호를 보내지 않고 phase만 업데이트
|
|
832
857
|
if (state.interventionEnabled === false) {
|
|
833
858
|
logger.warn(`[${state.laneName}] Stronger prompt skipped (intervention disabled). Will escalate to restart.`);
|
|
@@ -841,20 +866,25 @@ export class StallDetectionService {
|
|
|
841
866
|
return;
|
|
842
867
|
}
|
|
843
868
|
|
|
844
|
-
const interventionPath = safeJoin(state.laneRunDir, 'intervention.txt');
|
|
845
|
-
const prompt = `[SYSTEM INTERVENTION] You seem to be stuck or waiting.
|
|
846
|
-
Please continue with your current task immediately.
|
|
847
|
-
If you're waiting for something, explain what you need and proceed with what you can do now.
|
|
848
|
-
If you've completed the task, please summarize your work and finish.
|
|
849
|
-
If you encountered a git error, resolve it and continue.`;
|
|
850
|
-
|
|
851
869
|
try {
|
|
852
|
-
|
|
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
|
+
}
|
|
853
883
|
|
|
854
884
|
state.phase = StallPhase.STRONGER_PROMPT_SENT;
|
|
855
885
|
state.lastPhaseChangeTime = Date.now();
|
|
856
886
|
|
|
857
|
-
logger.warn(`[${state.laneName}]
|
|
887
|
+
logger.warn(`[${state.laneName}] Stronger prompt queued - agent will resume with intervention`);
|
|
858
888
|
|
|
859
889
|
events.emit('recovery.stronger_prompt', {
|
|
860
890
|
laneName: state.laneName,
|
|
@@ -865,16 +895,31 @@ If you encountered a git error, resolve it and continue.`;
|
|
|
865
895
|
}
|
|
866
896
|
|
|
867
897
|
/**
|
|
868
|
-
* 재시작 요청
|
|
898
|
+
* 재시작 요청 - 프로세스 종료 및 재시작 메시지와 함께 resume
|
|
869
899
|
*/
|
|
870
|
-
private requestRestart(state: LaneStallState): void {
|
|
900
|
+
private async requestRestart(state: LaneStallState): Promise<void> {
|
|
871
901
|
state.restartCount++;
|
|
872
902
|
state.phase = StallPhase.RESTART_REQUESTED;
|
|
873
903
|
state.lastPhaseChangeTime = Date.now();
|
|
874
904
|
|
|
875
|
-
//
|
|
876
|
-
if (state.
|
|
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) {
|
|
877
921
|
try {
|
|
922
|
+
// SIGKILL로 즉시 종료 (SIGTERM이 안 먹힐 수 있으므로)
|
|
878
923
|
state.childProcess.kill('SIGKILL');
|
|
879
924
|
logger.info(`[StallService] [${state.laneName}] Killed process ${state.childProcess.pid}`);
|
|
880
925
|
} catch (error: any) {
|
|
@@ -882,7 +927,7 @@ If you encountered a git error, resolve it and continue.`;
|
|
|
882
927
|
}
|
|
883
928
|
}
|
|
884
929
|
|
|
885
|
-
logger.warn(`[${state.laneName}]
|
|
930
|
+
logger.warn(`[${state.laneName}] Restart requested (restart #${state.restartCount}/${this.config.maxRestarts})`);
|
|
886
931
|
|
|
887
932
|
events.emit('recovery.restart', {
|
|
888
933
|
laneName: state.laneName,
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CursorFlow Hook System - Context Builders
|
|
3
|
+
*
|
|
4
|
+
* 각 Hook Point에 대한 컨텍스트를 생성하는 빌더 함수들입니다.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
HookContext,
|
|
9
|
+
BeforeTaskContext,
|
|
10
|
+
AfterTaskContext,
|
|
11
|
+
OnErrorContext,
|
|
12
|
+
OnStallContext,
|
|
13
|
+
OnLaneEndContext,
|
|
14
|
+
TaskDefinition,
|
|
15
|
+
TaskResult,
|
|
16
|
+
DependencyResult,
|
|
17
|
+
FlowController,
|
|
18
|
+
HookDataAccessor,
|
|
19
|
+
} from '../types';
|
|
20
|
+
import { createDataAccessor, DataAccessorOptions } from '../data-accessor';
|
|
21
|
+
import { createFlowController, FlowControllerOptions, FlowControllerImpl } from '../flow-controller';
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Base Context Builder Options
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
export interface BaseContextOptions {
|
|
28
|
+
/** Lane 이름 */
|
|
29
|
+
laneName: string;
|
|
30
|
+
/** Run ID */
|
|
31
|
+
runId: string;
|
|
32
|
+
/** 현재 태스크 인덱스 */
|
|
33
|
+
taskIndex: number;
|
|
34
|
+
/** 전체 태스크 수 */
|
|
35
|
+
totalTasks: number;
|
|
36
|
+
/** 현재 태스크 */
|
|
37
|
+
task: {
|
|
38
|
+
name: string;
|
|
39
|
+
prompt: string;
|
|
40
|
+
model: string;
|
|
41
|
+
dependsOn?: string[];
|
|
42
|
+
};
|
|
43
|
+
/** Worktree 디렉토리 */
|
|
44
|
+
worktreeDir: string;
|
|
45
|
+
/** Run 디렉토리 */
|
|
46
|
+
runDir: string;
|
|
47
|
+
/** 태스크 브랜치 */
|
|
48
|
+
taskBranch: string;
|
|
49
|
+
/** 파이프라인 브랜치 */
|
|
50
|
+
pipelineBranch: string;
|
|
51
|
+
/** 태스크 파일 경로 */
|
|
52
|
+
tasksFile: string;
|
|
53
|
+
/** Chat ID */
|
|
54
|
+
chatId: string;
|
|
55
|
+
/** 태스크 목록 (수정 가능 참조) */
|
|
56
|
+
tasks: TaskDefinition[];
|
|
57
|
+
/** 완료된 태스크 목록 */
|
|
58
|
+
completedTasks: TaskResult[];
|
|
59
|
+
/** 의존성 결과 */
|
|
60
|
+
dependencyResults: DependencyResult[];
|
|
61
|
+
/** 태스크 시작 시간 */
|
|
62
|
+
taskStartTime: number;
|
|
63
|
+
/** Lane 시작 시간 */
|
|
64
|
+
laneStartTime: number;
|
|
65
|
+
/** AgentSupervisor 인스턴스 */
|
|
66
|
+
agentSupervisor?: any;
|
|
67
|
+
/** Run Root */
|
|
68
|
+
runRoot?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Context Builders
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 기본 컨텍스트 생성
|
|
77
|
+
*/
|
|
78
|
+
function createBaseContext(options: BaseContextOptions): {
|
|
79
|
+
context: Omit<HookContext, 'flow' | 'getData'>;
|
|
80
|
+
flowController: FlowControllerImpl;
|
|
81
|
+
dataAccessor: HookDataAccessor;
|
|
82
|
+
} {
|
|
83
|
+
// FlowController 생성
|
|
84
|
+
const flowControllerOptions: FlowControllerOptions = {
|
|
85
|
+
laneName: options.laneName,
|
|
86
|
+
runDir: options.runDir,
|
|
87
|
+
worktreeDir: options.worktreeDir,
|
|
88
|
+
currentTaskIndex: options.taskIndex,
|
|
89
|
+
tasks: options.tasks,
|
|
90
|
+
tasksFile: options.tasksFile,
|
|
91
|
+
chatId: options.chatId,
|
|
92
|
+
agentSupervisor: options.agentSupervisor,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const flowController = createFlowController(flowControllerOptions);
|
|
96
|
+
|
|
97
|
+
// DataAccessor 생성
|
|
98
|
+
const dataAccessorOptions: DataAccessorOptions = {
|
|
99
|
+
worktreeDir: options.worktreeDir,
|
|
100
|
+
runDir: options.runDir,
|
|
101
|
+
taskBranch: options.taskBranch,
|
|
102
|
+
pipelineBranch: options.pipelineBranch,
|
|
103
|
+
laneName: options.laneName,
|
|
104
|
+
taskName: options.task.name,
|
|
105
|
+
completedTasks: options.completedTasks,
|
|
106
|
+
pendingTasks: options.tasks.slice(options.taskIndex + 1),
|
|
107
|
+
dependencyResults: options.dependencyResults,
|
|
108
|
+
taskStartTime: options.taskStartTime,
|
|
109
|
+
laneStartTime: options.laneStartTime,
|
|
110
|
+
runRoot: options.runRoot,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const dataAccessor = createDataAccessor(dataAccessorOptions);
|
|
114
|
+
|
|
115
|
+
// 기본 컨텍스트
|
|
116
|
+
const context = {
|
|
117
|
+
laneName: options.laneName,
|
|
118
|
+
runId: options.runId,
|
|
119
|
+
taskIndex: options.taskIndex,
|
|
120
|
+
totalTasks: options.totalTasks,
|
|
121
|
+
task: options.task,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return { context, flowController, dataAccessor };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* beforeTask 컨텍스트 생성
|
|
129
|
+
*/
|
|
130
|
+
export function createBeforeTaskContext(options: BaseContextOptions): {
|
|
131
|
+
context: BeforeTaskContext;
|
|
132
|
+
flowController: FlowControllerImpl;
|
|
133
|
+
} {
|
|
134
|
+
const { context, flowController, dataAccessor } = createBaseContext(options);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
context: {
|
|
138
|
+
...context,
|
|
139
|
+
flow: flowController,
|
|
140
|
+
getData: dataAccessor,
|
|
141
|
+
},
|
|
142
|
+
flowController,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* afterTask 컨텍스트 생성
|
|
148
|
+
*/
|
|
149
|
+
export function createAfterTaskContext(
|
|
150
|
+
options: BaseContextOptions,
|
|
151
|
+
result: {
|
|
152
|
+
status: 'success' | 'error' | 'blocked';
|
|
153
|
+
exitCode?: number;
|
|
154
|
+
error?: string;
|
|
155
|
+
}
|
|
156
|
+
): {
|
|
157
|
+
context: AfterTaskContext;
|
|
158
|
+
flowController: FlowControllerImpl;
|
|
159
|
+
} {
|
|
160
|
+
const { context, flowController, dataAccessor } = createBaseContext(options);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
context: {
|
|
164
|
+
...context,
|
|
165
|
+
flow: flowController,
|
|
166
|
+
getData: dataAccessor,
|
|
167
|
+
result,
|
|
168
|
+
},
|
|
169
|
+
flowController,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* onError 컨텍스트 생성
|
|
175
|
+
*/
|
|
176
|
+
export function createOnErrorContext(
|
|
177
|
+
options: BaseContextOptions,
|
|
178
|
+
error: {
|
|
179
|
+
type: 'agent_error' | 'git_error' | 'timeout' | 'unknown';
|
|
180
|
+
message: string;
|
|
181
|
+
stack?: string;
|
|
182
|
+
retryable: boolean;
|
|
183
|
+
}
|
|
184
|
+
): {
|
|
185
|
+
context: OnErrorContext;
|
|
186
|
+
flowController: FlowControllerImpl;
|
|
187
|
+
} {
|
|
188
|
+
const { context, flowController, dataAccessor } = createBaseContext(options);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
context: {
|
|
192
|
+
...context,
|
|
193
|
+
flow: flowController,
|
|
194
|
+
getData: dataAccessor,
|
|
195
|
+
error,
|
|
196
|
+
},
|
|
197
|
+
flowController,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* onStall 컨텍스트 생성
|
|
203
|
+
*/
|
|
204
|
+
export function createOnStallContext(
|
|
205
|
+
options: BaseContextOptions,
|
|
206
|
+
stall: {
|
|
207
|
+
idleTimeMs: number;
|
|
208
|
+
lastActivity: string;
|
|
209
|
+
bytesReceived: number;
|
|
210
|
+
phase: 'initial' | 'warning' | 'critical';
|
|
211
|
+
}
|
|
212
|
+
): {
|
|
213
|
+
context: OnStallContext;
|
|
214
|
+
flowController: FlowControllerImpl;
|
|
215
|
+
} {
|
|
216
|
+
const { context, flowController, dataAccessor } = createBaseContext(options);
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
context: {
|
|
220
|
+
...context,
|
|
221
|
+
flow: flowController,
|
|
222
|
+
getData: dataAccessor,
|
|
223
|
+
stall,
|
|
224
|
+
},
|
|
225
|
+
flowController,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* onLaneEnd 컨텍스트 생성
|
|
231
|
+
*/
|
|
232
|
+
export function createOnLaneEndContext(
|
|
233
|
+
options: BaseContextOptions,
|
|
234
|
+
summary: {
|
|
235
|
+
status: 'completed' | 'failed' | 'aborted';
|
|
236
|
+
completedTasks: number;
|
|
237
|
+
failedTasks: number;
|
|
238
|
+
totalDuration: number;
|
|
239
|
+
}
|
|
240
|
+
): {
|
|
241
|
+
context: OnLaneEndContext;
|
|
242
|
+
flowController: FlowControllerImpl;
|
|
243
|
+
} {
|
|
244
|
+
const { context, flowController, dataAccessor } = createBaseContext(options);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
context: {
|
|
248
|
+
...context,
|
|
249
|
+
flow: flowController,
|
|
250
|
+
getData: dataAccessor,
|
|
251
|
+
summary,
|
|
252
|
+
},
|
|
253
|
+
flowController,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|