@litmers/cursorflow-orchestrator 0.1.31 → 0.1.36

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 (150) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +182 -59
  3. package/commands/cursorflow-add.md +159 -0
  4. package/commands/cursorflow-doctor.md +45 -23
  5. package/commands/cursorflow-monitor.md +23 -2
  6. package/commands/cursorflow-new.md +87 -0
  7. package/commands/cursorflow-run.md +60 -111
  8. package/dist/cli/add.d.ts +7 -0
  9. package/dist/cli/add.js +377 -0
  10. package/dist/cli/add.js.map +1 -0
  11. package/dist/cli/clean.js +1 -0
  12. package/dist/cli/clean.js.map +1 -1
  13. package/dist/cli/config.d.ts +7 -0
  14. package/dist/cli/config.js +181 -0
  15. package/dist/cli/config.js.map +1 -0
  16. package/dist/cli/doctor.js +47 -4
  17. package/dist/cli/doctor.js.map +1 -1
  18. package/dist/cli/index.js +34 -30
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/logs.js +17 -34
  21. package/dist/cli/logs.js.map +1 -1
  22. package/dist/cli/monitor.js +62 -65
  23. package/dist/cli/monitor.js.map +1 -1
  24. package/dist/cli/new.d.ts +7 -0
  25. package/dist/cli/new.js +232 -0
  26. package/dist/cli/new.js.map +1 -0
  27. package/dist/cli/prepare.js +95 -193
  28. package/dist/cli/prepare.js.map +1 -1
  29. package/dist/cli/resume.js +57 -68
  30. package/dist/cli/resume.js.map +1 -1
  31. package/dist/cli/run.js +60 -30
  32. package/dist/cli/run.js.map +1 -1
  33. package/dist/cli/stop.js +6 -0
  34. package/dist/cli/stop.js.map +1 -1
  35. package/dist/cli/tasks.d.ts +5 -3
  36. package/dist/cli/tasks.js +181 -29
  37. package/dist/cli/tasks.js.map +1 -1
  38. package/dist/core/failure-policy.d.ts +9 -0
  39. package/dist/core/failure-policy.js +9 -0
  40. package/dist/core/failure-policy.js.map +1 -1
  41. package/dist/core/orchestrator.d.ts +20 -6
  42. package/dist/core/orchestrator.js +215 -334
  43. package/dist/core/orchestrator.js.map +1 -1
  44. package/dist/core/runner/agent.d.ts +27 -0
  45. package/dist/core/runner/agent.js +294 -0
  46. package/dist/core/runner/agent.js.map +1 -0
  47. package/dist/core/runner/index.d.ts +5 -0
  48. package/dist/core/runner/index.js +22 -0
  49. package/dist/core/runner/index.js.map +1 -0
  50. package/dist/core/runner/pipeline.d.ts +9 -0
  51. package/dist/core/runner/pipeline.js +539 -0
  52. package/dist/core/runner/pipeline.js.map +1 -0
  53. package/dist/core/runner/prompt.d.ts +25 -0
  54. package/dist/core/runner/prompt.js +175 -0
  55. package/dist/core/runner/prompt.js.map +1 -0
  56. package/dist/core/runner/task.d.ts +26 -0
  57. package/dist/core/runner/task.js +283 -0
  58. package/dist/core/runner/task.js.map +1 -0
  59. package/dist/core/runner/utils.d.ts +37 -0
  60. package/dist/core/runner/utils.js +161 -0
  61. package/dist/core/runner/utils.js.map +1 -0
  62. package/dist/core/runner.d.ts +2 -96
  63. package/dist/core/runner.js +11 -1136
  64. package/dist/core/runner.js.map +1 -1
  65. package/dist/core/stall-detection.d.ts +326 -0
  66. package/dist/core/stall-detection.js +781 -0
  67. package/dist/core/stall-detection.js.map +1 -0
  68. package/dist/services/logging/console.js +2 -1
  69. package/dist/services/logging/console.js.map +1 -1
  70. package/dist/types/config.d.ts +6 -6
  71. package/dist/types/flow.d.ts +84 -0
  72. package/dist/types/flow.js +10 -0
  73. package/dist/types/flow.js.map +1 -0
  74. package/dist/types/index.d.ts +1 -0
  75. package/dist/types/index.js +3 -3
  76. package/dist/types/index.js.map +1 -1
  77. package/dist/types/lane.d.ts +0 -2
  78. package/dist/types/logging.d.ts +5 -1
  79. package/dist/types/task.d.ts +7 -11
  80. package/dist/utils/config.d.ts +5 -1
  81. package/dist/utils/config.js +15 -16
  82. package/dist/utils/config.js.map +1 -1
  83. package/dist/utils/dependency.d.ts +36 -1
  84. package/dist/utils/dependency.js +256 -1
  85. package/dist/utils/dependency.js.map +1 -1
  86. package/dist/utils/doctor.js +40 -8
  87. package/dist/utils/doctor.js.map +1 -1
  88. package/dist/utils/enhanced-logger.d.ts +45 -82
  89. package/dist/utils/enhanced-logger.js +239 -844
  90. package/dist/utils/enhanced-logger.js.map +1 -1
  91. package/dist/utils/flow.d.ts +9 -0
  92. package/dist/utils/flow.js +73 -0
  93. package/dist/utils/flow.js.map +1 -0
  94. package/dist/utils/git.d.ts +29 -0
  95. package/dist/utils/git.js +115 -5
  96. package/dist/utils/git.js.map +1 -1
  97. package/dist/utils/state.js +0 -2
  98. package/dist/utils/state.js.map +1 -1
  99. package/dist/utils/task-service.d.ts +2 -2
  100. package/dist/utils/task-service.js +40 -31
  101. package/dist/utils/task-service.js.map +1 -1
  102. package/package.json +4 -3
  103. package/src/cli/add.ts +397 -0
  104. package/src/cli/clean.ts +1 -0
  105. package/src/cli/config.ts +177 -0
  106. package/src/cli/doctor.ts +48 -4
  107. package/src/cli/index.ts +36 -32
  108. package/src/cli/logs.ts +20 -33
  109. package/src/cli/monitor.ts +70 -75
  110. package/src/cli/new.ts +235 -0
  111. package/src/cli/prepare.ts +98 -205
  112. package/src/cli/resume.ts +61 -76
  113. package/src/cli/run.ts +333 -306
  114. package/src/cli/stop.ts +8 -0
  115. package/src/cli/tasks.ts +200 -21
  116. package/src/core/failure-policy.ts +9 -0
  117. package/src/core/orchestrator.ts +279 -379
  118. package/src/core/runner/agent.ts +314 -0
  119. package/src/core/runner/index.ts +6 -0
  120. package/src/core/runner/pipeline.ts +567 -0
  121. package/src/core/runner/prompt.ts +174 -0
  122. package/src/core/runner/task.ts +320 -0
  123. package/src/core/runner/utils.ts +142 -0
  124. package/src/core/runner.ts +8 -1347
  125. package/src/core/stall-detection.ts +936 -0
  126. package/src/services/logging/console.ts +2 -1
  127. package/src/types/config.ts +6 -6
  128. package/src/types/flow.ts +91 -0
  129. package/src/types/index.ts +15 -3
  130. package/src/types/lane.ts +0 -2
  131. package/src/types/logging.ts +5 -1
  132. package/src/types/task.ts +7 -11
  133. package/src/utils/config.ts +16 -17
  134. package/src/utils/dependency.ts +311 -2
  135. package/src/utils/doctor.ts +36 -8
  136. package/src/utils/enhanced-logger.ts +264 -927
  137. package/src/utils/flow.ts +42 -0
  138. package/src/utils/git.ts +145 -5
  139. package/src/utils/state.ts +0 -2
  140. package/src/utils/task-service.ts +48 -40
  141. package/commands/cursorflow-review.md +0 -56
  142. package/commands/cursorflow-runs.md +0 -59
  143. package/dist/cli/runs.d.ts +0 -5
  144. package/dist/cli/runs.js +0 -214
  145. package/dist/cli/runs.js.map +0 -1
  146. package/dist/core/reviewer.d.ts +0 -66
  147. package/dist/core/reviewer.js +0 -265
  148. package/dist/core/reviewer.js.map +0 -1
  149. package/src/cli/runs.ts +0 -212
  150. 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,68 @@ 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 [L1-T2-lanename10]
