@litmers/cursorflow-orchestrator 0.2.5 → 0.2.7

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 (76) hide show
  1. package/CHANGELOG.md +29 -20
  2. package/README.md +13 -8
  3. package/dist/cli/complete.js +22 -5
  4. package/dist/cli/complete.js.map +1 -1
  5. package/dist/cli/index.js +2 -0
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/logs.js +61 -51
  8. package/dist/cli/logs.js.map +1 -1
  9. package/dist/cli/monitor.js +45 -56
  10. package/dist/cli/monitor.js.map +1 -1
  11. package/dist/cli/resume.js +2 -2
  12. package/dist/cli/resume.js.map +1 -1
  13. package/dist/core/git-lifecycle-manager.js +2 -2
  14. package/dist/core/git-lifecycle-manager.js.map +1 -1
  15. package/dist/core/git-pipeline-coordinator.js +25 -25
  16. package/dist/core/git-pipeline-coordinator.js.map +1 -1
  17. package/dist/core/orchestrator.d.ts +17 -0
  18. package/dist/core/orchestrator.js +186 -8
  19. package/dist/core/orchestrator.js.map +1 -1
  20. package/dist/core/runner/pipeline.js +3 -3
  21. package/dist/core/runner/pipeline.js.map +1 -1
  22. package/dist/hooks/data-accessor.js +2 -2
  23. package/dist/hooks/data-accessor.js.map +1 -1
  24. package/dist/services/logging/buffer.d.ts +2 -1
  25. package/dist/services/logging/buffer.js +63 -22
  26. package/dist/services/logging/buffer.js.map +1 -1
  27. package/dist/services/logging/formatter.d.ts +0 -4
  28. package/dist/services/logging/formatter.js +33 -201
  29. package/dist/services/logging/formatter.js.map +1 -1
  30. package/dist/services/logging/paths.d.ts +3 -0
  31. package/dist/services/logging/paths.js +3 -0
  32. package/dist/services/logging/paths.js.map +1 -1
  33. package/dist/types/config.d.ts +9 -1
  34. package/dist/types/flow.d.ts +6 -0
  35. package/dist/types/logging.d.ts +1 -1
  36. package/dist/utils/config.js +6 -2
  37. package/dist/utils/config.js.map +1 -1
  38. package/dist/utils/enhanced-logger.d.ts +37 -17
  39. package/dist/utils/enhanced-logger.js +267 -237
  40. package/dist/utils/enhanced-logger.js.map +1 -1
  41. package/dist/utils/events.d.ts +18 -15
  42. package/dist/utils/events.js +8 -5
  43. package/dist/utils/events.js.map +1 -1
  44. package/dist/utils/log-formatter.d.ts +26 -0
  45. package/dist/utils/log-formatter.js +274 -0
  46. package/dist/utils/log-formatter.js.map +1 -0
  47. package/dist/utils/logger.js +4 -17
  48. package/dist/utils/logger.js.map +1 -1
  49. package/dist/utils/repro-thinking-logs.js +4 -4
  50. package/dist/utils/repro-thinking-logs.js.map +1 -1
  51. package/package.json +2 -2
  52. package/scripts/monitor-lanes.sh +5 -5
  53. package/scripts/stream-logs.sh +1 -1
  54. package/scripts/test-log-parser.ts +42 -8
  55. package/src/cli/complete.ts +21 -6
  56. package/src/cli/index.ts +2 -0
  57. package/src/cli/logs.ts +60 -46
  58. package/src/cli/monitor.ts +47 -64
  59. package/src/cli/resume.ts +1 -1
  60. package/src/core/git-lifecycle-manager.ts +2 -2
  61. package/src/core/git-pipeline-coordinator.ts +25 -25
  62. package/src/core/orchestrator.ts +214 -7
  63. package/src/core/runner/pipeline.ts +3 -3
  64. package/src/hooks/data-accessor.ts +2 -2
  65. package/src/services/logging/buffer.ts +68 -20
  66. package/src/services/logging/formatter.ts +32 -199
  67. package/src/services/logging/paths.ts +3 -0
  68. package/src/types/config.ts +13 -1
  69. package/src/types/flow.ts +6 -0
  70. package/src/types/logging.ts +0 -2
  71. package/src/utils/config.ts +6 -2
  72. package/src/utils/enhanced-logger.ts +290 -239
  73. package/src/utils/events.ts +21 -18
  74. package/src/utils/log-formatter.ts +287 -0
  75. package/src/utils/logger.ts +3 -18
  76. package/src/utils/repro-thinking-logs.ts +4 -4
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  /**
3
- * Enhanced Logger - Simplified JSONL terminal output capture
3
+ * Enhanced Logger - Simplified terminal output capture
4
+ *
5
+ * Features:
6
+ * - Raw log: Original output as-is
7
+ * - Readable log: Formatted with formatMessageForConsole style
4
8
  */
