@litmers/cursorflow-orchestrator 0.1.31 → 0.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +144 -52
  2. package/commands/cursorflow-add.md +159 -0
  3. package/commands/cursorflow-monitor.md +23 -2
  4. package/commands/cursorflow-new.md +87 -0
  5. package/dist/cli/add.d.ts +7 -0
  6. package/dist/cli/add.js +377 -0
  7. package/dist/cli/add.js.map +1 -0
  8. package/dist/cli/clean.js +1 -0
  9. package/dist/cli/clean.js.map +1 -1
  10. package/dist/cli/config.d.ts +7 -0
  11. package/dist/cli/config.js +181 -0
  12. package/dist/cli/config.js.map +1 -0
  13. package/dist/cli/index.js +34 -30
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/cli/logs.js +7 -33
  16. package/dist/cli/logs.js.map +1 -1
  17. package/dist/cli/monitor.js +51 -62
  18. package/dist/cli/monitor.js.map +1 -1
  19. package/dist/cli/new.d.ts +7 -0
  20. package/dist/cli/new.js +232 -0
  21. package/dist/cli/new.js.map +1 -0
  22. package/dist/cli/prepare.js +95 -193
  23. package/dist/cli/prepare.js.map +1 -1
  24. package/dist/cli/resume.js +11 -47
  25. package/dist/cli/resume.js.map +1 -1
  26. package/dist/cli/run.js +27 -22
  27. package/dist/cli/run.js.map +1 -1
  28. package/dist/cli/tasks.js +1 -2
  29. package/dist/cli/tasks.js.map +1 -1
  30. package/dist/core/failure-policy.d.ts +9 -0
  31. package/dist/core/failure-policy.js +9 -0
  32. package/dist/core/failure-policy.js.map +1 -1
  33. package/dist/core/orchestrator.d.ts +20 -6
  34. package/dist/core/orchestrator.js +213 -333
  35. package/dist/core/orchestrator.js.map +1 -1
  36. package/dist/core/runner/agent.d.ts +27 -0
  37. package/dist/core/runner/agent.js +294 -0
  38. package/dist/core/runner/agent.js.map +1 -0
  39. package/dist/core/runner/index.d.ts +5 -0
  40. package/dist/core/runner/index.js +22 -0
  41. package/dist/core/runner/index.js.map +1 -0
  42. package/dist/core/runner/pipeline.d.ts +9 -0
  43. package/dist/core/runner/pipeline.js +539 -0
  44. package/dist/core/runner/pipeline.js.map +1 -0
  45. package/dist/core/runner/prompt.d.ts +25 -0
  46. package/dist/core/runner/prompt.js +175 -0
  47. package/dist/core/runner/prompt.js.map +1 -0
  48. package/dist/core/runner/task.d.ts +26 -0
  49. package/dist/core/runner/task.js +283 -0
  50. package/dist/core/runner/task.js.map +1 -0
  51. package/dist/core/runner/utils.d.ts +37 -0
  52. package/dist/core/runner/utils.js +161 -0
  53. package/dist/core/runner/utils.js.map +1 -0
  54. package/dist/core/runner.d.ts +2 -96
  55. package/dist/core/runner.js +11 -1136
  56. package/dist/core/runner.js.map +1 -1
  57. package/dist/core/stall-detection.d.ts +326 -0
  58. package/dist/core/stall-detection.js +781 -0
  59. package/dist/core/stall-detection.js.map +1 -0
  60. package/dist/types/config.d.ts +6 -6
  61. package/dist/types/flow.d.ts +84 -0
  62. package/dist/types/flow.js +10 -0
  63. package/dist/types/flow.js.map +1 -0
  64. package/dist/types/index.d.ts +1 -0
  65. package/dist/types/index.js +3 -3
  66. package/dist/types/index.js.map +1 -1
  67. package/dist/types/lane.d.ts +0 -2
  68. package/dist/types/logging.d.ts +5 -1
  69. package/dist/types/task.d.ts +7 -11
  70. package/dist/utils/config.js +7 -15
  71. package/dist/utils/config.js.map +1 -1
  72. package/dist/utils/dependency.d.ts +36 -1
  73. package/dist/utils/dependency.js +256 -1
  74. package/dist/utils/dependency.js.map +1 -1
  75. package/dist/utils/enhanced-logger.d.ts +45 -82
  76. package/dist/utils/enhanced-logger.js +238 -844
  77. package/dist/utils/enhanced-logger.js.map +1 -1
  78. package/dist/utils/git.d.ts +29 -0
  79. package/dist/utils/git.js +115 -5
  80. package/dist/utils/git.js.map +1 -1
  81. package/dist/utils/state.js +0 -2
  82. package/dist/utils/state.js.map +1 -1
  83. package/dist/utils/task-service.d.ts +2 -2
  84. package/dist/utils/task-service.js +40 -31
  85. package/dist/utils/task-service.js.map +1 -1
  86. package/package.json +4 -3
  87. package/src/cli/add.ts +397 -0
  88. package/src/cli/clean.ts +1 -0
  89. package/src/cli/config.ts +177 -0
  90. package/src/cli/index.ts +36 -32
  91. package/src/cli/logs.ts +7 -31
  92. package/src/cli/monitor.ts +55 -71
  93. package/src/cli/new.ts +235 -0
  94. package/src/cli/prepare.ts +98 -205
  95. package/src/cli/resume.ts +13 -56
  96. package/src/cli/run.ts +311 -306
  97. package/src/cli/tasks.ts +1 -2
  98. package/src/core/failure-policy.ts +9 -0
  99. package/src/core/orchestrator.ts +277 -378
  100. package/src/core/runner/agent.ts +314 -0
  101. package/src/core/runner/index.ts +6 -0
  102. package/src/core/runner/pipeline.ts +567 -0
  103. package/src/core/runner/prompt.ts +174 -0
  104. package/src/core/runner/task.ts +320 -0
  105. package/src/core/runner/utils.ts +142 -0
  106. package/src/core/runner.ts +8 -1347
  107. package/src/core/stall-detection.ts +936 -0
  108. package/src/types/config.ts +6 -6
  109. package/src/types/flow.ts +91 -0
  110. package/src/types/index.ts +15 -3
  111. package/src/types/lane.ts +0 -2
  112. package/src/types/logging.ts +5 -1
  113. package/src/types/task.ts +7 -11
  114. package/src/utils/config.ts +8 -16
  115. package/src/utils/dependency.ts +311 -2
  116. package/src/utils/enhanced-logger.ts +263 -927
  117. package/src/utils/git.ts +145 -5
  118. package/src/utils/state.ts +0 -2
  119. package/src/utils/task-service.ts +48 -40
  120. package/commands/cursorflow-review.md +0 -56
  121. package/commands/cursorflow-runs.md +0 -59
  122. package/dist/cli/runs.d.ts +0 -5
  123. package/dist/cli/runs.js +0 -214
  124. package/dist/cli/runs.js.map +0 -1
  125. package/dist/core/reviewer.d.ts +0 -66
  126. package/dist/core/reviewer.js +0 -265
  127. package/dist/core/reviewer.js.map +0 -1
  128. package/src/cli/runs.ts +0 -212
  129. package/src/core/reviewer.ts +0 -285
