@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
@@ -1,15 +1,10 @@
1
1
  "use strict";
2
2
  /**
3
- * Enhanced Logger - Comprehensive terminal output capture and management
3
+ * Enhanced Logger - Simplified terminal output capture
4
4
  *
5
5
  * Features:
6
- * - ANSI escape sequence stripping for clean logs
7
- * - Automatic timestamps on each line
8
- * - Log rotation and size management
9
- * - Session headers with context
10
- * - Raw and clean log file options
11
- * - Structured JSON logs for programmatic access
12
- * - Streaming output support for real-time capture
6
+ * - Raw log: Original output as-is
7
+ * - Readable log: Formatted with formatMessageForConsole style
13
8
  */
14
9
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
10
  if (k2 === undefined) k2 = k;
@@ -45,7 +40,7 @@ var __importStar = (this && this.__importStar) || (function () {
45
40
  };
46
41
  })();
47
42
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.EnhancedLogManager = exports.CleanLogTransform = exports.StreamingMessageParser = exports.DEFAULT_LOG_CONFIG = void 0;
43
+ exports.CleanLogTransform = exports.StreamingMessageParser = exports.EnhancedLogManager = exports.DEFAULT_LOG_CONFIG = void 0;
49
44
  exports.stripAnsi = stripAnsi;
50
45
  exports.formatTimestamp = formatTimestamp;
51
46
  exports.createLogManager = createLogManager;
@@ -53,8 +48,7 @@ exports.readJsonLog = readJsonLog;
53
48
  exports.exportLogs = exportLogs;
54
49
  const fs = __importStar(require("fs"));
55
50
  const path = __importStar(require("path"));
56
- const stream_1 = require("stream");
57
- const logger = __importStar(require("./logger"));
51
+ const log_formatter_1 = require("./log-formatter");
58
52
  const path_1 = require("./path");
