@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.
- package/CHANGELOG.md +29 -20
- package/README.md +13 -8
- package/dist/cli/complete.js +22 -5
- package/dist/cli/complete.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/logs.js +61 -51
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +45 -56
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/resume.js +2 -2
- package/dist/cli/resume.js.map +1 -1
- package/dist/core/git-lifecycle-manager.js +2 -2
- package/dist/core/git-lifecycle-manager.js.map +1 -1
- package/dist/core/git-pipeline-coordinator.js +25 -25
- package/dist/core/git-pipeline-coordinator.js.map +1 -1
- package/dist/core/orchestrator.d.ts +17 -0
- package/dist/core/orchestrator.js +186 -8
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/pipeline.js +3 -3
- package/dist/core/runner/pipeline.js.map +1 -1
- package/dist/hooks/data-accessor.js +2 -2
- package/dist/hooks/data-accessor.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +2 -1
- package/dist/services/logging/buffer.js +63 -22
- package/dist/services/logging/buffer.js.map +1 -1
- package/dist/services/logging/formatter.d.ts +0 -4
- package/dist/services/logging/formatter.js +33 -201
- package/dist/services/logging/formatter.js.map +1 -1
- package/dist/services/logging/paths.d.ts +3 -0
- package/dist/services/logging/paths.js +3 -0
- package/dist/services/logging/paths.js.map +1 -1
- package/dist/types/config.d.ts +9 -1
- package/dist/types/flow.d.ts +6 -0
- package/dist/types/logging.d.ts +1 -1
- package/dist/utils/config.js +6 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +37 -17
- package/dist/utils/enhanced-logger.js +267 -237
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/events.d.ts +18 -15
- package/dist/utils/events.js +8 -5
- package/dist/utils/events.js.map +1 -1
- package/dist/utils/log-formatter.d.ts +26 -0
- package/dist/utils/log-formatter.js +274 -0
- package/dist/utils/log-formatter.js.map +1 -0
- package/dist/utils/logger.js +4 -17
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/repro-thinking-logs.js +4 -4
- package/dist/utils/repro-thinking-logs.js.map +1 -1
- package/package.json +2 -2
- package/scripts/monitor-lanes.sh +5 -5
- package/scripts/stream-logs.sh +1 -1
- package/scripts/test-log-parser.ts +42 -8
- package/src/cli/complete.ts +21 -6
- package/src/cli/index.ts +2 -0
- package/src/cli/logs.ts +60 -46
- package/src/cli/monitor.ts +47 -64
- package/src/cli/resume.ts +1 -1
- package/src/core/git-lifecycle-manager.ts +2 -2
- package/src/core/git-pipeline-coordinator.ts +25 -25
- package/src/core/orchestrator.ts +214 -7
- package/src/core/runner/pipeline.ts +3 -3
- package/src/hooks/data-accessor.ts +2 -2
- package/src/services/logging/buffer.ts +68 -20
- package/src/services/logging/formatter.ts +32 -199
- package/src/services/logging/paths.ts +3 -0
- package/src/types/config.ts +13 -1
- package/src/types/flow.ts +6 -0
- package/src/types/logging.ts +0 -2
- package/src/utils/config.ts +6 -2
- package/src/utils/enhanced-logger.ts +290 -239
- package/src/utils/events.ts +21 -18
- package/src/utils/log-formatter.ts +287 -0
- package/src/utils/logger.ts +3 -18
- package/src/utils/repro-thinking-logs.ts +4 -4
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Enhanced Logger - Simplified
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
fs.
|
|
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
|
-
|
|
105
|
-
this.
|
|
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
|
|
179
|
+
* Write session header to logs
|
|
109
180
|
*/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
238
|
+
* Write to raw log with size tracking
|
|
168
239
|
*/
|
|
169
|
-
|
|
170
|
-
if (this.
|
|
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
|
-
|
|
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
|
|
268
|
+
* Write a parsed message to the readable log using formatMessageForConsole style
|
|
190
269
|
*/
|
|
191
270
|
writeReadableMessage(msg) {
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
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 =
|
|
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
|
-
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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:
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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 =
|
|
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
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
411
|
-
type,
|
|
495
|
+
const rawMsg = {
|
|
496
|
+
type: 'raw',
|
|
412
497
|
role: 'system',
|
|
413
|
-
content:
|
|
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
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
545
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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.
|
|
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.
|
|
626
|
+
if (!fs.existsSync(this.readableLogPath))
|
|
585
627
|
return null;
|
|
586
|
-
const content = fs.readFileSync(this.
|
|
587
|
-
const lines = content.split('\n').filter(l => l.
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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
|
|
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
|
-
|
|
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, '
|
|
684
|
+
const logPath = (0, paths_1.getLaneLogPath)(laneRunDir, 'raw');
|
|
647
685
|
let output = '';
|
|
648
686
|
if (fs.existsSync(logPath)) {
|
|
649
|
-
|
|
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');
|