5
9
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
10
  if (k2 === undefined) k2 = k;
@@ -37,31 +41,86 @@ var __importStar = (this && this.__importStar) || (function () {
37
41
  })();
38
42
  Object.defineProperty(exports, "__esModule", { value: true });
39
43
  exports.CleanLogTransform = exports.StreamingMessageParser = exports.EnhancedLogManager = exports.DEFAULT_LOG_CONFIG = void 0;
44
+ exports.stripAnsi = stripAnsi;
45
+ exports.formatTimestamp = formatTimestamp;
40
46
  exports.createLogManager = createLogManager;
41
47
  exports.readJsonLog = readJsonLog;
42
48
  exports.exportLogs = exportLogs;
43
49
  const fs = __importStar(require("fs"));
44
50
  const path = __importStar(require("path"));
45
- const formatter_1 = require("../services/logging/formatter");
51
+ const log_formatter_1 = require("./log-formatter");
46
52
  const path_1 = require("./path");
47
53
  const paths_1 = require("../services/logging/paths");
48
54
  exports.DEFAULT_LOG_CONFIG = {
49
55
  enabled: true,
50
56
  stripAnsi: true,
57
+ addTimestamps: true,
51
58
  maxFileSize: 50 * 1024 * 1024, // 50MB
52
59
  maxFiles: 5,
53
- writeJsonLog: true,
60
+ keepRawLogs: true,
61
+ keepAbsoluteRawLogs: false,
62
+ raw: false,
63
+ writeJsonLog: false, // Disabled by default now
54
64
  timestampFormat: 'iso',
55
65
  };
56
66
  /**
57
- * Enhanced Log Manager - JSONL format only
67
+ * ANSI escape sequence regex pattern
68
+ */
69
+ const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
70
+ const EXTENDED_ANSI_REGEX = /(?:\x1B[@-Z\\-_]|\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)|\x1B[PX^_][^\x1B]*\x1B\\|\x1B.)/g;
71
+ /**
72
+ * Strip ANSI escape sequences from text
73
+ */
74
+ function stripAnsi(text) {
75
+ return text
76
+ .replace(EXTENDED_ANSI_REGEX, '')
77
+ .replace(ANSI_REGEX, '')
78
+ .replace(/\r[^\n]/g, '\n')
79
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
80
+ }
81
+ /**
82
+ * Format timestamp
83
+ */
84
+ function formatTimestamp(format, startTime) {
85
+ const now = Date.now();
86
+ switch (format) {
87
+ case 'iso':
88
+ return new Date(now).toISOString();
89
+ case 'relative':
90
+ if (startTime) {
91
+ const elapsed = now - startTime;
92
+ const seconds = Math.floor(elapsed / 1000);
93
+ const minutes = Math.floor(seconds / 60);
94
+ const hours = Math.floor(minutes / 60);
95
+ if (hours > 0) {
96
+ return `+${hours}h${minutes % 60}m${seconds % 60}s`;
97
+ }
98
+ else if (minutes > 0) {
99
+ return `+${minutes}m${seconds % 60}s`;
100
+ }
101
+ else {
102
+ return `+${seconds}s`;
103
+ }
104
+ }
105
+ return new Date(now).toISOString();
106
+ case 'short':
107
+ return new Date(now).toLocaleTimeString('en-US', { hour12: false });
108
+ default:
109
+ return new Date(now).toISOString();
110
+ }
111
+ }
112
+ /**
113
+ * Simplified Log Manager - Only raw and readable logs
58
114
  */