145
+ */
146
+ getLaneTaskLabel() {
147
+ const laneNum = (this.session.laneIndex ?? 0) + 1;
148
+ const taskNum = (this.session.taskIndex ?? 0) + 1;
149
+ const shortLaneName = this.session.laneName.substring(0, 10);
150
+ return `L${laneNum}-T${taskNum}-${shortLaneName}`;
151
+ }
398
152
  /**
399
153
  * Initialize log files and write session headers
400
154
  */
401
155
  initLogFiles() {
402
156
  // 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
157
  if (this.config.keepRawLogs) {
158
+ this.rotateIfNeeded(this.rawLogPath);
413
159
  this.rawLogFd = fs.openSync(this.rawLogPath, 'a');
414
160
  }
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)
161
+ this.rotateIfNeeded(this.readableLogPath);
422
162
  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) {
163
+ // Get initial file size for raw log if enabled
164
+ if (this.rawLogFd !== null) {
165
+ try {
427
166
  this.rawLogSize = fs.statSync(this.rawLogPath).size;
428
167
  }
429
- if (this.config.keepAbsoluteRawLogs) {
430
- this.absoluteRawLogSize = fs.statSync(this.absoluteRawLogPath).size;
168
+ catch {
169
+ this.rawLogSize = 0;
431
170
  }
432
171
  }
433
- catch {
434
- this.cleanLogSize = 0;
435
- this.rawLogSize = 0;
436
- this.absoluteRawLogSize = 0;
437
- }
438
172
  // Write session header
439
173
  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
174
  }
522
175
  /**
523
176
  * Write session header to logs
@@ -535,29 +188,13 @@ class EnhancedLogManager {
535
188
  ╚══════════════════════════════════════════════════════════════════════════════╝
536
189
 
537
190
  `;
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
- });
191
+ this.writeToRawLog(header);
192
+ this.writeToReadableLog(header);
556
193
  }
557
194
  /**
558
195
  * Rotate log file if it exceeds max size
559
196
  */
560
- rotateIfNeeded(logPath, type) {
197
+ rotateIfNeeded(logPath) {
561
198
  if (!fs.existsSync(logPath))
562
199
  return;
563
200
  try {
@@ -594,30 +231,13 @@ class EnhancedLogManager {
594
231
  const rotatedPath = (0, path_1.safeJoin)(dir, `${base}.1${ext}`);
595
232
  fs.renameSync(logPath, rotatedPath);
596
233
  }
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
234
  /**
615
235
  * Write to raw log with size tracking
616
236
  */
617
237
  writeToRawLog(data) {
618
238
  if (this.rawLogFd === null)
619
239
  return;
620
- const buffer = Buffer.from(data);
240
+ const buffer = typeof data === 'string' ? Buffer.from(data) : data;
621
241
  fs.writeSync(this.rawLogFd, buffer);
622
242
  this.rawLogSize += buffer.length;
623
243
  // Check if rotation needed
@@ -629,212 +249,197 @@ class EnhancedLogManager {
629
249
  }
630
250
  }
631
251
  /**
632
- * Write to absolute raw log with size tracking
252
+ * Write to readable log
633
253
  */
634
- writeToAbsoluteRawLog(data) {
635
- if (this.absoluteRawLogFd === null)
254
+ writeToReadableLog(data) {
255
+ if (this.readableLogFd === null)
636
256
  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;
257
+ try {
258
+ fs.writeSync(this.readableLogFd, data);
259
+ }
260
+ catch {
261
+ // Ignore write errors
646
262
  }
647
263
  }
648
264
  /**
649
- * Write a JSON log entry
265
+ * Write a parsed message to the readable log using formatMessageForConsole style
650
266
  */
651
- writeJsonEntry(entry) {
652
- if (this.jsonLogFd === null)
653
- return;
654
- const line = JSON.stringify(entry) + '\n';
655
- fs.writeSync(this.jsonLogFd, line);
267
+ writeReadableMessage(msg) {
268
+ // Use formatMessageForConsole for consistent formatting
269
+ // Use short lane-task label like [L01-T02]
270
+ const formatted = (0, log_formatter_1.formatMessageForConsole)(msg, {
271
+ laneLabel: `[${this.getLaneTaskLabel()}]`,
272
+ includeTimestamp: false, // We'll add our own short timestamp
273
+ });
274
+ // Strip ANSI codes and add short timestamp for file output
275
+ const clean = stripAnsi(formatted);
276
+ const ts = this.getShortTime();
277
+ this.writeToReadableLog(`[${ts}] ${clean}\n`);
278
+ // Callback for console output
279
+ if (this.onParsedMessage) {
280
+ this.onParsedMessage(msg);
281
+ }
656
282
  }
657
283
  /**
658
284
  * Write stdout data
659
285
  */
660
286
  writeStdout(data) {
661
287
  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() || '';
288
+ // Write raw log (original data)
289
+ this.writeToRawLog(data);
290
+ // Parse JSON output and write to readable log
291
+ const lines = text.split('\n');
678
292
  for (const line of lines) {
679
- const cleanLine = stripAnsi(line).trim();
680
- if (!cleanLine)
293
+ const trimmed = line.trim();
294
+ if (!trimmed)
681
295
  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
296
+ // Try to parse as JSON (cursor-agent output)
297
+ if (trimmed.startsWith('{')) {
298
+ try {
299
+ const json = JSON.parse(trimmed);
300
+ const msg = this.parseJsonToMessage(json);
301
+ if (msg) {
302
+ this.writeReadableMessage(msg);
303
+ continue;
735
304
  }
736
305
  }
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
- });
306
+ catch {
307
+ // Not valid JSON, fall through
754
308
  }
755
309
  }
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 { }
310
+ // Non-JSON line - write as-is with short timestamp and lane-task label
311
+ const cleanLine = stripAnsi(trimmed);
312
+ if (cleanLine && !this.isNoiseLog(cleanLine)) {
313
+ const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
314
+ const label = this.getLaneTaskLabel();
315
+ if (hasTimestamp) {
316
+ // If already has timestamp, just ensure label is present
317
+ const formatted = cleanLine.includes(`[${label}]`)
318
+ ? cleanLine
319
+ : cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}]`);
320
+ this.writeToReadableLog(`${formatted}\n`);
321
+ }
322
+ else {
323
+ const ts = this.getShortTime();
324
+ this.writeToReadableLog(`[${ts}] [${label}] ${cleanLine}\n`);
775
325
  }
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
326
  }
788
327
  }
789
328
  }
790
329
  /**
791
- * Parse streaming JSON data for readable log - legacy, integrated into writeStdout
330
+ * Parse cursor-agent JSON output to ParsedMessage
792
331
  */
793
- parseStreamingData(text) {
794
- // Legacy method, no longer used but kept for internal references if any
332
+ parseJsonToMessage(json) {
333
+ const type = json.type;
334
+ const timestamp = json.timestamp_ms || Date.now();
335
+ switch (type) {
336
+ case 'system':
337
+ return {
338
+ type: 'system',
339
+ role: 'system',
340
+ content: `Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
341
+ timestamp,
342
+ };
343
+ case 'user':
344
+ if (json.message?.content) {
345
+ const textContent = json.message.content
346
+ .filter((c) => c.type === 'text')
347
+ .map((c) => c.text)
348
+ .join('');
349
+ return {
350
+ type: 'user',
351
+ role: 'user',
352
+ content: textContent,
353
+ timestamp,
354
+ };
355
+ }
356
+ return null;
357
+ case 'assistant':
358
+ if (json.message?.content) {
359
+ const textContent = json.message.content
360
+ .filter((c) => c.type === 'text')
361
+ .map((c) => c.text)
362
+ .join('');
363
+ return {
364
+ type: 'assistant',
365
+ role: 'assistant',
366
+ content: textContent,
367
+ timestamp,
368
+ };
369
+ }
370
+ return null;
371
+ case 'tool_call':
372
+ if (json.subtype === 'started' && json.tool_call) {
373
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
374
+ const toolArgs = json.tool_call[toolName]?.args || {};
375
+ return {
376
+ type: 'tool',
377
+ role: 'tool',
378
+ content: `[Tool: ${toolName}] ${JSON.stringify(toolArgs)}`,
379
+ timestamp,
380
+ metadata: { callId: json.call_id, toolName },
381
+ };
382
+ }
383
+ else if (json.subtype === 'completed' && json.tool_call) {
384
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
385
+ return {
386
+ type: 'tool_result',
387
+ role: 'tool',
388
+ content: `[Tool Result: ${toolName}]`,
389
+ timestamp,
390
+ metadata: { callId: json.call_id, toolName },
391
+ };
392
+ }
393
+ return null;
394
+ case 'result':
395
+ return {
396
+ type: 'result',
397
+ role: 'assistant',
398
+ content: json.result || '',
399
+ timestamp,
400
+ metadata: {
401
+ duration_ms: json.duration_ms,
402
+ is_error: json.is_error,
403
+ },
404
+ };
405
+ case 'thinking':
406
+ if (json.text) {
407
+ return {
408
+ type: 'thinking',
409
+ role: 'assistant',
410
+ content: json.text,
411
+ timestamp,
412
+ };
413
+ }
414
+ return null;
415
+ default:
416
+ return null;
417
+ }
795
418
  }
796
419
  /**
797
420
  * Write stderr data
798
421
  */
799
422
  writeStderr(data) {
800
423
  const text = data.toString();
801
- // Write absolute raw log
802
- if (this.config.keepAbsoluteRawLogs) {
803
- this.writeToAbsoluteRawLog(data);
804
- }
805
424
  // 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 { }
425
+ this.writeToRawLog(data);
426
+ // Write to readable log with error prefix
427
+ const lines = text.split('\n');
428
+ for (const line of lines) {
429
+ const cleanLine = stripAnsi(line).trim();
430
+ if (cleanLine && !this.isNoiseLog(cleanLine)) {
431
+ const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
432
+ const label = this.getLaneTaskLabel();
433
+ if (hasTimestamp) {
434
+ const formatted = cleanLine.includes(`[${label}]`)
435
+ ? cleanLine
436
+ : cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}] ❌ ERR`);
437
+ this.writeToReadableLog(`${formatted}\n`);
438
+ }
439
+ else {
440
+ const ts = this.getShortTime();
441
+ this.writeToReadableLog(`[${ts}] [${label}] ❌ ERR ${cleanLine}\n`);
824
442
  }
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
443
  }
839
444
  }
840
445
  }
@@ -842,35 +447,12 @@ class EnhancedLogManager {
842
447
  * Write a custom log entry
843
448
  */
844
449
  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
- }
450
+ const ts = this.getShortTime();
451
+ const label = this.getLaneTaskLabel();
452
+ const emoji = level === 'error' ? '❌' : level === 'info' ? 'ℹ️' : '🔍';
453
+ const line = `[${ts}] [${label}] ${emoji} ${level.toUpperCase()} ${message}\n`;
454
+ this.writeToRawLog(line);
455
+ this.writeToReadableLog(line);
874
456
  }
875
457
  /**
876
458
  * Add a section marker
@@ -878,58 +460,34 @@ class EnhancedLogManager {
878
460
  section(title) {
879
461
  const divider = '═'.repeat(78);
880
462
  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
- }
463
+ this.writeToRawLog(line);
464
+ this.writeToReadableLog(line);
897
465
  }
898
466
  /**
899
467
  * Update task context
900
468
  */
901
- setTask(taskName, model) {
469
+ setTask(taskName, model, taskIndex) {
902
470
  this.session.taskName = taskName;
903
471
  if (model) {
904
472
  this.session.model = model;
905
473
  }
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
- });
474
+ if (taskIndex !== undefined) {
475
+ this.session.taskIndex = taskIndex;
917
476
  }
477
+ this.section(`Task: ${taskName}${model ? ` (Model: ${model})` : ''}`);
918
478
  }
919
479
  /**
920
- * Check if a log line is noise (progress bars, spinners, etc.)
480
+ * Check if a log line is noise
921
481
  */
922
482
  isNoiseLog(text) {
923
- // Skip empty or whitespace-only
924
483
  if (!text.trim())
925
484
  return true;
926
- // Skip common progress/spinner patterns
927
485
  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
486
+ /^[\s│├└─┌┐┘┴┬┤]+$/,
487
+ /^[.\s]+$/,
488
+ /^[=>\s-]+$/,
489
+ /^\d+%$/,
490
+ /^⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/,
933
491
  ];
934
492
  return noisePatterns.some(p => p.test(text));
935
493
  }
@@ -938,10 +496,8 @@ class EnhancedLogManager {
938
496
  */
939
497
  getLogPaths() {
940
498
  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,
499
+ clean: this.readableLogPath, // For backward compatibility
500
+ raw: this.rawLogPath,
945
501
  readable: this.readableLogPath,
946
502
  };
947
503
  }
@@ -949,26 +505,13 @@ class EnhancedLogManager {
949
505
  * Create file descriptors for process stdio redirection
950
506
  */
951
507
  getFileDescriptors() {
952
- // For process spawning, use the raw log fd if available, otherwise clean
953
- const fd = this.rawLogFd !== null ? this.rawLogFd : this.cleanLogFd;
508
+ const fd = this.rawLogFd;
954
509
  return { stdout: fd, stderr: fd };
955
510
  }
956
511
  /**
957
- * Close all log files and ensure all data is flushed to disk
512
+ * Close all log files
958
513
  */
959
514
  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
515
  // Write session end marker
973
516
  const endMarker = `
974
517
  ╔══════════════════════════════════════════════════════════════════════════════╗
@@ -977,15 +520,6 @@ class EnhancedLogManager {
977
520
  ╚══════════════════════════════════════════════════════════════════════════════╝
978
521
 
979
522
  `;
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
523
  if (this.rawLogFd !== null) {
990
524
  try {
991
525
  fs.writeSync(this.rawLogFd, endMarker);
@@ -995,34 +529,6 @@ class EnhancedLogManager {
995
529
  catch { }
996
530
  this.rawLogFd = null;
997
531
  }
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
532
  if (this.readableLogFd !== null) {
1027
533
  try {
1028
534
  fs.writeSync(this.readableLogFd, endMarker);
@@ -1034,21 +540,19 @@ class EnhancedLogManager {
1034
540
  }
1035
541
  }
1036
542
  /**
1037
- * Extract the last error message from the clean log file
543
+ * Extract the last error message from the log
1038
544
  */
1039
545
  getLastError() {
1040
546
  try {
1041
- if (!fs.existsSync(this.cleanLogPath))
547
+ if (!fs.existsSync(this.readableLogPath))
1042
548
  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('❌') ||
549
+ const content = fs.readFileSync(this.readableLogPath, 'utf8');
550
+ const lines = content.split('\n').filter(l => l.includes('❌') ||
551
+ l.includes('[ERROR]') ||
1047
552
  l.includes('error:') ||
1048
553
  l.includes('Fatal') ||
1049
554
  l.includes('fail'));
1050
555
  if (lines.length === 0) {
1051
- // Fallback to last 5 lines if no specific error marker found
1052
556
  const allLines = content.split('\n').filter(l => l.trim());
1053
557
  return allLines.slice(-5).join('\n');
1054
558
  }
@@ -1078,154 +582,45 @@ exports.EnhancedLogManager = EnhancedLogManager;
1078
582
  /**
1079
583
  * Create a log manager for a lane
1080
584
  */
1081
- function createLogManager(laneRunDir, laneName, config, onParsedMessage) {
585
+ function createLogManager(laneRunDir, laneName, config, onParsedMessage, laneIndex) {
1082
586
  const session = {
1083
587
  id: `${laneName}-${Date.now().toString(36)}`,
1084
588
  laneName,
1085
589
  startTime: Date.now(),
590
+ laneIndex: laneIndex ?? 0,
591
+ taskIndex: 0,
1086
592
  };
1087
593
  return new EnhancedLogManager(laneRunDir, session, config, onParsedMessage);
1088
594
  }
1089
595
  /**
1090
- * Read and parse JSON log file
596
+ * Read and parse JSON log file (legacy compatibility - returns empty array)
1091
597
  */
1092
598
  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
- }
599
+ return [];
1114
600
  }
1115
601
  /**
1116
- * Export logs to various formats
602
+ * Export logs (legacy compatibility)
1117
603
  */
1118
604
  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');
605
+ const readableLogPath = (0, path_1.safeJoin)(laneRunDir, 'terminal-readable.log');
1121
606
  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;
607
+ if (fs.existsSync(readableLogPath)) {
608
+ output = fs.readFileSync(readableLogPath, 'utf8');
1138
609
  }
1139
610
  if (outputPath) {
1140
611
  fs.writeFileSync(outputPath, output, 'utf8');
1141
612
  }
1142
613
  return output;
1143
614
  }
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;
615
+ // Legacy exports for backward compatibility
616
+ class StreamingMessageParser {
617
+ constructor(onMessage) { }
618
+ parseLine(line) { }
619
+ flush() { }
1222
620
  }
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;');
621
+ exports.StreamingMessageParser = StreamingMessageParser;
622
+ class CleanLogTransform {
623
+ constructor(config, session) { }
1230
624
  }
625
+ exports.CleanLogTransform = CleanLogTransform;
1231
626
  //# sourceMappingURL=enhanced-logger.js.map