@@ -0,0 +1,314 @@
1
+ import { spawn, spawnSync } from 'child_process';
2
+ import * as logger from '../../utils/logger';
3
+ import { AgentSendResult, DependencyRequestPlan } from '../../types';
4
+ import { withRetry } from '../failure-policy';
5
+ import * as path from 'path';
6
+ import * as fs from 'fs';
7
+ import { appendLog, createConversationEntry } from '../../utils/state';
8
+
9
+ /**
10
+ * Execute cursor-agent command with timeout and better error handling
11
+ */
12
+ export function cursorAgentCreateChat(workspaceDir?: string): string {
13
+ try {
14
+ const args = ['create-chat'];
15
+ if (workspaceDir) {
16
+ args.push('--workspace', workspaceDir);
17
+ }
18
+
19
+ const res = spawnSync('cursor-agent', args, {
20
+ encoding: 'utf8',
21
+ stdio: 'pipe',
22
+ timeout: 30000, // 30 second timeout
23
+ cwd: workspaceDir || process.cwd(),
24
+ });
25
+
26
+ if (res.error || res.status !== 0) {
27
+ throw res.error || new Error(res.stderr || 'Failed to create chat');
28
+ }
29
+
30
+ const out = res.stdout;
31
+ const lines = out.split('\n').filter(Boolean);
32
+ const chatId = lines[lines.length - 1] || null;
33
+
34
+ if (!chatId) {
35
+ throw new Error('Failed to get chat ID from cursor-agent');
36
+ }
37
+
38
+ logger.info(`Created chat session: ${chatId}`);
39
+ return chatId;
40
+ } catch (error: any) {
41
+ // Check for common errors
42
+ if (error.message.includes('ENOENT')) {
43
+ throw new Error('cursor-agent CLI not found. Install with: npm install -g @cursor/agent');
44
+ }
45
+
46
+ if (error.message.includes('ETIMEDOUT') || error.killed) {
47
+ throw new Error('cursor-agent timed out. Check your internet connection and Cursor authentication.');
48
+ }
49
+
50
+ if (error.stderr) {
51
+ const stderr = error.stderr.toString();
52
+
53
+ // Check for authentication errors
54
+ if (stderr.includes('not authenticated') ||
55
+ stderr.includes('login') ||
56
+ stderr.includes('auth')) {
57
+ throw new Error(
58
+ 'Cursor authentication failed. Please:\n' +
59
+ ' 1. Open Cursor IDE\n' +
60
+ ' 2. Sign in to your account\n' +
61
+ ' 3. Verify you can use AI features\n' +
62
+ ' 4. Try running cursorflow again\n\n' +
63
+ `Original error: ${stderr.trim()}`
64
+ );
65
+ }
66
+
67
+ // Check for API key errors
68
+ if (stderr.includes('api key') || stderr.includes('API_KEY')) {
69
+ throw new Error(
70
+ 'Cursor API key error. Please check your Cursor account and subscription.\n' +
71
+ `Error: ${stderr.trim()}`
72
+ );
73
+ }
74
+ }
75
+
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Helper to parse JSON from stdout
82
+ */
83
+ function parseJsonFromStdout(stdout: string): any {
84
+ if (!stdout) return null;
85
+
86
+ // Try to find JSON in the output (sometimes mixed with other text)
87
+ const lines = stdout.split('\n');
88
+ for (let i = lines.length - 1; i >= 0; i--) {
89
+ const line = lines[i]!.trim();
90
+ if (line.startsWith('{') && line.endsWith('}')) {
91
+ try {
92
+ return JSON.parse(line);
93
+ } catch {
94
+ // Continue searching
95
+ }
96
+ }
97
+ }
98
+
99
+ // If no single-line JSON, try the whole stdout
100
+ try {
101
+ return JSON.parse(stdout.trim());
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Execute cursor-agent command with timeout and better error handling
109
+ */
110
+ async function cursorAgentSendRaw({ workspaceDir, chatId, prompt, model, signalDir, timeout, enableIntervention, outputFormat, taskName }: {
111
+ workspaceDir: string;
112
+ chatId: string;
113
+ prompt: string;
114
+ model?: string;
115
+ signalDir?: string;
116
+ timeout?: number;
117
+ enableIntervention?: boolean;
118
+ outputFormat?: 'json' | 'plain';
119
+ taskName?: string;
120
+ }): Promise<AgentSendResult> {
121
+ const timeoutMs = timeout || 10 * 60 * 1000; // 10 minutes default
122
+ const args = ['send', chatId, prompt];
123
+
124
+ if (model) {
125
+ args.push('--model', model);
126
+ }
127
+
128
+ if (outputFormat === 'json') {
129
+ args.push('--format', 'json');
130
+ }
131
+
132
+ // Add worktree context if provided
133
+ if (workspaceDir) {
134
+ args.push('--workspace', workspaceDir);
135
+ }
136
+
137
+ return new Promise((resolve) => {
138
+ logger.info(`Sending prompt to cursor-agent (timeout: ${Math.round(timeoutMs / 1000)}s)...`);
139
+
140
+ const child = spawn('cursor-agent', args, {
141
+ cwd: workspaceDir || process.cwd(),
142
+ stdio: enableIntervention ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
143
+ });
144
+
145
+ let fullStdout = '';
146
+ let fullStderr = '';
147
+ let timeoutHandle: NodeJS.Timeout;
148
+ let heartbeatInterval: NodeJS.Timeout | undefined;
149
+ let lastActivity = Date.now();
150
+ let bytesReceived = 0;
151
+
152
+ // Signal watching for intervention and timeout
153
+ let signalWatcher: fs.FSWatcher | null = null;
154
+ if (signalDir) {
155
+ if (!fs.existsSync(signalDir)) {
156
+ fs.mkdirSync(signalDir, { recursive: true });
157
+ }
158
+
159
+ const interventionPath = path.join(signalDir, 'intervention.txt');
160
+ const timeoutPath = path.join(signalDir, 'timeout.txt');
161
+
162
+ // Watch for intervention or timeout signals from UI
163
+ signalWatcher = fs.watch(signalDir, (event, filename) => {
164
+ if (filename === 'intervention.txt' && fs.existsSync(interventionPath)) {
165
+ try {
166
+ const message = fs.readFileSync(interventionPath, 'utf8').trim();
167
+ if (message) {
168
+ logger.info(`👋 Human intervention received: ${message.substring(0, 50)}...`);
169
+ if (child.stdin && child.stdin.writable) {
170
+ child.stdin.write(message + '\n');
171
+
172
+ if (signalDir) {
173
+ const convoPath = path.join(signalDir, 'conversation.jsonl');
174
+ appendLog(convoPath, createConversationEntry('intervention', `[HUMAN INTERVENTION]: ${message}`, {
175
+ task: taskName || 'AGENT_TURN',
176
+ model: 'manual'
177
+ }));
178
+ }
179
+ } else {
180
+ logger.warn(`Intervention requested but stdin not available: ${message}`);
181
+ }
182
+ fs.unlinkSync(interventionPath);
183
+ }
184
+ } catch {}
185
+ }
186
+
187
+ if (filename === 'timeout.txt' && timeoutPath && fs.existsSync(timeoutPath)) {
188
+ try {
189
+ const newTimeoutStr = fs.readFileSync(timeoutPath, 'utf8').trim();
190
+ const newTimeoutMs = parseInt(newTimeoutStr);
191
+ if (!isNaN(newTimeoutMs) && newTimeoutMs > 0) {
192
+ logger.info(`⏱ Dynamic timeout update: ${Math.round(newTimeoutMs / 1000)}s`);
193
+ if (timeoutHandle) clearTimeout(timeoutHandle);
194
+ const elapsed = Date.now() - startTime;
195
+ const remaining = Math.max(1000, newTimeoutMs - elapsed);
196
+ timeoutHandle = setTimeout(() => {
197
+ clearInterval(heartbeatInterval);
198
+ child.kill();
199
+ resolve({ ok: false, exitCode: -1, error: `cursor-agent timed out after updated limit.` });
200
+ }, remaining);
201
+ fs.unlinkSync(timeoutPath);
202
+ }
203
+ } catch {}
204
+ }
205
+ });
206
+ }
207
+
208
+ if (child.stdout) {
209
+ child.stdout.on('data', (data) => {
210
+ fullStdout += data.toString();
211
+ bytesReceived += data.length;
212
+ process.stdout.write(data);
213
+ });
214
+ }
215
+
216
+ if (child.stderr) {
217
+ child.stderr.on('data', (data) => {
218
+ fullStderr += data.toString();
219
+ process.stderr.write(data);
220
+ });
221
+ }
222
+
223
+ const startTime = Date.now();
224
+ timeoutHandle = setTimeout(() => {
225
+ clearInterval(heartbeatInterval);
226
+ child.kill();
227
+ resolve({
228
+ ok: false,
229
+ exitCode: -1,
230
+ error: `cursor-agent timed out after ${Math.round(timeoutMs / 1000)} seconds.`,
231
+ });
232
+ }, timeoutMs);
233
+
234
+ child.on('close', (code) => {
235
+ clearTimeout(timeoutHandle);
236
+ clearInterval(heartbeatInterval);
237
+ if (signalWatcher) signalWatcher.close();
238
+
239
+ const json = parseJsonFromStdout(fullStdout);
240
+
241
+ if (code !== 0 || !json || json.type !== 'result') {
242
+ let errorMsg = fullStderr.trim() || fullStdout.trim() || `exit=${code}`;
243
+ resolve({ ok: false, exitCode: code ?? -1, error: errorMsg });
244
+ } else {
245
+ resolve({
246
+ ok: !json.is_error,
247
+ exitCode: code ?? 0,
248
+ sessionId: json.session_id || chatId,
249
+ resultText: json.result || '',
250
+ });
251
+ }
252
+ });
253
+
254
+ child.on('error', (err) => {
255
+ clearTimeout(timeoutHandle);
256
+ if (signalWatcher) signalWatcher.close();
257
+ resolve({ ok: false, exitCode: -1, error: `Failed to start cursor-agent: ${err.message}` });
258
+ });
259
+ });
260
+ }
261
+
262
+ /**
263
+ * Execute cursor-agent command with retries for transient errors
264
+ */
265
+ export async function cursorAgentSend(options: {
266
+ workspaceDir: string;
267
+ chatId: string;
268
+ prompt: string;
269
+ model?: string;
270
+ signalDir?: string;
271
+ timeout?: number;
272
+ enableIntervention?: boolean;
273
+ outputFormat?: 'json' | 'plain';
274
+ taskName?: string;
275
+ }): Promise<AgentSendResult> {
276
+ const laneName = options.signalDir ? path.basename(path.dirname(options.signalDir)) : 'agent';
277
+
278
+ return withRetry(
279
+ laneName,
280
+ () => cursorAgentSendRaw(options),
281
+ (res) => ({ ok: res.ok, error: res.error }),
282
+ { maxRetries: 3 }
283
+ );
284
+ }
285
+
286
+ /**
287
+ * Extract dependency change request from agent response
288
+ */
289
+ export function extractDependencyRequest(text: string): { required: boolean; plan?: DependencyRequestPlan; raw: string } {
290
+ const t = String(text || '');
291
+ const marker = 'DEPENDENCY_CHANGE_REQUIRED';
292
+
293
+ if (!t.includes(marker)) {
294
+ return { required: false, raw: t };
295
+ }
296
+
297
+ const after = t.split(marker).slice(1).join(marker);
298
+ const match = after.match(/\{[\s\S]*?\}/);
299
+
300
+ if (match) {
301
+ try {
302
+ return {
303
+ required: true,
304
+ plan: JSON.parse(match[0]!) as DependencyRequestPlan,
305
+ raw: t,
306
+ };
307
+ } catch {
308
+ return { required: true, raw: t };
309
+ }
310
+ }
311
+
312
+ return { required: true, raw: t };
313
+ }
314
+
@@ -0,0 +1,6 @@
1
+ export * from './agent';
2
+ export * from './prompt';
3
+ export * from './utils';
4
+ export * from './task';
5
+ export * from './pipeline';
6
+