59
115
  class EnhancedLogManager {
60
116
  config;
61
117
  session;
62
118
  logDir;
63
- jsonlLogPath;
64
- jsonlLogFd = null;
119
+ rawLogPath;
120
+ readableLogPath;
121
+ rawLogFd = null;
122
+ readableLogFd = null;
123
+ rawLogSize = 0;
65
124
  onParsedMessage;
66
125
  constructor(logDir, session, config = {}, onParsedMessage) {
67
126
  this.config = { ...exports.DEFAULT_LOG_CONFIG, ...config };
@@ -71,7 +130,10 @@ class EnhancedLogManager {
71
130
  // Ensure log directory exists
72
131
  fs.mkdirSync(logDir, { recursive: true });
73
132
  // Subprocess (lane) logs live in the lane run directory to avoid nesting with main logs.
74
- this.jsonlLogPath = (0, paths_1.getLaneLogPath)(logDir, 'jsonl');
133
+ // Main process logs are written separately to a run-level file (see utils/logger.ts).
134
+ // Set up log file paths (simplified)
135
+ this.rawLogPath = (0, paths_1.getLaneLogPath)(logDir, 'raw');
136
+ this.readableLogPath = (0, paths_1.getLaneLogPath)(logDir, 'readable');
75
137
  // Initialize log files
76
138
  this.initLogFiles();
77
139
  }
@@ -94,34 +156,43 @@ class EnhancedLogManager {
94
156
  * Initialize log files and write session headers
95
157
  */
96
158
  initLogFiles() {
97
- if (this.config.writeJsonLog) {
98
- // Ensure parent directory exists before opening file
99
- const logDir = path.dirname(this.jsonlLogPath);
100
- fs.mkdirSync(logDir, { recursive: true });
101
- this.rotateIfNeeded(this.jsonlLogPath);
102
- this.jsonlLogFd = fs.openSync(this.jsonlLogPath, 'a');
159
+ // Check and rotate if necessary
160
+ if (this.config.keepRawLogs) {
161
+ this.rotateIfNeeded(this.rawLogPath);
162
+ this.rawLogFd = fs.openSync(this.rawLogPath, 'a');
103
163
  }
104
- // Write session start to JSON log
105
- this.writeSessionStart();
164
+ this.rotateIfNeeded(this.readableLogPath);
165
+ this.readableLogFd = fs.openSync(this.readableLogPath, 'a');
166
+ // Get initial file size for raw log if enabled
167
+ if (this.rawLogFd !== null) {
168
+ try {
169
+ this.rawLogSize = fs.statSync(this.rawLogPath).size;
170
+ }
171
+ catch {
172
+ this.rawLogSize = 0;
173
+ }
174
+ }
175
+ // Write session header
176
+ this.writeSessionHeader();
106
177
  }
107
178
  /**
108
- * Write session start to JSON log
179
+ * Write session header to logs
109
180
  */
110
- writeSessionStart() {
111
- if (this.config.writeJsonLog) {
112
- this.writeToJsonLog({
113
- type: 'session',
114
- role: 'system',
115
- content: 'Session started',
116
- timestamp: this.session.startTime,
117
- metadata: {
118
- sessionId: this.session.id,
119
- laneName: this.session.laneName,
120
- taskName: this.session.taskName,
121
- model: this.session.model,
122
- }
123
- });
124
- }
181
+ writeSessionHeader() {
182
+ const header = `
183
+ ╔══════════════════════════════════════════════════════════════════════════════╗
184
+ ║ CursorFlow Session Log ║
185
+ ╠══════════════════════════════════════════════════════════════════════════════╣
186
+ Session ID: ${this.session.id.padEnd(62)}║
187
+ ║ Lane: ${this.session.laneName.padEnd(62)}║
188
+ ║ Task: ${(this.session.taskName || '-').padEnd(62)}║
189
+ ║ Model: ${(this.session.model || 'default').padEnd(62)}║
190
+ ║ Started: ${new Date(this.session.startTime).toISOString().padEnd(62)}║
191
+ ╚══════════════════════════════════════════════════════════════════════════════╝
192
+
193
+ `;
194
+ this.writeToRawLog(header);
195
+ this.writeToReadableLog(header);
125
196
  }
126
197
  /**
127
198
  * Rotate log file if it exceeds max size
@@ -164,35 +235,49 @@ class EnhancedLogManager {
164
235
  fs.renameSync(logPath, rotatedPath);
165
236
  }
166
237
  /**
167
- * Write to JSONL log
238
+ * Write to raw log with size tracking
168
239
  */
169
- writeToJsonLog(msg) {
170
- if (this.jsonlLogFd === null)
240
+ writeToRawLog(data) {
241
+ if (this.rawLogFd === null)
242
+ return;
243
+ const buffer = typeof data === 'string' ? Buffer.from(data) : data;
244
+ fs.writeSync(this.rawLogFd, buffer);
245
+ this.rawLogSize += buffer.length;
246
+ // Check if rotation needed
247
+ if (this.rawLogSize >= this.config.maxFileSize) {
248
+ fs.closeSync(this.rawLogFd);
249
+ this.rotateLog(this.rawLogPath);
250
+ this.rawLogFd = fs.openSync(this.rawLogPath, 'a');
251
+ this.rawLogSize = 0;
252
+ }
253
+ }
254
+ /**
255
+ * Write to readable log
256
+ */
257
+ writeToReadableLog(data) {
258
+ if (this.readableLogFd === null)
171
259
  return;
172
260
  try {
173
- // Add lane and task context to JSON log
174
- const entry = {
175
- ...msg,
176
- lane: this.session.laneName,
177
- task: this.session.taskName,
178
- laneIndex: this.session.laneIndex,
179
- taskIndex: this.session.taskIndex,
180
- timestamp_iso: new Date(msg.timestamp).toISOString(),
181
- };
182
- fs.writeSync(this.jsonlLogFd, JSON.stringify(entry) + '\n');
261
+ fs.writeSync(this.readableLogFd, data);
183
262
  }
184
263
  catch {
185
264
  // Ignore write errors
186
265
  }
187
266
  }
188
267
  /**
189
- * Write a parsed message to the JSON log and console
268
+ * Write a parsed message to the readable log using formatMessageForConsole style
190
269
  */
191
270
  writeReadableMessage(msg) {
192
- // Write to JSON log
193
- if (this.config.writeJsonLog) {
194
- this.writeToJsonLog(msg);
195
- }
271
+ // Use formatMessageForConsole for consistent formatting
272
+ // Use short lane-task label like [1-1-lanename10]
273
+ const formatted = (0, log_formatter_1.formatMessageForConsole)(msg, {
274
+ laneLabel: `[${this.getLaneTaskLabel()}]`,
275
+ includeTimestamp: false, // We'll add our own short timestamp
276
+ });
277
+ // Strip ANSI codes and add short timestamp for file output
278
+ const clean = stripAnsi(formatted);
279
+ const ts = this.getShortTime();
280
+ this.writeToReadableLog(`[${ts}] ${clean}\n`);
196
281
  // Callback for console output
197
282
  if (this.onParsedMessage) {
198
283
  this.onParsedMessage(msg);
@@ -203,6 +288,9 @@ class EnhancedLogManager {
203
288
  */
204
289
  writeStdout(data) {
205
290
  const text = data.toString();
291
+ // Write raw log (original data)
292
+ this.writeToRawLog(data);
293
+ // Parse JSON output and write to readable log
206
294
  const lines = text.split('\n');
207
295
  for (const line of lines) {
208
296
  const trimmed = line.trim();
@@ -219,11 +307,6 @@ class EnhancedLogManager {
219
307
  }
220
308
  // parseJsonToMessage returned null - create fallback message for known JSON
221
309
  if (json.type) {
222
- // Skip noisy events that we don't want to show
223
- if (json.type === 'thinking' || json.type === 'call' ||
224
- (json.type === 'tool_call' && !json.tool_call)) {
225
- continue;
226
- }
227
310
  const fallbackMsg = {
228
311
  type: 'info',
229
312
  role: 'system',
@@ -238,29 +321,41 @@ class EnhancedLogManager {
238
321
  // Not valid JSON, fall through
239
322
  }
240
323
  }
241
- // Non-JSON line
242
- const cleanLine = (0, formatter_1.stripAnsi)(trimmed);
324
+ // Non-JSON line - write as-is with short timestamp and lane-task label
325
+ const cleanLine = stripAnsi(trimmed);
243
326
  if (cleanLine && !this.isNoiseLog(cleanLine)) {
244
- // Detect if this looks like Git output
245
- const isGit = this.isGitOutput(cleanLine);
246
- const type = isGit ? 'git' : 'stdout';
247
- // Output to console
248
- if (this.onParsedMessage) {
249
- this.onParsedMessage({
250
- type,
251
- role: 'system',
252
- content: trimmed,
253
- timestamp: Date.now(),
254
- });
327
+ const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
328
+ const label = this.getLaneTaskLabel();
329
+ const ts = this.getShortTime();
330
+ // For file output, use clean line (no ANSI codes)
331
+ let fileFormattedLine;
332
+ if (hasTimestamp) {
333
+ fileFormattedLine = cleanLine.includes(`[${label}]`)
334
+ ? cleanLine
335
+ : cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}]`);
255
336
  }
256
- // Write to JSON log
257
- if (this.config.writeJsonLog) {
258
- this.writeToJsonLog({
259
- type,
337
+ else {
338
+ fileFormattedLine = `[${ts}] [${label}] ${cleanLine}`;
339
+ }
340
+ this.writeToReadableLog(`${fileFormattedLine}\n`);
341
+ // For console output, preserve ANSI colors from original line
342
+ if (this.onParsedMessage) {
343
+ let consoleFormattedLine;
344
+ if (hasTimestamp) {
345
+ consoleFormattedLine = trimmed.includes(`[${label}]`)
346
+ ? trimmed
347
+ : trimmed.replace(/^(\[[^\]]+\])/, `$1 [${label}]`);
348
+ }
349
+ else {
350
+ consoleFormattedLine = `[${ts}] [${label}] ${trimmed}`;
351
+ }
352
+ const rawMsg = {
353
+ type: 'raw',
260
354
  role: 'system',
261
- content: cleanLine,
355
+ content: consoleFormattedLine,
262
356
  timestamp: Date.now(),
263
- });
357
+ };
358
+ this.onParsedMessage(rawMsg);
264
359
  }
265
360
  }
266
361
  }
@@ -272,32 +367,13 @@ class EnhancedLogManager {
272
367
  const type = json.type;
273
368
  const timestamp = json.timestamp_ms || Date.now();
274
369
  switch (type) {
275
- case 'info':
276
- case 'warn':
277
- case 'error':
278
- case 'success':
279
- case 'debug':
280
- case 'progress':
281
370
  case 'system':
282
- case 'git':
283
- // Handle internal logger JSON or cursor-agent system events
284
- if (json.content && typeof json.content === 'string') {
285
- return {
286
- type: type,
287
- role: 'system',
288
- content: json.content,
289
- timestamp,
290
- };
291
- }
292
- if (type === 'system') {
293
- return {
294
- type: 'system',
295
- role: 'system',
296
- content: `Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
297
- timestamp,
298
- };
299
- }
300
- return null;
371
+ return {
372
+ type: 'system',
373
+ role: 'system',
374
+ content: `Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
375
+ timestamp,
376
+ };
301
377
  case 'user':
302
378
  if (json.message?.content) {
303
379
  const textContent = json.message.content
@@ -388,68 +464,45 @@ class EnhancedLogManager {
388
464
  */
389
465
  writeStderr(data) {
390
466
  const text = data.toString();
467
+ // Write raw log
468
+ this.writeToRawLog(data);
469
+ // Write to readable log - treat stderr same as stdout
470
+ // Git and many tools use stderr for non-error output
391
471
  const lines = text.split('\n');
392
472
  for (const line of lines) {
393
- const cleanLine = (0, formatter_1.stripAnsi)(line).trim();
473
+ const cleanLine = stripAnsi(line).trim();
394
474
  if (cleanLine && !this.isNoiseLog(cleanLine)) {
475
+ const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
476
+ const label = this.getLaneTaskLabel();
477
+ const ts = this.getShortTime();
478
+ // Determine if this is actually an error message
395
479
  const isError = this.isErrorLine(cleanLine);
396
- const isGit = this.isGitOutput(cleanLine);
397
- const type = isError ? 'stderr' : (isGit ? 'git' : 'stderr');
398
- // Write to JSON log
399
- if (this.config.writeJsonLog) {
400
- this.writeToJsonLog({
401
- type,
402
- role: 'system',
403
- content: cleanLine,
404
- timestamp: Date.now(),
405
- metadata: { isError, isGit }
406
- });
480
+ const prefix = isError ? '❌ ERR' : '';
481
+ let formattedLine;
482
+ if (hasTimestamp) {
483
+ formattedLine = cleanLine.includes(`[${label}]`)
484
+ ? cleanLine
485
+ : cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}]${prefix ? ' ' + prefix : ''}`);
486
+ }
487
+ else {
488
+ formattedLine = prefix
489
+ ? `[${ts}] [${label}] ${prefix} ${cleanLine}`
490
+ : `[${ts}] [${label}] ${cleanLine}`;
407
491
  }
492
+ this.writeToReadableLog(`${formattedLine}\n`);
408
493
  // Output to console
409
494
  if (this.onParsedMessage) {
410
- this.onParsedMessage({
411
- type,
495
+ const rawMsg = {
496
+ type: 'raw',
412
497
  role: 'system',
413
- content: line.trim(),
498
+ content: formattedLine,
414
499
  timestamp: Date.now(),
415
- });
500
+ };
501
+ this.onParsedMessage(rawMsg);
416
502
  }
417
503
  }
418
504
  }
419
505
  }
420
- /**
421
- * Check if a line is likely Git output
422
- */
423
- isGitOutput(text) {
424
- const gitPatterns = [
425
- /^Running: git /i,
426
- /^git version /i,
427
- /^On branch /i,
428
- /^Your branch is /i,
429
- /^Changes to be committed:/i,
430
- /^Changes not staged for commit:/i,
431
- /^Untracked files:/i,
432
- /^\s*\((use "git |use --|use -)/i,
433
- /^\s*modified:\s+/i,
434
- /^\s*new file:\s+/i,
435
- /^\s*deleted:\s+/i,
436
- /^\s*renamed:\s+/i,
437
- /^Switched to branch /i,
438
- /^Switched to a new branch /i,
439
- /^Merge made by the /i,
440
- /^Everything up-to-date/i,
441
- /^Already up to date/i,
442
- /^branch '.*' set up to track remote branch '.*' from 'origin'/i,
443
- /^Counting objects: /i,
444
- /^Compressing objects: /i,
445
- /^Writing objects: /i,
446
- /^Total \d+ \(delta \d+\)/i,
447
- /^remote: Resolving deltas:/i,
448
- /^To .*\.git/i,
449
- /^[a-f0-9]{7,40}\.\.[a-f0-9]{7,40}\s+.*->\s+.*/i,
450
- ];
451
- return gitPatterns.some(p => p.test(text));
452
- }
453
506
  /**
454
507
  * Check if a line is actually an error message
455
508
  */
@@ -473,40 +526,21 @@ class EnhancedLogManager {
473
526
  * Write a custom log entry
474
527
  */
475
528
  log(level, message, metadata) {
476
- const isGit = metadata?.context === 'git';
477
- const type = isGit ? 'git' : level;
478
- if (this.config.writeJsonLog) {
479
- this.writeToJsonLog({
480
- type,
481
- role: 'system',
482
- content: message,
483
- timestamp: Date.now(),
484
- metadata
485
- });
486
- }
487
- if (this.onParsedMessage) {
488
- this.onParsedMessage({
489
- type,
490
- role: 'system',
491
- content: message,
492
- timestamp: Date.now(),
493
- metadata
494
- });
495
- }
529
+ const ts = this.getShortTime();
530
+ const label = this.getLaneTaskLabel();
531
+ const emoji = level === 'error' ? '❌' : level === 'info' ? 'ℹ️' : '🔍';
532
+ const line = `[${ts}] [${label}] ${emoji} ${level.toUpperCase()} ${message}\n`;
533
+ this.writeToRawLog(line);
534
+ this.writeToReadableLog(line);
496
535
  }
497
536
  /**
498
537
  * Add a section marker
499
538
  */
500
539
  section(title) {
501
- if (this.config.writeJsonLog) {
502
- this.writeToJsonLog({
503
- type: 'info',
504
- role: 'system',
505
- content: `--- ${title} ---`,
506
- timestamp: Date.now(),
507
- metadata: { isSection: true }
508
- });
509
- }
540
+ const divider = '═'.repeat(78);
541
+ const line = `\n${divider}\n ${title}\n${divider}\n`;
542
+ this.writeToRawLog(line);
543
+ this.writeToReadableLog(line);
510
544
  }
511
545
  /**
512
546
  * Update task context
@@ -541,39 +575,47 @@ class EnhancedLogManager {
541
575
  */
542
576
  getLogPaths() {
543
577
  return {
544
- jsonl: this.jsonlLogPath,
545
- clean: this.jsonlLogPath, // Backward compatibility
578
+ clean: this.readableLogPath, // For backward compatibility
579
+ raw: this.rawLogPath,
580
+ readable: this.readableLogPath,
546
581
  };
547
582
  }
548
583
  /**
549
584
  * Create file descriptors for process stdio redirection
550
585
  */
551
586
  getFileDescriptors() {
552
- const fd = this.jsonlLogFd;
587
+ const fd = this.rawLogFd;
553
588
  return { stdout: fd, stderr: fd };
554
589
  }
555
590
  /**
556
591
  * Close all log files
557
592
  */
558
593
  close() {
559
- if (this.jsonlLogFd !== null) {
594
+ // Write session end marker
595
+ const endMarker = `
596
+ ╔══════════════════════════════════════════════════════════════════════════════╗
597
+ ║ Session Ended: ${new Date().toISOString().padEnd(60)}║
598
+ ║ Duration: ${this.formatDuration(Date.now() - this.session.startTime).padEnd(65)}║
599
+ ╚══════════════════════════════════════════════════════════════════════════════╝
600
+
601
+ `;
602
+ if (this.rawLogFd !== null) {
560
603
  try {
561
- if (this.config.writeJsonLog) {
562
- this.writeToJsonLog({
563
- type: 'session',
564
- role: 'system',
565
- content: 'Session ended',
566
- timestamp: Date.now(),
567
- metadata: {
568
- duration: Date.now() - this.session.startTime,
569
- }
570
- });
571
- }
572
- fs.fsyncSync(this.jsonlLogFd);
573
- fs.closeSync(this.jsonlLogFd);
604
+ fs.writeSync(this.rawLogFd, endMarker);
605
+ fs.fsyncSync(this.rawLogFd);
606
+ fs.closeSync(this.rawLogFd);
574
607
  }
575
608
  catch { }
576
- this.jsonlLogFd = null;
609
+ this.rawLogFd = null;
610
+ }
611
+ if (this.readableLogFd !== null) {
612
+ try {
613
+ fs.writeSync(this.readableLogFd, endMarker);
614
+ fs.fsyncSync(this.readableLogFd);
615
+ fs.closeSync(this.readableLogFd);
616
+ }
617
+ catch { }
618
+ this.readableLogFd = null;
577
619
  }
578
620
  }
579
621
  /**
@@ -581,25 +623,39 @@ class EnhancedLogManager {
581
623
  */
582
624
  getLastError() {
583
625
  try {
584
- if (!fs.existsSync(this.jsonlLogPath))
626
+ if (!fs.existsSync(this.readableLogPath))
585
627
  return null;
586
- const content = fs.readFileSync(this.jsonlLogPath, 'utf8');
587
- const lines = content.split('\n').filter(l => l.trim());
588
- for (let i = lines.length - 1; i >= 0; i--) {
589
- try {
590
- const entry = JSON.parse(lines[i]);
591
- if (entry.type === 'error' || entry.type === 'stderr' && entry.metadata?.isError) {
592
- return entry.content;
593
- }
594
- }
595
- catch { }
628
+ const content = fs.readFileSync(this.readableLogPath, 'utf8');
629
+ const lines = content.split('\n').filter(l => l.includes('❌') ||
630
+ l.includes('[ERROR]') ||
631
+ l.includes('error:') ||
632
+ l.includes('Fatal') ||
633
+ l.includes('fail'));
634
+ if (lines.length === 0) {
635
+ const allLines = content.split('\n').filter(l => l.trim());
636
+ return allLines.slice(-5).join('\n');
596
637
  }
597
- return null;
638
+ return lines[lines.length - 1].trim();
598
639
  }
599
640
  catch {
600
641
  return null;
601
642
  }
602
643
  }
644
+ /**
645
+ * Format duration for display
646
+ */
647
+ formatDuration(ms) {
648
+ const seconds = Math.floor((ms / 1000) % 60);
649
+ const minutes = Math.floor((ms / (1000 * 60)) % 60);
650
+ const hours = Math.floor(ms / (1000 * 60 * 60));
651
+ if (hours > 0) {
652
+ return `${hours}h ${minutes}m ${seconds}s`;
653
+ }
654
+ else if (minutes > 0) {
655
+ return `${minutes}m ${seconds}s`;
656
+ }
657
+ return `${seconds}s`;
658
+ }
603
659
  }
604
660
  exports.EnhancedLogManager = EnhancedLogManager;
605
661
  /**
@@ -616,45 +672,19 @@ function createLogManager(laneRunDir, laneName, config, onParsedMessage, laneInd
616
672
  return new EnhancedLogManager(laneRunDir, session, config, onParsedMessage);
617
673
  }
618
674
  /**
619
- * Read and parse JSON log file
675
+ * Read and parse JSON log file (legacy compatibility - returns empty array)
620
676
  */
621
677
  function readJsonLog(logPath) {
622
- try {
623
- if (!fs.existsSync(logPath))
624
- return [];
625
- const content = fs.readFileSync(logPath, 'utf8');
626
- return content.split('\n')
627
- .filter(l => l.trim())
628
- .map(l => {
629
- try {
630
- return JSON.parse(l);
631
- }
632
- catch {
633
- return null;
634
- }
635
- })
636
- .filter(Boolean);
637
- }
638
- catch {
639
- return [];
640
- }
678
+ return [];
641
679
  }
642
680
  /**
643
- * Export logs
681
+ * Export logs (legacy compatibility)
644
682
  */
645
683
  function exportLogs(laneRunDir, format, outputPath) {
646
- const logPath = (0, paths_1.getLaneLogPath)(laneRunDir, 'jsonl');
684
+ const logPath = (0, paths_1.getLaneLogPath)(laneRunDir, 'raw');
647
685
  let output = '';
648
686
  if (fs.existsSync(logPath)) {
649
- if (format === 'json') {
650
- const logs = readJsonLog(logPath);
651
- output = JSON.stringify(logs, null, 2);
652
- }
653
- else {
654
- // Basic text conversion for other formats
655
- const logs = readJsonLog(logPath);
656
- output = logs.map(l => `[${l.timestamp_iso}] [${l.type}] ${l.content}`).join('\n');
657
- }
687
+ output = fs.readFileSync(logPath, 'utf8');
658
688
  }
659
689
  if (outputPath) {
660
690
  fs.writeFileSync(outputPath, output, 'utf8');