@litmers/cursorflow-orchestrator 0.1.20 → 0.1.28
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 +20 -0
- package/commands/cursorflow-clean.md +19 -0
- package/commands/cursorflow-runs.md +59 -0
- package/commands/cursorflow-stop.md +55 -0
- package/dist/cli/clean.js +171 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +83 -42
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1007 -189
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +87 -3
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +188 -236
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +125 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.d.ts +5 -0
- package/dist/cli/runs.js +214 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/setup-commands.js +0 -0
- package/dist/cli/signal.js +1 -1
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/stop.d.ts +5 -0
- package/dist/cli/stop.js +215 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/tasks.d.ts +10 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +212 -0
- package/dist/core/auto-recovery.js +737 -0
- package/dist/core/auto-recovery.js.map +1 -0
- package/dist/core/failure-policy.d.ts +156 -0
- package/dist/core/failure-policy.js +488 -0
- package/dist/core/failure-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +15 -2
- package/dist/core/orchestrator.js +397 -15
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +2 -0
- package/dist/core/reviewer.js +2 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +33 -10
- package/dist/core/runner.js +321 -146
- package/dist/core/runner.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +67 -0
- package/dist/services/logging/buffer.js +309 -0
- package/dist/services/logging/buffer.js.map +1 -0
- package/dist/services/logging/console.d.ts +89 -0
- package/dist/services/logging/console.js +169 -0
- package/dist/services/logging/console.js.map +1 -0
- package/dist/services/logging/file-writer.d.ts +71 -0
- package/dist/services/logging/file-writer.js +516 -0
- package/dist/services/logging/file-writer.js.map +1 -0
- package/dist/services/logging/formatter.d.ts +39 -0
- package/dist/services/logging/formatter.js +227 -0
- package/dist/services/logging/formatter.js.map +1 -0
- package/dist/services/logging/index.d.ts +11 -0
- package/dist/services/logging/index.js +30 -0
- package/dist/services/logging/index.js.map +1 -0
- package/dist/services/logging/parser.d.ts +31 -0
- package/dist/services/logging/parser.js +222 -0
- package/dist/services/logging/parser.js.map +1 -0
- package/dist/services/process/index.d.ts +59 -0
- package/dist/services/process/index.js +257 -0
- package/dist/services/process/index.js.map +1 -0
- package/dist/types/agent.d.ts +20 -0
- package/dist/types/agent.js +6 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/config.d.ts +65 -0
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +125 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lane.d.ts +43 -0
- package/dist/types/lane.js +6 -0
- package/dist/types/lane.js.map +1 -0
- package/dist/types/logging.d.ts +71 -0
- package/dist/types/logging.js +16 -0
- package/dist/types/logging.js.map +1 -0
- package/dist/types/review.d.ts +17 -0
- package/dist/types/review.js +6 -0
- package/dist/types/review.js.map +1 -0
- package/dist/types/run.d.ts +32 -0
- package/dist/types/run.js +6 -0
- package/dist/types/run.js.map +1 -0
- package/dist/types/task.d.ts +71 -0
- package/dist/types/task.js +6 -0
- package/dist/types/task.js.map +1 -0
- package/dist/ui/components.d.ts +134 -0
- package/dist/ui/components.js +389 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/log-viewer.d.ts +49 -0
- package/dist/ui/log-viewer.js +449 -0
- package/dist/ui/log-viewer.js.map +1 -0
- package/dist/utils/checkpoint.d.ts +87 -0
- package/dist/utils/checkpoint.js +317 -0
- package/dist/utils/checkpoint.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.js +11 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/dependency.d.ts +74 -0
- package/dist/utils/dependency.js +420 -0
- package/dist/utils/dependency.js.map +1 -0
- package/dist/utils/doctor.js +10 -5
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +94 -9
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +322 -2
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.d.ts +91 -0
- package/dist/utils/health.js +556 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/lock.d.ts +95 -0
- package/dist/utils/lock.js +332 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/log-buffer.d.ts +17 -0
- package/dist/utils/log-buffer.js +14 -0
- package/dist/utils/log-buffer.js.map +1 -0
- package/dist/utils/log-constants.d.ts +23 -0
- package/dist/utils/log-constants.js +28 -0
- package/dist/utils/log-constants.js.map +1 -0
- package/dist/utils/log-formatter.d.ts +9 -0
- package/dist/utils/log-formatter.js +113 -70
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/log-service.d.ts +19 -0
- package/dist/utils/log-service.js +47 -0
- package/dist/utils/log-service.js.map +1 -0
- package/dist/utils/logger.d.ts +46 -27
- package/dist/utils/logger.js +82 -60
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/process-manager.d.ts +21 -0
- package/dist/utils/process-manager.js +138 -0
- package/dist/utils/process-manager.js.map +1 -0
- package/dist/utils/retry.d.ts +121 -0
- package/dist/utils/retry.js +374 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/run-service.d.ts +88 -0
- package/dist/utils/run-service.js +412 -0
- package/dist/utils/run-service.js.map +1 -0
- package/dist/utils/state.d.ts +58 -2
- package/dist/utils/state.js +306 -3
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +82 -0
- package/dist/utils/task-service.js +348 -0
- package/dist/utils/task-service.js.map +1 -0
- package/dist/utils/types.d.ts +2 -272
- package/dist/utils/types.js +16 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +38 -23
- package/scripts/ai-security-check.js +0 -1
- package/scripts/local-security-gate.sh +0 -0
- package/scripts/monitor-lanes.sh +94 -0
- package/scripts/patches/test-cursor-agent.js +0 -1
- package/scripts/release.sh +0 -0
- package/scripts/setup-security.sh +0 -0
- package/scripts/stream-logs.sh +72 -0
- package/scripts/verify-and-fix.sh +0 -0
- package/src/cli/clean.ts +180 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/init.ts +1 -1
- package/src/cli/logs.ts +79 -42
- package/src/cli/monitor.ts +1815 -899
- package/src/cli/prepare.ts +97 -3
- package/src/cli/resume.ts +220 -277
- package/src/cli/run.ts +154 -3
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +1 -1
- package/src/cli/stop.ts +209 -0
- package/src/cli/tasks.ts +154 -0
- package/src/core/auto-recovery.ts +909 -0
- package/src/core/failure-policy.ts +592 -0
- package/src/core/orchestrator.ts +1136 -675
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +1443 -1217
- package/src/services/logging/buffer.ts +326 -0
- package/src/services/logging/console.ts +193 -0
- package/src/services/logging/file-writer.ts +526 -0
- package/src/services/logging/formatter.ts +268 -0
- package/src/services/logging/index.ts +16 -0
- package/src/services/logging/parser.ts +232 -0
- package/src/services/process/index.ts +261 -0
- package/src/types/agent.ts +24 -0
- package/src/types/config.ts +79 -0
- package/src/types/events.ts +156 -0
- package/src/types/index.ts +29 -0
- package/src/types/lane.ts +56 -0
- package/src/types/logging.ts +96 -0
- package/src/types/review.ts +20 -0
- package/src/types/run.ts +37 -0
- package/src/types/task.ts +79 -0
- package/src/ui/components.ts +430 -0
- package/src/ui/log-viewer.ts +485 -0
- package/src/utils/checkpoint.ts +374 -0
- package/src/utils/config.ts +11 -2
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +11 -5
- package/src/utils/enhanced-logger.ts +108 -49
- package/src/utils/git.ts +871 -499
- package/src/utils/health.ts +596 -0
- package/src/utils/lock.ts +346 -0
- package/src/utils/log-buffer.ts +28 -0
- package/src/utils/log-constants.ts +26 -0
- package/src/utils/log-formatter.ts +120 -37
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/process-manager.ts +100 -0
- package/src/utils/retry.ts +413 -0
- package/src/utils/run-service.ts +433 -0
- package/src/utils/state.ts +369 -3
- package/src/utils/task-service.ts +370 -0
- package/src/utils/types.ts +2 -315
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunService - Manages CursorFlow run history and active processes
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - List all runs (with filtering by status)
|
|
6
|
+
* - Get detailed run information
|
|
7
|
+
* - Stop running processes
|
|
8
|
+
* - Delete run resources
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import { RunStatus, RunInfo, LaneInfo, LaneState } from './types';
|
|
14
|
+
import * as logger from './logger';
|
|
15
|
+
|
|
16
|
+
export interface RunFilter {
|
|
17
|
+
status?: RunStatus;
|
|
18
|
+
limit?: number;
|
|
19
|
+
taskName?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RunResources {
|
|
23
|
+
branches: string[];
|
|
24
|
+
worktrees: string[];
|
|
25
|
+
logSize: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class RunService {
|
|
29
|
+
private logsDir: string;
|
|
30
|
+
private runsDir: string;
|
|
31
|
+
|
|
32
|
+
constructor(logsDir: string) {
|
|
33
|
+
this.logsDir = logsDir;
|
|
34
|
+
this.runsDir = path.join(logsDir, 'runs');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* List all runs with optional filtering
|
|
39
|
+
*/
|
|
40
|
+
listRuns(filter: RunFilter = {}): RunInfo[] {
|
|
41
|
+
const { status, limit, taskName } = filter;
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(this.runsDir)) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const runDirs = fs.readdirSync(this.runsDir)
|
|
48
|
+
.filter(name => name.startsWith('run-'))
|
|
49
|
+
.sort((a, b) => b.localeCompare(a)); // Most recent first
|
|
50
|
+
|
|
51
|
+
let runs: RunInfo[] = [];
|
|
52
|
+
|
|
53
|
+
for (const runId of runDirs) {
|
|
54
|
+
const runInfo = this.getRunInfo(runId);
|
|
55
|
+
|
|
56
|
+
if (!runInfo) continue;
|
|
57
|
+
|
|
58
|
+
// Apply filters
|
|
59
|
+
if (status && runInfo.status !== status) continue;
|
|
60
|
+
if (taskName && !runInfo.taskName.toLowerCase().includes(taskName.toLowerCase())) continue;
|
|
61
|
+
|
|
62
|
+
runs.push(runInfo);
|
|
63
|
+
|
|
64
|
+
if (limit && runs.length >= limit) break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return runs;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get detailed information about a specific run
|
|
72
|
+
*/
|
|
73
|
+
getRunInfo(runId: string): RunInfo | null {
|
|
74
|
+
const runPath = path.join(this.runsDir, runId);
|
|
75
|
+
|
|
76
|
+
if (!fs.existsSync(runPath)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Read orchestrator state
|
|
82
|
+
const statePath = path.join(runPath, 'state.json');
|
|
83
|
+
let taskName = 'Unknown';
|
|
84
|
+
let lanes: LaneInfo[] = [];
|
|
85
|
+
let branches: string[] = [];
|
|
86
|
+
let worktrees: string[] = [];
|
|
87
|
+
|
|
88
|
+
if (fs.existsSync(statePath)) {
|
|
89
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
90
|
+
taskName = state.taskName || this.extractTaskNameFromRunId(runId);
|
|
91
|
+
|
|
92
|
+
// Extract lane info from state
|
|
93
|
+
if (state.lanes) {
|
|
94
|
+
for (const [laneName, laneState] of Object.entries(state.lanes)) {
|
|
95
|
+
const ls = laneState as LaneState;
|
|
96
|
+
lanes.push({
|
|
97
|
+
name: laneName,
|
|
98
|
+
status: ls.status,
|
|
99
|
+
currentTask: ls.currentTaskIndex,
|
|
100
|
+
totalTasks: ls.totalTasks,
|
|
101
|
+
pid: ls.pid,
|
|
102
|
+
pipelineBranch: ls.pipelineBranch || undefined,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (ls.pipelineBranch) {
|
|
106
|
+
branches.push(ls.pipelineBranch);
|
|
107
|
+
}
|
|
108
|
+
if (ls.worktreeDir) {
|
|
109
|
+
worktrees.push(ls.worktreeDir);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Alternatively, scan lanes directory
|
|
116
|
+
const lanesDir = path.join(runPath, 'lanes');
|
|
117
|
+
if (fs.existsSync(lanesDir) && lanes.length === 0) {
|
|
118
|
+
const laneDirs = fs.readdirSync(lanesDir);
|
|
119
|
+
for (const laneName of laneDirs) {
|
|
120
|
+
const laneStatePath = path.join(lanesDir, laneName, 'state.json');
|
|
121
|
+
if (fs.existsSync(laneStatePath)) {
|
|
122
|
+
try {
|
|
123
|
+
const laneState = JSON.parse(fs.readFileSync(laneStatePath, 'utf-8')) as LaneState;
|
|
124
|
+
lanes.push({
|
|
125
|
+
name: laneName,
|
|
126
|
+
status: laneState.status,
|
|
127
|
+
currentTask: laneState.currentTaskIndex,
|
|
128
|
+
totalTasks: laneState.totalTasks,
|
|
129
|
+
pid: laneState.pid,
|
|
130
|
+
pipelineBranch: laneState.pipelineBranch || undefined,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (laneState.pipelineBranch) {
|
|
134
|
+
branches.push(laneState.pipelineBranch);
|
|
135
|
+
}
|
|
136
|
+
if (laneState.worktreeDir) {
|
|
137
|
+
worktrees.push(laneState.worktreeDir);
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// Skip invalid state files
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Calculate run status and timing
|
|
147
|
+
const status = this.calculateRunStatus(lanes);
|
|
148
|
+
const startTime = this.extractTimestampFromRunId(runId);
|
|
149
|
+
const endTime = this.getRunEndTime(runPath, lanes);
|
|
150
|
+
const duration = endTime ? endTime - startTime : Date.now() - startTime;
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
id: runId,
|
|
154
|
+
path: runPath,
|
|
155
|
+
taskName,
|
|
156
|
+
status,
|
|
157
|
+
startTime,
|
|
158
|
+
endTime,
|
|
159
|
+
duration,
|
|
160
|
+
lanes,
|
|
161
|
+
branches: [...new Set(branches)],
|
|
162
|
+
worktrees: [...new Set(worktrees)],
|
|
163
|
+
};
|
|
164
|
+
} catch (error) {
|
|
165
|
+
logger.debug(`Failed to read run info for ${runId}: ${error}`);
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get currently active (running) runs
|
|
172
|
+
*/
|
|
173
|
+
getActiveRuns(): RunInfo[] {
|
|
174
|
+
return this.listRuns({ status: 'running' });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Calculate overall run status based on lane statuses
|
|
179
|
+
*/
|
|
180
|
+
private calculateRunStatus(lanes: LaneInfo[]): RunStatus {
|
|
181
|
+
if (lanes.length === 0) return 'pending';
|
|
182
|
+
|
|
183
|
+
const statuses = lanes.map(l => l.status);
|
|
184
|
+
|
|
185
|
+
// If any lane is running, the run is running
|
|
186
|
+
if (statuses.some(s => s === 'running' || s === 'reviewing')) {
|
|
187
|
+
return 'running';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// If all lanes are completed, the run is completed
|
|
191
|
+
if (statuses.every(s => s === 'completed')) {
|
|
192
|
+
return 'completed';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// If any lane failed and none are running, check for partial completion
|
|
196
|
+
if (statuses.some(s => s === 'failed')) {
|
|
197
|
+
const hasCompleted = statuses.some(s => s === 'completed');
|
|
198
|
+
return hasCompleted ? 'partial' : 'failed';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// If some are pending and some completed
|
|
202
|
+
if (statuses.some(s => s === 'pending' || s === 'waiting')) {
|
|
203
|
+
return statuses.some(s => s === 'completed') ? 'partial' : 'pending';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return 'pending';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Extract timestamp from run ID (format: run-TIMESTAMP)
|
|
211
|
+
*/
|
|
212
|
+
private extractTimestampFromRunId(runId: string): number {
|
|
213
|
+
const match = runId.match(/run-(\d+)/);
|
|
214
|
+
return match ? parseInt(match[1], 10) : Date.now();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Extract task name from run ID or directory structure
|
|
219
|
+
*/
|
|
220
|
+
private extractTaskNameFromRunId(runId: string): string {
|
|
221
|
+
// Try to read from any lane's tasks file reference
|
|
222
|
+
const runPath = path.join(this.runsDir, runId);
|
|
223
|
+
const lanesDir = path.join(runPath, 'lanes');
|
|
224
|
+
|
|
225
|
+
if (fs.existsSync(lanesDir)) {
|
|
226
|
+
const laneDirs = fs.readdirSync(lanesDir);
|
|
227
|
+
for (const laneName of laneDirs) {
|
|
228
|
+
const statePath = path.join(lanesDir, laneName, 'state.json');
|
|
229
|
+
if (fs.existsSync(statePath)) {
|
|
230
|
+
try {
|
|
231
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
232
|
+
if (state.tasksFile) {
|
|
233
|
+
const taskDir = path.basename(path.dirname(state.tasksFile));
|
|
234
|
+
// Extract feature name from timestamp_FeatureName format
|
|
235
|
+
const parts = taskDir.split('_');
|
|
236
|
+
if (parts.length >= 2) {
|
|
237
|
+
return parts.slice(1).join('_');
|
|
238
|
+
}
|
|
239
|
+
return taskDir;
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
// Continue to next lane
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return 'Unknown';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get run end time from lane states
|
|
253
|
+
*/
|
|
254
|
+
private getRunEndTime(runPath: string, lanes: LaneInfo[]): number | undefined {
|
|
255
|
+
const terminalStatuses = ['completed', 'failed'];
|
|
256
|
+
|
|
257
|
+
if (!lanes.every(l => terminalStatuses.includes(l.status))) {
|
|
258
|
+
return undefined; // Still running
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Find the latest end time from lane states
|
|
262
|
+
const lanesDir = path.join(runPath, 'lanes');
|
|
263
|
+
let latestEndTime = 0;
|
|
264
|
+
|
|
265
|
+
if (fs.existsSync(lanesDir)) {
|
|
266
|
+
const laneDirs = fs.readdirSync(lanesDir);
|
|
267
|
+
for (const laneName of laneDirs) {
|
|
268
|
+
const statePath = path.join(lanesDir, laneName, 'state.json');
|
|
269
|
+
if (fs.existsSync(statePath)) {
|
|
270
|
+
try {
|
|
271
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf-8')) as LaneState;
|
|
272
|
+
if (state.endTime && state.endTime > latestEndTime) {
|
|
273
|
+
latestEndTime = state.endTime;
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
// Skip
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return latestEndTime > 0 ? latestEndTime : undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get resources linked to a run (branches, worktrees)
|
|
287
|
+
*/
|
|
288
|
+
getLinkedResources(runId: string): RunResources {
|
|
289
|
+
const runInfo = this.getRunInfo(runId);
|
|
290
|
+
|
|
291
|
+
if (!runInfo) {
|
|
292
|
+
return { branches: [], worktrees: [], logSize: 0 };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Calculate log size
|
|
296
|
+
let logSize = 0;
|
|
297
|
+
try {
|
|
298
|
+
logSize = this.getDirectorySize(runInfo.path);
|
|
299
|
+
} catch {
|
|
300
|
+
// Ignore size calculation errors
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
branches: runInfo.branches,
|
|
305
|
+
worktrees: runInfo.worktrees,
|
|
306
|
+
logSize,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Stop a running workflow
|
|
312
|
+
*/
|
|
313
|
+
stopRun(runId: string): boolean {
|
|
314
|
+
const runInfo = this.getRunInfo(runId);
|
|
315
|
+
|
|
316
|
+
if (!runInfo || runInfo.status !== 'running') {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let stopped = false;
|
|
321
|
+
for (const lane of runInfo.lanes) {
|
|
322
|
+
if (lane.pid && lane.status === 'running') {
|
|
323
|
+
try {
|
|
324
|
+
process.kill(lane.pid, 'SIGTERM');
|
|
325
|
+
stopped = true;
|
|
326
|
+
logger.info(`Stopped lane ${lane.name} (PID: ${lane.pid})`, { context: runId });
|
|
327
|
+
} catch (error) {
|
|
328
|
+
logger.debug(`Failed to stop lane ${lane.name}: ${error}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return stopped;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Stop all running workflows
|
|
338
|
+
*/
|
|
339
|
+
stopAllRuns(): number {
|
|
340
|
+
const activeRuns = this.getActiveRuns();
|
|
341
|
+
let stoppedCount = 0;
|
|
342
|
+
|
|
343
|
+
for (const run of activeRuns) {
|
|
344
|
+
if (this.stopRun(run.id)) {
|
|
345
|
+
stoppedCount++;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return stoppedCount;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Delete a run and optionally its associated resources
|
|
354
|
+
*/
|
|
355
|
+
deleteRun(runId: string, options: { includeBranches?: boolean; force?: boolean } = {}): void {
|
|
356
|
+
const runInfo = this.getRunInfo(runId);
|
|
357
|
+
|
|
358
|
+
if (!runInfo) {
|
|
359
|
+
throw new Error(`Run not found: ${runId}`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Don't delete running runs
|
|
363
|
+
if (runInfo.status === 'running') {
|
|
364
|
+
throw new Error(`Cannot delete running run: ${runId}. Stop it first.`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Delete log directory
|
|
368
|
+
if (fs.existsSync(runInfo.path)) {
|
|
369
|
+
fs.rmSync(runInfo.path, { recursive: true, force: true });
|
|
370
|
+
logger.success(`Deleted run logs: ${runId}`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Note: Branch and worktree deletion should be handled by clean command
|
|
374
|
+
// as it requires git operations
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Calculate directory size in bytes
|
|
379
|
+
*/
|
|
380
|
+
private getDirectorySize(dirPath: string): number {
|
|
381
|
+
let size = 0;
|
|
382
|
+
|
|
383
|
+
const files = fs.readdirSync(dirPath);
|
|
384
|
+
for (const file of files) {
|
|
385
|
+
const filePath = path.join(dirPath, file);
|
|
386
|
+
const stat = fs.statSync(filePath);
|
|
387
|
+
|
|
388
|
+
if (stat.isDirectory()) {
|
|
389
|
+
size += this.getDirectorySize(filePath);
|
|
390
|
+
} else {
|
|
391
|
+
size += stat.size;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return size;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Format duration for display
|
|
400
|
+
*/
|
|
401
|
+
static formatDuration(ms: number): string {
|
|
402
|
+
const seconds = Math.floor(ms / 1000);
|
|
403
|
+
const minutes = Math.floor(seconds / 60);
|
|
404
|
+
const hours = Math.floor(minutes / 60);
|
|
405
|
+
|
|
406
|
+
if (hours > 0) {
|
|
407
|
+
return `${hours}h ${minutes % 60}m`;
|
|
408
|
+
} else if (minutes > 0) {
|
|
409
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
410
|
+
} else {
|
|
411
|
+
return `${seconds}s`;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Format bytes for display
|
|
417
|
+
*/
|
|
418
|
+
static formatBytes(bytes: number): string {
|
|
419
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
420
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
421
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
422
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Create a RunService instance with default paths
|
|
428
|
+
*/
|
|
429
|
+
export function createRunService(projectRoot?: string): RunService {
|
|
430
|
+
const root = projectRoot || process.cwd();
|
|
431
|
+
const logsDir = path.join(root, '_cursorflow', 'logs');
|
|
432
|
+
return new RunService(logsDir);
|
|
433
|
+
}
|