59
53
  exports.DEFAULT_LOG_CONFIG = {
60
54
  enabled: true,
@@ -65,181 +59,13 @@ exports.DEFAULT_LOG_CONFIG = {
65
59
  keepRawLogs: true,
66
60
  keepAbsoluteRawLogs: false,
67
61
  raw: false,
68
- writeJsonLog: true,
62
+ writeJsonLog: false, // Disabled by default now
69
63
  timestampFormat: 'iso',
70
64
  };
71
- /**
72
- * Streaming JSON Parser - Parses cursor-agent stream-json output
73
- * and combines tokens into readable messages
74
- */
75
- class StreamingMessageParser {
76
- currentMessage = '';
77
- currentRole = '';
78
- messageStartTime = 0;
79
- onMessage;
80
- constructor(onMessage) {
81
- this.onMessage = onMessage;
82
- }
83
- /**
84
- * Parse a line of JSON output from cursor-agent
85
- */
86
- parseLine(line) {
87
- const trimmed = line.trim();
88
- if (!trimmed || !trimmed.startsWith('{'))
89
- return;
90
- try {
91
- const json = JSON.parse(trimmed);
92
- this.handleJsonMessage(json);
93
- }
94
- catch {
95
- // Not valid JSON, ignore
96
- }
97
- }
98
- handleJsonMessage(json) {
99
- const type = json.type;
100
- switch (type) {
101
- case 'system':
102
- // System init message
103
- this.emitMessage({
104
- type: 'system',
105
- role: 'system',
106
- content: `[System] Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
107
- timestamp: json.timestamp_ms || Date.now(),
108
- });
109
- break;
110
- case 'user':
111
- // User message - emit as complete message
112
- if (json.message?.content) {
113
- const textContent = json.message.content
114
- .filter((c) => c.type === 'text')
115
- .map((c) => c.text)
116
- .join('');
117
- this.emitMessage({
118
- type: 'user',
119
- role: 'user',
120
- content: textContent,
121
- timestamp: json.timestamp_ms || Date.now(),
122
- });
123
- }
124
- break;
125
- case 'assistant':
126
- // Streaming assistant message - accumulate tokens
127
- if (json.message?.content) {
128
- const textContent = json.message.content
129
- .filter((c) => c.type === 'text')
130
- .map((c) => c.text)
131
- .join('');
132
- // Check if this is a new message or continuation
133
- if (this.currentRole !== 'assistant') {
134
- // Flush previous message if any
135
- this.flush();
136
- this.currentRole = 'assistant';
137
- this.messageStartTime = json.timestamp_ms || Date.now();
138
- }
139
- this.currentMessage += textContent;
140
- }
141
- break;
142
- case 'tool_call':
143
- // Tool call - emit as formatted message
144
- if (json.subtype === 'started' && json.tool_call) {
145
- const toolName = Object.keys(json.tool_call)[0] || 'unknown';
146
- const toolArgs = json.tool_call[toolName]?.args || {};
147
- this.flush(); // Flush any pending assistant message
148
- this.emitMessage({
149
- type: 'tool',
150
- role: 'tool',
151
- content: `[Tool: ${toolName}] ${JSON.stringify(toolArgs)}`,
152
- timestamp: json.timestamp_ms || Date.now(),
153
- metadata: { callId: json.call_id, toolName },
154
- });
155
- }
156
- else if (json.subtype === 'completed' && json.tool_call) {
157
- const toolName = Object.keys(json.tool_call)[0] || 'unknown';
158
- const result = json.tool_call[toolName]?.result;
159
- if (result?.success) {
160
- // Truncate large results
161
- const content = result.success.content || '';
162
- const truncated = content.length > 500
163
- ? content.substring(0, 500) + '... (truncated)'
164
- : content;
165
- this.emitMessage({
166
- type: 'tool_result',
167
- role: 'tool',
168
- content: `[Tool Result: ${toolName}] ${truncated}`,
169
- timestamp: json.timestamp_ms || Date.now(),
170
- metadata: { callId: json.call_id, toolName, lines: result.success.totalLines },
171
- });
172
- }
173
- }
174
- break;
175
- case 'result':
176
- // Final result - flush any pending and emit result
177
- this.flush();
178
- this.emitMessage({
179
- type: 'result',
180
- role: 'assistant',
181
- content: json.result || '',
182
- timestamp: json.timestamp_ms || Date.now(),
183
- metadata: {
184
- duration_ms: json.duration_ms,
185
- is_error: json.is_error,
186
- subtype: json.subtype,
187
- },
188
- });
189
- break;
190
- case 'thinking':
191
- // Thinking message (Claude 3.7+ etc.)
192
- if (json.subtype === 'delta' && json.text) {
193
- // Check if this is a new message or continuation
194
- if (this.currentRole !== 'thinking') {
195
- // Flush previous message if any
196
- this.flush();
197
- this.currentRole = 'thinking';
198
- this.messageStartTime = json.timestamp_ms || Date.now();
199
- }
200
- this.currentMessage += json.text;
201
- }
202
- else if (json.subtype === 'completed') {
203
- // Thinking completed - flush immediately
204
- this.flush();
205
- }
206
- break;
207
- }
208
- }
209
- /**
210
- * Flush accumulated message
211
- */
212
- flush() {
213
- if (this.currentMessage && this.currentRole) {
214
- this.emitMessage({
215
- type: this.currentRole,
216
- role: this.currentRole,
217
- content: this.currentMessage,
218
- timestamp: this.messageStartTime,
219
- });
220
- }
221
- this.currentMessage = '';
222
- this.currentRole = '';
223
- this.messageStartTime = 0;
224
- }
225
- emitMessage(msg) {
226
- if (msg.content.trim()) {
227
- this.onMessage(msg);
228
- }
229
- }
230
- }
231
- exports.StreamingMessageParser = StreamingMessageParser;
232
65
  /**
233
66
  * ANSI escape sequence regex pattern
234
- * Matches:
235
- * - CSI sequences (colors, cursor movement, etc.)
236
- * - OSC sequences (terminal titles, etc.)
237
- * - Single-character escape codes
238
67
  */
239
68
  const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
240
- /**
241
- * Extended ANSI regex for more complete stripping
242
- */
243
69
  const EXTENDED_ANSI_REGEX = /(?:\x1B[@-Z\\-_]|\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)|\x1B[PX^_][^\x1B]*\x1B\\|\x1B.)/g;
244
70
  /**
245
71
  * Strip ANSI escape sequences from text
@@ -248,13 +74,11 @@ function stripAnsi(text) {
248
74
  return text
249
75
  .replace(EXTENDED_ANSI_REGEX, '')
250
76
  .replace(ANSI_REGEX, '')
251
- // Also remove carriage returns that overwrite lines (progress bars, etc.)
252
77
  .replace(/\r[^\n]/g, '\n')
253
- // Clean up any remaining control characters except newlines/tabs
254
78
  .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
255
79
  }
256
80
  /**
257
- * Format timestamp based on format preference
81
+ * Format timestamp
258
82
  */
259
83
  function formatTimestamp(format, startTime) {
260
84
  const now = Date.now();
@@ -285,239 +109,67 @@ function formatTimestamp(format, startTime) {
285
109
  }
286
110
  }
287
111
  /**
288
- * Regex to detect if a line already has a timestamp at the start
289
- * Matches:
290
- * - ISO format: [2024-01-01T12:34:56...]
291
- * - Short format: [12:34:56]
292
- */
293
- const EXISTING_TIMESTAMP_REGEX = /^\[(\d{4}-\d{2}-\d{2}T)?\d{2}:\d{2}:\d{2}/;
294
- /**
295
- * Check if a line already has a timestamp
296
- */
297
- function hasExistingTimestamp(line) {
298
- return EXISTING_TIMESTAMP_REGEX.test(line.trim());
299
- }
300
- /**
301
- * Transform stream that strips ANSI and adds timestamps
302
- */
303
- class CleanLogTransform extends stream_1.Transform {
304
- config;
305
- session;
306
- buffer = '';
307
- constructor(config, session) {
308
- super({ encoding: 'utf8' });
309
- this.config = config;
310
- this.session = session;
311
- }
312
- _transform(chunk, encoding, callback) {
313
- let text = chunk.toString();
314
- // Buffer partial lines
315
- this.buffer += text;
316
- const lines = this.buffer.split('\n');
317
- // Keep the last incomplete line in buffer
318
- this.buffer = lines.pop() || '';
319
- for (const line of lines) {
320
- let processed = line;
321
- // Strip ANSI if enabled
322
- if (this.config.stripAnsi) {
323
- processed = stripAnsi(processed);
324
- }
325
- // Add timestamp if enabled AND line doesn't already have one
326
- if (this.config.addTimestamps && processed.trim() && !hasExistingTimestamp(processed)) {
327
- const ts = formatTimestamp(this.config.timestampFormat, this.session.startTime);
328
- processed = `[${ts}] ${processed}`;
329
- }
330
- this.push(processed + '\n');
331
- }
332
- callback();
333
- }
334
- _flush(callback) {
335
- // Process any remaining buffered content
336
- if (this.buffer.trim()) {
337
- let processed = this.buffer;
338
- if (this.config.stripAnsi) {
339
- processed = stripAnsi(processed);
340
- }
341
- if (this.config.addTimestamps && processed.trim() && !hasExistingTimestamp(processed)) {
342
- const ts = formatTimestamp(this.config.timestampFormat, this.session.startTime);
343
- processed = `[${ts}] ${processed}`;
344
- }
345
- this.push(processed + '\n');
346
- }
347
- callback();
348
- }
349
- }
350
- exports.CleanLogTransform = CleanLogTransform;
351
- /**
352
- * Enhanced Log Manager - Manages log files with rotation and multiple outputs
112
+ * Simplified Log Manager - Only raw and readable logs
353
113
  */
354
114
  class EnhancedLogManager {
355
115
  config;
356
116
  session;
357
117
  logDir;
358
- cleanLogPath;
359
118
  rawLogPath;
360
- absoluteRawLogPath;
361
- jsonLogPath;
362
119
  readableLogPath;
363
- cleanLogFd = null;
364
120
  rawLogFd = null;
365
- absoluteRawLogFd = null;
366
- jsonLogFd = null;
367
121
  readableLogFd = null;
368
- cleanLogSize = 0;
369
122
  rawLogSize = 0;
370
- absoluteRawLogSize = 0;
371
- cleanTransform = null;
372
- streamingParser = null;
373
- lineBuffer = '';
374
123
  onParsedMessage;
375
124
  constructor(logDir, session, config = {}, onParsedMessage) {
376
125
  this.config = { ...exports.DEFAULT_LOG_CONFIG, ...config };
377
- // Support 'raw' as alias for 'keepAbsoluteRawLogs'
378
- if (this.config.raw) {
379
- this.config.keepAbsoluteRawLogs = true;
380
- }
381
126
  this.session = session;
382
127
  this.logDir = logDir;
383
128
  this.onParsedMessage = onParsedMessage;
384
129
  // Ensure log directory exists
385
130
  fs.mkdirSync(logDir, { recursive: true });
386
- // Set up log file paths
387
- this.cleanLogPath = (0, path_1.safeJoin)(logDir, 'terminal.log');
131
+ // Set up log file paths (simplified)
388
132
  this.rawLogPath = (0, path_1.safeJoin)(logDir, 'terminal-raw.log');
389
- this.absoluteRawLogPath = (0, path_1.safeJoin)(logDir, 'terminal-absolute-raw.log');
390
- this.jsonLogPath = (0, path_1.safeJoin)(logDir, 'terminal.jsonl');
391
133
  this.readableLogPath = (0, path_1.safeJoin)(logDir, 'terminal-readable.log');
392
- if (this.config.raw) {
393
- logger.info(`[${session.laneName}] 📄 Raw data capture enabled: ${this.absoluteRawLogPath}`);
394
- }
395
134
  // Initialize log files
396
135
  this.initLogFiles();
397
136
  }
137
+ /**
138
+ * Get short time format (HH:MM:SS)
139
+ */
140
+ getShortTime() {
141
+ return new Date().toLocaleTimeString('en-US', { hour12: false });
142
+ }
143
+ /**
144
+ * Get lane-task label like [L01-T02]
145
+ */
146
+ getLaneTaskLabel() {
147
+ const laneNum = (this.session.laneIndex ?? 0) + 1;
148
+ const taskNum = (this.session.taskIndex ?? 0) + 1;
149
+ return `L${laneNum.toString().padStart(2, '0')}-T${taskNum.toString().padStart(2, '0')}`;
150
+ }
398
151
  /**
399
152
  * Initialize log files and write session headers
400
153
  */
401
154
  initLogFiles() {
402
155
  // Check and rotate if necessary
403
- this.rotateIfNeeded(this.cleanLogPath, 'clean');
404
- if (this.config.keepRawLogs) {
405
- this.rotateIfNeeded(this.rawLogPath, 'raw');
406
- }
407
- if (this.config.keepAbsoluteRawLogs) {
408
- this.rotateIfNeeded(this.absoluteRawLogPath, 'raw');
409
- }
410
- // Open file descriptors
411
- this.cleanLogFd = fs.openSync(this.cleanLogPath, 'a');
412
156
  if (this.config.keepRawLogs) {
157
+ this.rotateIfNeeded(this.rawLogPath);
413
158
  this.rawLogFd = fs.openSync(this.rawLogPath, 'a');
414
159
  }
415
- if (this.config.keepAbsoluteRawLogs) {
416
- this.absoluteRawLogFd = fs.openSync(this.absoluteRawLogPath, 'a');
417
- }
418
- if (this.config.writeJsonLog) {
419
- this.jsonLogFd = fs.openSync(this.jsonLogPath, 'a');
420
- }
421
- // Open readable log file (for parsed streaming output)
160
+ this.rotateIfNeeded(this.readableLogPath);
422
161
  this.readableLogFd = fs.openSync(this.readableLogPath, 'a');
423
- // Get initial file sizes
424
- try {
425
- this.cleanLogSize = fs.statSync(this.cleanLogPath).size;
426
- if (this.config.keepRawLogs) {
162
+ // Get initial file size for raw log if enabled
163
+ if (this.rawLogFd !== null) {
164
+ try {
427
165
  this.rawLogSize = fs.statSync(this.rawLogPath).size;
428
166
  }
429
- if (this.config.keepAbsoluteRawLogs) {
430
- this.absoluteRawLogSize = fs.statSync(this.absoluteRawLogPath).size;
167
+ catch {
168
+ this.rawLogSize = 0;
431
169
  }
432
170
  }
433
- catch {
434
- this.cleanLogSize = 0;
435
- this.rawLogSize = 0;
436
- this.absoluteRawLogSize = 0;
437
- }
438
171
  // Write session header
439
172
  this.writeSessionHeader();
440
- // Create transform stream
441
- this.cleanTransform = new CleanLogTransform(this.config, this.session);
442
- this.cleanTransform.on('data', (data) => {
443
- this.writeToCleanLog(data);
444
- });
445
- // Create streaming parser for readable log
446
- this.streamingParser = new StreamingMessageParser((msg) => {
447
- this.writeReadableMessage(msg);
448
- if (this.onParsedMessage) {
449
- this.onParsedMessage(msg);
450
- }
451
- });
452
- }
453
- /**
454
- * Write a parsed message to the readable log
455
- */
456
- writeReadableMessage(msg) {
457
- if (this.readableLogFd === null)
458
- return;
459
- const ts = new Date(msg.timestamp).toISOString();
460
- let formatted;
461
- switch (msg.type) {
462
- case 'system':
463
- formatted = `[${ts}] ⚙️ SYSTEM: ${msg.content}\n`;
464
- break;
465
- case 'user':
466
- case 'assistant':
467
- case 'result':
468
- // Format with brackets and line (compact)
469
- const isUser = msg.type === 'user';
470
- const isResult = msg.type === 'result';
471
- const headerText = isUser ? '🧑 USER' : isResult ? '🤖 ASSISTANT (Final)' : '🤖 ASSISTANT';
472
- const duration = msg.metadata?.duration_ms
473
- ? ` (${Math.round(msg.metadata.duration_ms / 1000)}s)`
474
- : '';
475
- const label = `[ ${headerText}${duration} ] `;
476
- const totalWidth = 80;
477
- const topBorder = `┌─${label}${'─'.repeat(Math.max(0, totalWidth - label.length - 2))}`;
478
- const bottomBorder = `└─${'─'.repeat(totalWidth - 2)}`;
479
- const lines = msg.content.split('\n');
480
- formatted = `[${ts}] ${topBorder}\n`;
481
- for (const line of lines) {
482
- formatted += `[${ts}] │ ${line}\n`;
483
- }
484
- formatted += `[${ts}] ${bottomBorder}\n`;
485
- break;
486
- case 'tool':
487
- formatted = `[${ts}] 🔧 TOOL: ${msg.content}\n`;
488
- break;
489
- case 'tool_result':
490
- const toolResultLines = msg.metadata?.lines ? ` (${msg.metadata.lines} lines)` : '';
491
- formatted = `[${ts}] 📄 RESL: ${msg.metadata?.toolName || 'Tool'}${toolResultLines}\n`;
492
- break;
493
- case 'thinking':
494
- // Format thinking block
495
- const thinkLabel = `[ 🤔 THINKING ] `;
496
- const thinkWidth = 80;
497
- const thinkTop = `┌─${thinkLabel}${'─'.repeat(Math.max(0, thinkWidth - thinkLabel.length - 2))}`;
498
- const thinkBottom = `└─${'─'.repeat(thinkWidth - 2)}`;
499
- const thinkLines = msg.content.trim().split('\n');
500
- formatted = `[${ts}] ${thinkTop}\n`;
501
- for (const line of thinkLines) {
502
- formatted += `[${ts}] │ ${line}\n`;
503
- }
504
- formatted += `[${ts}] ${thinkBottom}\n`;
505
- break;
506
- default:
507
- formatted = `[${ts}] ${msg.content}\n`;
508
- }
509
- try {
510
- fs.writeSync(this.readableLogFd, formatted);
511
- }
512
- catch {
513
- // Ignore write errors
514
- }
515
- }
516
- /**
517
- * Indent text with a prefix
518
- */
519
- indentText(text, prefix) {
520
- return text.split('\n').map(line => prefix + line).join('\n');
521
173
  }
522
174
  /**
523
175
  * Write session header to logs
@@ -535,29 +187,13 @@ class EnhancedLogManager {
535
187
  ╚══════════════════════════════════════════════════════════════════════════════╝
536
188
 
537
189
  `;
538
- this.writeToCleanLog(header);
539
- if (this.config.keepRawLogs && this.rawLogFd !== null) {
540
- fs.writeSync(this.rawLogFd, header);
541
- }
542
- // Write JSON session entry
543
- this.writeJsonEntry({
544
- timestamp: new Date(this.session.startTime).toISOString(),
545
- level: 'session',
546
- source: 'system',
547
- lane: this.session.laneName,
548
- task: this.session.taskName,
549
- message: 'Session started',
550
- metadata: {
551
- sessionId: this.session.id,
552
- model: this.session.model,
553
- ...this.session.metadata,
554
- },
555
- });
190
+ this.writeToRawLog(header);
191
+ this.writeToReadableLog(header);
556
192
  }
557
193
  /**
558
194
  * Rotate log file if it exceeds max size
559
195
  */
560
- rotateIfNeeded(logPath, type) {
196
+ rotateIfNeeded(logPath) {
561
197
  if (!fs.existsSync(logPath))
562
198
  return;
563
199
  try {
@@ -594,30 +230,13 @@ class EnhancedLogManager {
594
230
  const rotatedPath = (0, path_1.safeJoin)(dir, `${base}.1${ext}`);
595
231
  fs.renameSync(logPath, rotatedPath);
596
232
  }
597
- /**
598
- * Write to clean log with size tracking
599
- */
600
- writeToCleanLog(data) {
601
- if (this.cleanLogFd === null)
602
- return;
603
- const buffer = Buffer.from(data);
604
- fs.writeSync(this.cleanLogFd, buffer);
605
- this.cleanLogSize += buffer.length;
606
- // Check if rotation needed
607
- if (this.cleanLogSize >= this.config.maxFileSize) {
608
- fs.closeSync(this.cleanLogFd);
609
- this.rotateLog(this.cleanLogPath);
610
- this.cleanLogFd = fs.openSync(this.cleanLogPath, 'a');
611
- this.cleanLogSize = 0;
612
- }
613
- }
614
233
  /**
615
234
  * Write to raw log with size tracking
616
235
  */
617
236
  writeToRawLog(data) {
618
237
  if (this.rawLogFd === null)
619
238
  return;
620
- const buffer = Buffer.from(data);
239
+ const buffer = typeof data === 'string' ? Buffer.from(data) : data;
621
240
  fs.writeSync(this.rawLogFd, buffer);
622
241
  this.rawLogSize += buffer.length;
623
242
  // Check if rotation needed
@@ -629,212 +248,197 @@ class EnhancedLogManager {
629
248
  }
630
249
  }
631
250
  /**
632
- * Write to absolute raw log with size tracking
251
+ * Write to readable log
633
252
  */
634
- writeToAbsoluteRawLog(data) {
635
- if (this.absoluteRawLogFd === null)
253
+ writeToReadableLog(data) {
254
+ if (this.readableLogFd === null)
636
255
  return;
637
- const buffer = typeof data === 'string' ? Buffer.from(data) : data;
638
- fs.writeSync(this.absoluteRawLogFd, buffer);
639
- this.absoluteRawLogSize += buffer.length;
640
- // Check if rotation needed
641
- if (this.absoluteRawLogSize >= this.config.maxFileSize) {
642
- fs.closeSync(this.absoluteRawLogFd);
643
- this.rotateLog(this.absoluteRawLogPath);
644
- this.absoluteRawLogFd = fs.openSync(this.absoluteRawLogPath, 'a');
645
- this.absoluteRawLogSize = 0;
256
+ try {
257
+ fs.writeSync(this.readableLogFd, data);
258
+ }
259
+ catch {
260
+ // Ignore write errors
646
261
  }
647
262
  }
648
263
  /**
649
- * Write a JSON log entry
264
+ * Write a parsed message to the readable log using formatMessageForConsole style
650
265
  */
651
- writeJsonEntry(entry) {
652
- if (this.jsonLogFd === null)
653
- return;
654
- const line = JSON.stringify(entry) + '\n';
655
- fs.writeSync(this.jsonLogFd, line);
266
+ writeReadableMessage(msg) {
267
+ // Use formatMessageForConsole for consistent formatting
268
+ // Use short lane-task label like [L01-T02]
269
+ const formatted = (0, log_formatter_1.formatMessageForConsole)(msg, {
270
+ laneLabel: `[${this.getLaneTaskLabel()}]`,
271
+ includeTimestamp: false, // We'll add our own short timestamp
272
+ });
273
+ // Strip ANSI codes and add short timestamp for file output
274
+ const clean = stripAnsi(formatted);
275
+ const ts = this.getShortTime();
276
+ this.writeToReadableLog(`[${ts}] ${clean}\n`);
277
+ // Callback for console output
278
+ if (this.onParsedMessage) {
279
+ this.onParsedMessage(msg);
280
+ }
656
281
  }
657
282
  /**
658
283
  * Write stdout data
659
284
  */
660
285
  writeStdout(data) {
661
286
  const text = data.toString();
662
- // Write absolute raw log
663
- if (this.config.keepAbsoluteRawLogs) {
664
- this.writeToAbsoluteRawLog(data);
665
- }
666
- // Write raw log
667
- if (this.config.keepRawLogs) {
668
- this.writeToRawLog(text);
669
- }
670
- // Process through transform for clean log
671
- if (this.cleanTransform) {
672
- this.cleanTransform.write(data);
673
- }
674
- // Process lines for readable log and JSON entries
675
- this.lineBuffer += text;
676
- const lines = this.lineBuffer.split('\n');
677
- this.lineBuffer = lines.pop() || '';
287
+ // Write raw log (original data)
288
+ this.writeToRawLog(data);
289
+ // Parse JSON output and write to readable log
290
+ const lines = text.split('\n');
678
291
  for (const line of lines) {
679
- const cleanLine = stripAnsi(line).trim();
680
- if (!cleanLine)
292
+ const trimmed = line.trim();
293
+ if (!trimmed)
681
294
  continue;
682
- // Handle streaming JSON messages
683
- if (cleanLine.startsWith('{')) {
684
- if (this.streamingParser) {
685
- this.streamingParser.parseLine(cleanLine);
686
- }
687
- // Special handling for terminal.jsonl entries for AI messages
688
- if (this.config.writeJsonLog) {
689
- try {
690
- const json = JSON.parse(cleanLine);
691
- let displayMsg = cleanLine;
692
- let metadata = { ...json };
693
- // Extract cleaner text for significant AI message types
694
- if ((json.type === 'thinking' || json.type === 'thought') && (json.text || json.thought)) {
695
- displayMsg = json.text || json.thought;
696
- // Clean up any double newlines at the end of deltas
697
- displayMsg = displayMsg.replace(/\n+$/, '\n');
698
- }
699
- else if (json.type === 'assistant' && json.message?.content) {
700
- displayMsg = json.message.content
701
- .filter((c) => c.type === 'text')
702
- .map((c) => c.text)
703
- .join('');
704
- }
705
- else if (json.type === 'user' && json.message?.content) {
706
- displayMsg = json.message.content
707
- .filter((c) => c.type === 'text')
708
- .map((c) => c.text)
709
- .join('');
710
- }
711
- else if (json.type === 'tool_call' && json.subtype === 'started') {
712
- const toolName = Object.keys(json.tool_call)[0] || 'unknown';
713
- const args = json.tool_call[toolName]?.args || {};
714
- displayMsg = `🔧 CALL: ${toolName}(${JSON.stringify(args)})`;
715
- }
716
- else if (json.type === 'tool_call' && json.subtype === 'completed') {
717
- const toolName = Object.keys(json.tool_call)[0] || 'unknown';
718
- displayMsg = `📄 RESL: ${toolName}`;
719
- }
720
- else if (json.type === 'result') {
721
- displayMsg = json.result || 'Task completed';
722
- }
723
- this.writeJsonEntry({
724
- timestamp: new Date().toISOString(),
725
- level: 'stdout',
726
- lane: this.session.laneName,
727
- task: this.session.taskName,
728
- message: displayMsg.substring(0, 2000), // Larger limit for AI text
729
- metadata,
730
- });
731
- continue; // Already logged this JSON line
732
- }
733
- catch {
734
- // Not valid JSON or error, fall through to regular logging
295
+ // Try to parse as JSON (cursor-agent output)
296
+ if (trimmed.startsWith('{')) {
297
+ try {
298
+ const json = JSON.parse(trimmed);
299
+ const msg = this.parseJsonToMessage(json);
300
+ if (msg) {
301
+ this.writeReadableMessage(msg);
302
+ continue;
735
303
  }
736
304
  }
737
- }
738
- else {
739
- // Parse standard text logs into ParsedMessage
740
- // Format: [HH:MM:SS] [LANE] ℹ️ INFO message
741
- const textLogRegex = /^\[(\d{2}:\d{2}:\d{2})\]\s+\[(.*?)\]\s+(.*?)\s+(INFO|WARN|ERROR|SUCCESS|DONE|PROGRESS)\s+(.*)/;
742
- const match = cleanLine.match(textLogRegex);
743
- if (match && this.onParsedMessage) {
744
- const [, time, , emoji, level, content] = match;
745
- // Convert HH:MM:SS to a timestamp for today
746
- const [h, m, s] = time.split(':').map(Number);
747
- const timestamp = new Date().setHours(h, m, s, 0);
748
- this.onParsedMessage({
749
- type: level.toLowerCase().replace('done', 'result').replace('success', 'result'),
750
- role: 'system',
751
- content: `${emoji} ${content}`,
752
- timestamp,
753
- });
305
+ catch {
306
+ // Not valid JSON, fall through
754
307
  }
755
308
  }
756
- // Also include significant info/status lines in readable log (compact)
757
- if (this.readableLogFd !== null) {
758
- // Look for log lines: [ISO_DATE] [LEVEL] ...
759
- if (!this.isNoiseLog(cleanLine) && /\[\d{4}-\d{2}-\d{2}T/.test(cleanLine)) {
760
- try {
761
- // Check if it has a level marker
762
- if (/\[(INFO|WARN|ERROR|SUCCESS|DEBUG)\]/.test(cleanLine)) {
763
- // Special formatting for summary
764
- if (cleanLine.includes('Final Workspace Summary')) {
765
- const tsMatch = cleanLine.match(/\[(\d{4}-\d{2}-\d{2}T[^\]]+)\]/);
766
- const ts = tsMatch ? tsMatch[1] : new Date().toISOString();
767
- fs.writeSync(this.readableLogFd, `[${ts}] 📊 SUMMARY: ${cleanLine.split(']').slice(2).join(']').trim()}\n`);
768
- }
769
- else {
770
- fs.writeSync(this.readableLogFd, `${cleanLine}\n`);
771
- }
772
- }
773
- }
774
- catch { }
309
+ // Non-JSON line - write as-is with short timestamp and lane-task label
310
+ const cleanLine = stripAnsi(trimmed);
311
+ if (cleanLine && !this.isNoiseLog(cleanLine)) {
312
+ const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
313
+ const label = this.getLaneTaskLabel();
314
+ if (hasTimestamp) {
315
+ // If already has timestamp, just ensure label is present
316
+ const formatted = cleanLine.includes(`[${label}]`)
317
+ ? cleanLine
318
+ : cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}]`);
319
+ this.writeToReadableLog(`${formatted}\n`);
320
+ }
321
+ else {
322
+ const ts = this.getShortTime();
323
+ this.writeToReadableLog(`[${ts}] [${label}] ${cleanLine}\n`);
775
324
  }
776
- }
777
- // Write regular non-JSON lines to terminal.jsonl
778
- if (this.config.writeJsonLog && !this.isNoiseLog(cleanLine)) {
779
- this.writeJsonEntry({
780
- timestamp: new Date().toISOString(),
781
- level: 'stdout',
782
- lane: this.session.laneName,
783
- task: this.session.taskName,
784
- message: cleanLine.substring(0, 1000),
785
- raw: this.config.keepRawLogs ? undefined : line.substring(0, 1000),
786
- });
787
325
  }
788
326
  }
789
327
  }
790
328
  /**
791
- * Parse streaming JSON data for readable log - legacy, integrated into writeStdout
329
+ * Parse cursor-agent JSON output to ParsedMessage
792
330
  */
793
- parseStreamingData(text) {
794
- // Legacy method, no longer used but kept for internal references if any
331
+ parseJsonToMessage(json) {
332
+ const type = json.type;
333
+ const timestamp = json.timestamp_ms || Date.now();
334
+ switch (type) {
335
+ case 'system':
336
+ return {
337
+ type: 'system',
338
+ role: 'system',
339
+ content: `Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
340
+ timestamp,
341
+ };
342
+ case 'user':
343
+ if (json.message?.content) {
344
+ const textContent = json.message.content
345
+ .filter((c) => c.type === 'text')
346
+ .map((c) => c.text)
347
+ .join('');
348
+ return {
349
+ type: 'user',
350
+ role: 'user',
351
+ content: textContent,
352
+ timestamp,
353
+ };
354
+ }
355
+ return null;
356
+ case 'assistant':
357
+ if (json.message?.content) {
358
+ const textContent = json.message.content
359
+ .filter((c) => c.type === 'text')
360
+ .map((c) => c.text)
361
+ .join('');
362
+ return {
363
+ type: 'assistant',
364
+ role: 'assistant',
365
+ content: textContent,
366
+ timestamp,
367
+ };
368
+ }
369
+ return null;
370
+ case 'tool_call':
371
+ if (json.subtype === 'started' && json.tool_call) {
372
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
373
+ const toolArgs = json.tool_call[toolName]?.args || {};
374
+ return {
375
+ type: 'tool',
376
+ role: 'tool',
377
+ content: `[Tool: ${toolName}] ${JSON.stringify(toolArgs)}`,
378
+ timestamp,
379
+ metadata: { callId: json.call_id, toolName },
380
+ };
381
+ }
382
+ else if (json.subtype === 'completed' && json.tool_call) {
383
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
384
+ return {
385
+ type: 'tool_result',
386
+ role: 'tool',
387
+ content: `[Tool Result: ${toolName}]`,
388
+ timestamp,
389
+ metadata: { callId: json.call_id, toolName },
390
+ };
391
+ }
392
+ return null;
393
+ case 'result':
394
+ return {
395
+ type: 'result',
396
+ role: 'assistant',
397
+ content: json.result || '',
398
+ timestamp,
399
+ metadata: {
400
+ duration_ms: json.duration_ms,
401
+ is_error: json.is_error,
402
+ },
403
+ };
404
+ case 'thinking':
405
+ if (json.text) {
406
+ return {
407
+ type: 'thinking',
408
+ role: 'assistant',
409
+ content: json.text,
410
+ timestamp,
411
+ };
412
+ }
413
+ return null;
414
+ default:
415
+ return null;
416
+ }
795
417
  }
796
418
  /**
797
419
  * Write stderr data
798
420
  */
799
421
  writeStderr(data) {
800
422
  const text = data.toString();
801
- // Write absolute raw log
802
- if (this.config.keepAbsoluteRawLogs) {
803
- this.writeToAbsoluteRawLog(data);
804
- }
805
423
  // Write raw log
806
- if (this.config.keepRawLogs) {
807
- this.writeToRawLog(text);
808
- }
809
- // Process through transform for clean log
810
- if (this.cleanTransform) {
811
- this.cleanTransform.write(data);
812
- }
813
- // Also include error lines in readable log (compact)
814
- if (this.readableLogFd !== null) {
815
- const lines = text.split('\n');
816
- for (const line of lines) {
817
- const cleanLine = stripAnsi(line).trim();
818
- if (cleanLine && !this.isNoiseLog(cleanLine)) {
819
- try {
820
- const ts = new Date().toISOString();
821
- fs.writeSync(this.readableLogFd, `[${ts}] STDERR: ${cleanLine}\n`);
822
- }
823
- catch { }
424
+ this.writeToRawLog(data);
425
+ // Write to readable log with error prefix
426
+ const lines = text.split('\n');
427
+ for (const line of lines) {
428
+ const cleanLine = stripAnsi(line).trim();
429
+ if (cleanLine && !this.isNoiseLog(cleanLine)) {
430
+ const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
431
+ const label = this.getLaneTaskLabel();
432
+ if (hasTimestamp) {
433
+ const formatted = cleanLine.includes(`[${label}]`)
434
+ ? cleanLine
435
+ : cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}] ❌ ERR`);
436
+ this.writeToReadableLog(`${formatted}\n`);
437
+ }
438
+ else {
439
+ const ts = this.getShortTime();
440
+ this.writeToReadableLog(`[${ts}] [${label}] ❌ ERR ${cleanLine}\n`);
824
441
  }
825
- }
826
- }
827
- // Write JSON entry
828
- if (this.config.writeJsonLog) {
829
- const cleanText = stripAnsi(text).trim();
830
- if (cleanText) {
831
- this.writeJsonEntry({
832
- timestamp: new Date().toISOString(),
833
- level: 'stderr',
834
- lane: this.session.laneName,
835
- task: this.session.taskName,
836
- message: cleanText.substring(0, 1000),
837
- });
838
442
  }
839
443
  }
840
444
  }
@@ -842,35 +446,12 @@ class EnhancedLogManager {
842
446
  * Write a custom log entry
843
447
  */
844
448
  log(level, message, metadata) {
845
- const ts = formatTimestamp(this.config.timestampFormat, this.session.startTime);
846
- const prefix = level.toUpperCase().padEnd(8);
847
- const line = `[${ts}] [${prefix}] ${message}\n`;
848
- this.writeToCleanLog(line);
849
- if (this.config.keepRawLogs) {
850
- this.writeToRawLog(line);
851
- }
852
- if (this.config.keepAbsoluteRawLogs) {
853
- this.writeToAbsoluteRawLog(line);
854
- }
855
- // Write to readable log (compact)
856
- if (this.readableLogFd !== null) {
857
- const typeLabel = level === 'error' ? '❌ ERROR' : level === 'info' ? 'ℹ️ INFO' : '🔍 DEBUG';
858
- const formatted = `${new Date().toISOString()} ${typeLabel}: ${message}\n`;
859
- try {
860
- fs.writeSync(this.readableLogFd, formatted);
861
- }
862
- catch { }
863
- }
864
- if (this.config.writeJsonLog) {
865
- this.writeJsonEntry({
866
- timestamp: new Date().toISOString(),
867
- level,
868
- lane: this.session.laneName,
869
- task: this.session.taskName,
870
- message,
871
- metadata,
872
- });
873
- }
449
+ const ts = this.getShortTime();
450
+ const label = this.getLaneTaskLabel();
451
+ const emoji = level === 'error' ? '❌' : level === 'info' ? 'ℹ️' : '🔍';
452
+ const line = `[${ts}] [${label}] ${emoji} ${level.toUpperCase()} ${message}\n`;
453
+ this.writeToRawLog(line);
454
+ this.writeToReadableLog(line);
874
455
  }
875
456
  /**
876
457
  * Add a section marker
@@ -878,58 +459,34 @@ class EnhancedLogManager {
878
459
  section(title) {
879
460
  const divider = '═'.repeat(78);
880
461
  const line = `\n${divider}\n ${title}\n${divider}\n`;
881
- this.writeToCleanLog(line);
882
- if (this.config.keepRawLogs) {
883
- this.writeToRawLog(line);
884
- }
885
- if (this.config.keepAbsoluteRawLogs) {
886
- this.writeToAbsoluteRawLog(line);
887
- }
888
- // Write to readable log (compact)
889
- if (this.readableLogFd !== null) {
890
- const ts = new Date().toISOString();
891
- const formatted = `[${ts}] ━━━ ${title} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
892
- try {
893
- fs.writeSync(this.readableLogFd, formatted);
894
- }
895
- catch { }
896
- }
462
+ this.writeToRawLog(line);
463
+ this.writeToReadableLog(line);
897
464
  }
898
465
  /**
899
466
  * Update task context
900
467
  */
901
- setTask(taskName, model) {
468
+ setTask(taskName, model, taskIndex) {
902
469
  this.session.taskName = taskName;
903
470
  if (model) {
904
471
  this.session.model = model;
905
472
  }
906
- this.section(`Task: ${taskName}${model ? ` (Model: ${model})` : ''}`);
907
- if (this.config.writeJsonLog) {
908
- this.writeJsonEntry({
909
- timestamp: new Date().toISOString(),
910
- level: 'info',
911
- source: 'system',
912
- lane: this.session.laneName,
913
- task: taskName,
914
- message: `Task started: ${taskName}`,
915
- metadata: { model },
916
- });
473
+ if (taskIndex !== undefined) {
474
+ this.session.taskIndex = taskIndex;
917
475
  }
476
+ this.section(`Task: ${taskName}${model ? ` (Model: ${model})` : ''}`);
918
477
  }
919
478
  /**
920
- * Check if a log line is noise (progress bars, spinners, etc.)
479
+ * Check if a log line is noise
921
480
  */
922
481
  isNoiseLog(text) {
923
- // Skip empty or whitespace-only
924
482
  if (!text.trim())
925
483
  return true;
926
- // Skip common progress/spinner patterns
927
484
  const noisePatterns = [
928
- /^[\s│├└─┌┐┘┴┬┤]+$/, // Box drawing only (removed duplicate ├)
929
- /^[.\s]+$/, // Dots only
930
- /^[=>\s-]+$/, // Progress bar characters
931
- /^\d+%$/, // Percentage only
932
- /^⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/, // Spinner characters
485
+ /^[\s│├└─┌┐┘┴┬┤]+$/,
486
+ /^[.\s]+$/,
487
+ /^[=>\s-]+$/,
488
+ /^\d+%$/,
489
+ /^⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/,
933
490
  ];
934
491
  return noisePatterns.some(p => p.test(text));
935
492
  }
@@ -938,10 +495,8 @@ class EnhancedLogManager {
938
495
  */
939
496
  getLogPaths() {
940
497
  return {
941
- clean: this.cleanLogPath,
942
- raw: this.config.keepRawLogs ? this.rawLogPath : undefined,
943
- absoluteRaw: this.config.keepAbsoluteRawLogs ? this.absoluteRawLogPath : undefined,
944
- json: this.config.writeJsonLog ? this.jsonLogPath : undefined,
498
+ clean: this.readableLogPath, // For backward compatibility
499
+ raw: this.rawLogPath,
945
500
  readable: this.readableLogPath,
946
501
  };
947
502
  }
@@ -949,26 +504,13 @@ class EnhancedLogManager {
949
504
  * Create file descriptors for process stdio redirection
950
505
  */
951
506
  getFileDescriptors() {
952
- // For process spawning, use the raw log fd if available, otherwise clean
953
- const fd = this.rawLogFd !== null ? this.rawLogFd : this.cleanLogFd;
507
+ const fd = this.rawLogFd;
954
508
  return { stdout: fd, stderr: fd };
955
509
  }
956
510
  /**
957
- * Close all log files and ensure all data is flushed to disk
511
+ * Close all log files
958
512
  */
959
513
  close() {
960
- // Flush transform stream
961
- if (this.cleanTransform) {
962
- this.cleanTransform.end();
963
- }
964
- // Flush streaming parser
965
- if (this.streamingParser) {
966
- // Parse any remaining buffered data
967
- if (this.lineBuffer.trim()) {
968
- this.streamingParser.parseLine(this.lineBuffer);
969
- }
970
- this.streamingParser.flush();
971
- }
972
514
  // Write session end marker
973
515
  const endMarker = `
974
516
  ╔══════════════════════════════════════════════════════════════════════════════╗
@@ -977,15 +519,6 @@ class EnhancedLogManager {
977
519
  ╚══════════════════════════════════════════════════════════════════════════════╝
978
520
 
979
521
  `;
980
- if (this.cleanLogFd !== null) {
981
- try {
982
- fs.writeSync(this.cleanLogFd, endMarker);
983
- fs.fsyncSync(this.cleanLogFd);
984
- fs.closeSync(this.cleanLogFd);
985
- }
986
- catch { }
987
- this.cleanLogFd = null;
988
- }
989
522
  if (this.rawLogFd !== null) {
990
523
  try {
991
524
  fs.writeSync(this.rawLogFd, endMarker);
@@ -995,34 +528,6 @@ class EnhancedLogManager {
995
528
  catch { }
996
529
  this.rawLogFd = null;
997
530
  }
998
- if (this.absoluteRawLogFd !== null) {
999
- try {
1000
- fs.fsyncSync(this.absoluteRawLogFd);
1001
- fs.closeSync(this.absoluteRawLogFd);
1002
- }
1003
- catch { }
1004
- this.absoluteRawLogFd = null;
1005
- }
1006
- if (this.jsonLogFd !== null) {
1007
- try {
1008
- this.writeJsonEntry({
1009
- timestamp: new Date().toISOString(),
1010
- level: 'session',
1011
- source: 'system',
1012
- lane: this.session.laneName,
1013
- message: 'Session ended',
1014
- metadata: {
1015
- sessionId: this.session.id,
1016
- duration: Date.now() - this.session.startTime,
1017
- },
1018
- });
1019
- fs.fsyncSync(this.jsonLogFd);
1020
- fs.closeSync(this.jsonLogFd);
1021
- }
1022
- catch { }
1023
- this.jsonLogFd = null;
1024
- }
1025
- // Close readable log
1026
531
  if (this.readableLogFd !== null) {
1027
532
  try {
1028
533
  fs.writeSync(this.readableLogFd, endMarker);
@@ -1034,21 +539,19 @@ class EnhancedLogManager {
1034
539
  }
1035
540
  }
1036
541
  /**
1037
- * Extract the last error message from the clean log file
542
+ * Extract the last error message from the log
1038
543
  */
1039
544
  getLastError() {
1040
545
  try {
1041
- if (!fs.existsSync(this.cleanLogPath))
546
+ if (!fs.existsSync(this.readableLogPath))
1042
547
  return null;
1043
- const content = fs.readFileSync(this.cleanLogPath, 'utf8');
1044
- // Look for lines containing error markers
1045
- const lines = content.split('\n').filter(l => l.includes('[ERROR]') ||
1046
- l.includes('❌') ||
548
+ const content = fs.readFileSync(this.readableLogPath, 'utf8');
549
+ const lines = content.split('\n').filter(l => l.includes('❌') ||
550
+ l.includes('[ERROR]') ||
1047
551
  l.includes('error:') ||
1048
552
  l.includes('Fatal') ||
1049
553
  l.includes('fail'));
1050
554
  if (lines.length === 0) {
1051
- // Fallback to last 5 lines if no specific error marker found
1052
555
  const allLines = content.split('\n').filter(l => l.trim());
1053
556
  return allLines.slice(-5).join('\n');
1054
557
  }
@@ -1078,154 +581,45 @@ exports.EnhancedLogManager = EnhancedLogManager;
1078
581
  /**
1079
582
  * Create a log manager for a lane
1080
583
  */
1081
- function createLogManager(laneRunDir, laneName, config, onParsedMessage) {
584
+ function createLogManager(laneRunDir, laneName, config, onParsedMessage, laneIndex) {
1082
585
  const session = {
1083
586
  id: `${laneName}-${Date.now().toString(36)}`,
1084
587
  laneName,
1085
588
  startTime: Date.now(),
589
+ laneIndex: laneIndex ?? 0,
590
+ taskIndex: 0,
1086
591
  };
1087
592
  return new EnhancedLogManager(laneRunDir, session, config, onParsedMessage);
1088
593
  }
1089
594
  /**
1090
- * Read and parse JSON log file
595
+ * Read and parse JSON log file (legacy compatibility - returns empty array)
1091
596
  */
1092
597
  function readJsonLog(logPath) {
1093
- if (!fs.existsSync(logPath)) {
1094
- return [];
1095
- }
1096
- try {
1097
- const content = fs.readFileSync(logPath, 'utf8');
1098
- return content
1099
- .split('\n')
1100
- .filter(line => line.trim())
1101
- .map(line => {
1102
- try {
1103
- return JSON.parse(line);
1104
- }
1105
- catch {
1106
- return null;
1107
- }
1108
- })
1109
- .filter((entry) => entry !== null);
1110
- }
1111
- catch {
1112
- return [];
1113
- }
598
+ return [];
1114
599
  }
1115
600
  /**
1116
- * Export logs to various formats
601
+ * Export logs (legacy compatibility)
1117
602
  */
1118
603
  function exportLogs(laneRunDir, format, outputPath) {
1119
- const cleanLogPath = (0, path_1.safeJoin)(laneRunDir, 'terminal.log');
1120
- const jsonLogPath = (0, path_1.safeJoin)(laneRunDir, 'terminal.jsonl');
604
+ const readableLogPath = (0, path_1.safeJoin)(laneRunDir, 'terminal-readable.log');
1121
605
  let output = '';
1122
- switch (format) {
1123
- case 'text':
1124
- if (fs.existsSync(cleanLogPath)) {
1125
- output = fs.readFileSync(cleanLogPath, 'utf8');
1126
- }
1127
- break;
1128
- case 'json':
1129
- const entries = readJsonLog(jsonLogPath);
1130
- output = JSON.stringify(entries, null, 2);
1131
- break;
1132
- case 'markdown':
1133
- output = exportToMarkdown(jsonLogPath, cleanLogPath);
1134
- break;
1135
- case 'html':
1136
- output = exportToHtml(jsonLogPath, cleanLogPath);
1137
- break;
606
+ if (fs.existsSync(readableLogPath)) {
607
+ output = fs.readFileSync(readableLogPath, 'utf8');
1138
608
  }
1139
609
  if (outputPath) {
1140
610
  fs.writeFileSync(outputPath, output, 'utf8');
1141
611
  }
1142
612
  return output;
1143
613
  }
1144
- /**
1145
- * Export logs to Markdown format
1146
- */
1147
- function exportToMarkdown(jsonLogPath, cleanLogPath) {
1148
- const entries = readJsonLog(jsonLogPath);
1149
- let md = '# CursorFlow Session Log\n\n';
1150
- // Find session info
1151
- const sessionStart = entries.find(e => e.level === 'session' && e.message === 'Session started');
1152
- if (sessionStart?.metadata) {
1153
- md += '## Session Info\n\n';
1154
- md += `- **Session ID**: ${sessionStart.metadata.sessionId}\n`;
1155
- md += `- **Lane**: ${sessionStart.lane}\n`;
1156
- md += `- **Model**: ${sessionStart.metadata.model || 'default'}\n`;
1157
- md += `- **Started**: ${sessionStart.timestamp}\n\n`;
1158
- }
1159
- md += '## Log Entries\n\n';
1160
- // Group by task
1161
- const byTask = new Map();
1162
- for (const entry of entries) {
1163
- const task = entry.task || '(no task)';
1164
- if (!byTask.has(task)) {
1165
- byTask.set(task, []);
1166
- }
1167
- byTask.get(task).push(entry);
1168
- }
1169
- for (const [task, taskEntries] of byTask) {
1170
- md += `### Task: ${task}\n\n`;
1171
- md += '```\n';
1172
- for (const entry of taskEntries) {
1173
- const level = entry.level || 'info';
1174
- const message = entry.message || '';
1175
- if (level !== 'session') {
1176
- md += `[${entry.timestamp}] [${level.toUpperCase()}] ${message}\n`;
1177
- }
1178
- }
1179
- md += '```\n\n';
1180
- }
1181
- return md;
1182
- }
1183
- /**
1184
- * Export logs to HTML format
1185
- */
1186
- function exportToHtml(jsonLogPath, cleanLogPath) {
1187
- const entries = readJsonLog(jsonLogPath);
1188
- let html = `<!DOCTYPE html>
1189
- <html>
1190
- <head>
1191
- <title>CursorFlow Session Log</title>
1192
- <style>
1193
- body { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; margin: 20px; background: #1e1e1e; color: #d4d4d4; }
1194
- h1, h2 { color: #569cd6; }
1195
- .entry { padding: 4px 8px; margin: 2px 0; border-radius: 4px; }
1196
- .entry.stdout { background: #252526; }
1197
- .entry.stderr { background: #3c1f1f; color: #f48771; }
1198
- .entry.info { background: #1e3a5f; color: #9cdcfe; }
1199
- .entry.error { background: #5f1e1e; color: #f48771; }
1200
- .entry.session { background: #1e4620; color: #6a9955; }
1201
- .timestamp { color: #808080; font-size: 0.9em; }
1202
- .level { font-weight: bold; text-transform: uppercase; }
1203
- .task { color: #dcdcaa; }
1204
- pre { white-space: pre-wrap; word-wrap: break-word; }
1205
- </style>
1206
- </head>
1207
- <body>
1208
- <h1>CursorFlow Session Log</h1>
1209
- `;
1210
- for (const entry of entries) {
1211
- const level = entry.level || 'info';
1212
- const message = entry.message || '';
1213
- html += ` <div class="entry ${level}">
1214
- <span class="timestamp">${entry.timestamp}</span>
1215
- <span class="level">[${level}]</span>
1216
- ${entry.task ? `<span class="task">[${entry.task}]</span>` : ''}
1217
- <pre>${escapeHtml(message)}</pre>
1218
- </div>\n`;
1219
- }
1220
- html += '</body></html>';
1221
- return html;
614
+ // Legacy exports for backward compatibility
615
+ class StreamingMessageParser {
616
+ constructor(onMessage) { }
617
+ parseLine(line) { }
618
+ flush() { }
1222
619
  }
1223
- function escapeHtml(text) {
1224
- return text
1225
- .replace(/&/g, '&amp;')
1226
- .replace(/</g, '&lt;')
1227
- .replace(/>/g, '&gt;')
1228
- .replace(/"/g, '&quot;')
1229
- .replace(/'/g, '&#039;');
620
+ exports.StreamingMessageParser = StreamingMessageParser;
621
+ class CleanLogTransform {
622
+ constructor(config, session) { }
1230
623
  }
624
+ exports.CleanLogTransform = CleanLogTransform;
1231
625
  //# sourceMappingURL=enhanced-logger.js.map