@litmers/cursorflow-orchestrator 0.2.3 → 0.2.5
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 +14 -0
- package/README.md +7 -11
- package/dist/cli/complete.d.ts +7 -0
- package/dist/cli/complete.js +304 -0
- package/dist/cli/complete.js.map +1 -0
- package/dist/cli/logs.js +51 -61
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +56 -44
- 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.js +8 -7
- 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 +1 -2
- package/dist/services/logging/buffer.js +22 -63
- package/dist/services/logging/buffer.js.map +1 -1
- package/dist/services/logging/formatter.d.ts +4 -0
- package/dist/services/logging/formatter.js +201 -33
- package/dist/services/logging/formatter.js.map +1 -1
- package/dist/services/logging/paths.d.ts +0 -3
- package/dist/services/logging/paths.js +0 -3
- package/dist/services/logging/paths.js.map +1 -1
- package/dist/types/config.d.ts +1 -9
- package/dist/types/logging.d.ts +1 -1
- package/dist/utils/config.js +2 -6
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +17 -37
- package/dist/utils/enhanced-logger.js +237 -267
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/logger.js +17 -4
- 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 +8 -42
- package/src/cli/complete.ts +305 -0
- package/src/cli/logs.ts +46 -60
- package/src/cli/monitor.ts +64 -46
- 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 +7 -7
- package/src/core/runner/pipeline.ts +3 -3
- package/src/hooks/data-accessor.ts +2 -2
- package/src/services/logging/buffer.ts +20 -68
- package/src/services/logging/formatter.ts +199 -32
- package/src/services/logging/paths.ts +0 -3
- package/src/types/config.ts +1 -13
- package/src/types/logging.ts +2 -0
- package/src/utils/config.ts +2 -6
- package/src/utils/enhanced-logger.ts +239 -290
- package/src/utils/logger.ts +18 -3
- package/src/utils/repro-thinking-logs.ts +4 -4
- package/dist/utils/log-formatter.d.ts +0 -26
- package/dist/utils/log-formatter.js +0 -274
- package/dist/utils/log-formatter.js.map +0 -1
- package/src/utils/log-formatter.ts +0 -287
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Enhanced Logger - Simplified terminal output capture
|
|
4
|
-
*
|
|
5
|
-
* Features:
|
|
6
|
-
* - Raw log: Original output as-is
|
|
7
|
-
* - Readable log: Formatted with formatMessageForConsole style
|
|
3
|
+
* Enhanced Logger - Simplified JSONL terminal output capture
|
|
8
4
|
*/
|
|
9
5
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
6
|
if (k2 === undefined) k2 = k;
|
|
@@ -41,86 +37,31 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
37
|
})();
|
|
42
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
39
|
exports.CleanLogTransform = exports.StreamingMessageParser = exports.EnhancedLogManager = exports.DEFAULT_LOG_CONFIG = void 0;
|
|
44
|
-
exports.stripAnsi = stripAnsi;
|
|
45
|
-
exports.formatTimestamp = formatTimestamp;
|
|
46
40
|
exports.createLogManager = createLogManager;
|
|
47
41
|
exports.readJsonLog = readJsonLog;
|
|
48
42
|
exports.exportLogs = exportLogs;
|
|
49
43
|
const fs = __importStar(require("fs"));
|
|
50
44
|
const path = __importStar(require("path"));
|
|
51
|
-
const
|
|
45
|
+
const formatter_1 = require("../services/logging/formatter");
|
|
52
46
|
const path_1 = require("./path");
|
|
53
47
|
const paths_1 = require("../services/logging/paths");
|
|
54
48
|
exports.DEFAULT_LOG_CONFIG = {
|
|
55
49
|
enabled: true,
|
|
56
50
|
stripAnsi: true,
|
|
57
|
-
addTimestamps: true,
|
|
58
51
|
maxFileSize: 50 * 1024 * 1024, // 50MB
|
|
59
52
|
maxFiles: 5,
|
|
60
|
-
|
|
61
|
-
keepAbsoluteRawLogs: false,
|
|
62
|
-
raw: false,
|
|
63
|
-
writeJsonLog: false, // Disabled by default now
|
|
53
|
+
writeJsonLog: true,
|
|
64
54
|
timestampFormat: 'iso',
|
|
65
55
|
};
|
|
66
56
|
/**
|
|
67
|
-
*
|
|
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
|
|
57
|
+
* Enhanced Log Manager - JSONL format only
|
|
114
58
|
*/
|
|
115
59
|
class EnhancedLogManager {
|
|
116
60
|
config;
|
|
117
61
|
session;
|
|
118
62
|
logDir;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
rawLogFd = null;
|
|
122
|
-
readableLogFd = null;
|
|
123
|
-
rawLogSize = 0;
|
|
63
|
+
jsonlLogPath;
|
|
64
|
+
jsonlLogFd = null;
|
|
124
65
|
onParsedMessage;
|
|
125
66
|
constructor(logDir, session, config = {}, onParsedMessage) {
|
|
126
67
|
this.config = { ...exports.DEFAULT_LOG_CONFIG, ...config };
|
|
@@ -130,10 +71,7 @@ class EnhancedLogManager {
|
|
|
130
71
|
// Ensure log directory exists
|
|
131
72
|
fs.mkdirSync(logDir, { recursive: true });
|
|
132
73
|
// Subprocess (lane) logs live in the lane run directory to avoid nesting with main logs.
|
|
133
|
-
|
|
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');
|
|
74
|
+
this.jsonlLogPath = (0, paths_1.getLaneLogPath)(logDir, 'jsonl');
|
|
137
75
|
// Initialize log files
|
|
138
76
|
this.initLogFiles();
|
|
139
77
|
}
|
|
@@ -156,43 +94,34 @@ class EnhancedLogManager {
|
|
|
156
94
|
* Initialize log files and write session headers
|
|
157
95
|
*/
|
|
158
96
|
initLogFiles() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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');
|
|
163
103
|
}
|
|
164
|
-
|
|
165
|
-
this.
|
|
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();
|
|
104
|
+
// Write session start to JSON log
|
|
105
|
+
this.writeSessionStart();
|
|
177
106
|
}
|
|
178
107
|
/**
|
|
179
|
-
* Write session
|
|
108
|
+
* Write session start to JSON log
|
|
180
109
|
*/
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
+
}
|
|
196
125
|
}
|
|
197
126
|
/**
|
|
198
127
|
* Rotate log file if it exceeds max size
|
|
@@ -235,49 +164,35 @@ class EnhancedLogManager {
|
|
|
235
164
|
fs.renameSync(logPath, rotatedPath);
|
|
236
165
|
}
|
|
237
166
|
/**
|
|
238
|
-
* Write to
|
|
167
|
+
* Write to JSONL log
|
|
239
168
|
*/
|
|
240
|
-
|
|
241
|
-
if (this.
|
|
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)
|
|
169
|
+
writeToJsonLog(msg) {
|
|
170
|
+
if (this.jsonlLogFd === null)
|
|
259
171
|
return;
|
|
260
172
|
try {
|
|
261
|
-
|
|
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');
|
|
262
183
|
}
|
|
263
184
|
catch {
|
|
264
185
|
// Ignore write errors
|
|
265
186
|
}
|
|
266
187
|
}
|
|
267
188
|
/**
|
|
268
|
-
* Write a parsed message to the
|
|
189
|
+
* Write a parsed message to the JSON log and console
|
|
269
190
|
*/
|
|
270
191
|
writeReadableMessage(msg) {
|
|
271
|
-
//
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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`);
|
|
192
|
+
// Write to JSON log
|
|
193
|
+
if (this.config.writeJsonLog) {
|
|
194
|
+
this.writeToJsonLog(msg);
|
|
195
|
+
}
|
|
281
196
|
// Callback for console output
|
|
282
197
|
if (this.onParsedMessage) {
|
|
283
198
|
this.onParsedMessage(msg);
|
|
@@ -288,9 +203,6 @@ class EnhancedLogManager {
|
|
|
288
203
|
*/
|
|
289
204
|
writeStdout(data) {
|
|
290
205
|
const text = data.toString();
|
|
291
|
-
// Write raw log (original data)
|
|
292
|
-
this.writeToRawLog(data);
|
|
293
|
-
// Parse JSON output and write to readable log
|
|
294
206
|
const lines = text.split('\n');
|
|
295
207
|
for (const line of lines) {
|
|
296
208
|
const trimmed = line.trim();
|
|
@@ -307,6 +219,11 @@ class EnhancedLogManager {
|
|
|
307
219
|
}
|
|
308
220
|
// parseJsonToMessage returned null - create fallback message for known JSON
|
|
309
221
|
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
|
+
}
|
|
310
227
|
const fallbackMsg = {
|
|
311
228
|
type: 'info',
|
|
312
229
|
role: 'system',
|
|
@@ -321,41 +238,29 @@ class EnhancedLogManager {
|
|
|
321
238
|
// Not valid JSON, fall through
|
|
322
239
|
}
|
|
323
240
|
}
|
|
324
|
-
// Non-JSON line
|
|
325
|
-
const cleanLine = stripAnsi(trimmed);
|
|
241
|
+
// Non-JSON line
|
|
242
|
+
const cleanLine = (0, formatter_1.stripAnsi)(trimmed);
|
|
326
243
|
if (cleanLine && !this.isNoiseLog(cleanLine)) {
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
const
|
|
330
|
-
//
|
|
331
|
-
let fileFormattedLine;
|
|
332
|
-
if (hasTimestamp) {
|
|
333
|
-
fileFormattedLine = cleanLine.includes(`[${label}]`)
|
|
334
|
-
? cleanLine
|
|
335
|
-
: cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}]`);
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
fileFormattedLine = `[${ts}] [${label}] ${cleanLine}`;
|
|
339
|
-
}
|
|
340
|
-
this.writeToReadableLog(`${fileFormattedLine}\n`);
|
|
341
|
-
// For console output, preserve ANSI colors from original line
|
|
244
|
+
// Detect if this looks like Git output
|
|
245
|
+
const isGit = this.isGitOutput(cleanLine);
|
|
246
|
+
const type = isGit ? 'git' : 'stdout';
|
|
247
|
+
// Output to console
|
|
342
248
|
if (this.onParsedMessage) {
|
|
343
|
-
|
|
344
|
-
|
|
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',
|
|
249
|
+
this.onParsedMessage({
|
|
250
|
+
type,
|
|
354
251
|
role: 'system',
|
|
355
|
-
content:
|
|
252
|
+
content: trimmed,
|
|
356
253
|
timestamp: Date.now(),
|
|
357
|
-
};
|
|
358
|
-
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
// Write to JSON log
|
|
257
|
+
if (this.config.writeJsonLog) {
|
|
258
|
+
this.writeToJsonLog({
|
|
259
|
+
type,
|
|
260
|
+
role: 'system',
|
|
261
|
+
content: cleanLine,
|
|
262
|
+
timestamp: Date.now(),
|
|
263
|
+
});
|
|
359
264
|
}
|
|
360
265
|
}
|
|
361
266
|
}
|
|
@@ -367,13 +272,32 @@ class EnhancedLogManager {
|
|
|
367
272
|
const type = json.type;
|
|
368
273
|
const timestamp = json.timestamp_ms || Date.now();
|
|
369
274
|
switch (type) {
|
|
275
|
+
case 'info':
|
|
276
|
+
case 'warn':
|
|
277
|
+
case 'error':
|
|
278
|
+
case 'success':
|
|
279
|
+
case 'debug':
|
|
280
|
+
case 'progress':
|
|
370
281
|
case 'system':
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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;
|
|
377
301
|
case 'user':
|
|
378
302
|
if (json.message?.content) {
|
|
379
303
|
const textContent = json.message.content
|
|
@@ -464,45 +388,68 @@ class EnhancedLogManager {
|
|
|
464
388
|
*/
|
|
465
389
|
writeStderr(data) {
|
|
466
390
|
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
|
|
471
391
|
const lines = text.split('\n');
|
|
472
392
|
for (const line of lines) {
|
|
473
|
-
const cleanLine = stripAnsi(line).trim();
|
|
393
|
+
const cleanLine = (0, formatter_1.stripAnsi)(line).trim();
|
|
474
394
|
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
|
|
479
395
|
const isError = this.isErrorLine(cleanLine);
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
+
});
|
|
491
407
|
}
|
|
492
|
-
this.writeToReadableLog(`${formattedLine}\n`);
|
|
493
408
|
// Output to console
|
|
494
409
|
if (this.onParsedMessage) {
|
|
495
|
-
|
|
496
|
-
type
|
|
410
|
+
this.onParsedMessage({
|
|
411
|
+
type,
|
|
497
412
|
role: 'system',
|
|
498
|
-
content:
|
|
413
|
+
content: line.trim(),
|
|
499
414
|
timestamp: Date.now(),
|
|
500
|
-
};
|
|
501
|
-
this.onParsedMessage(rawMsg);
|
|
415
|
+
});
|
|
502
416
|
}
|
|
503
417
|
}
|
|
504
418
|
}
|
|
505
419
|
}
|
|
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
|
+
}
|
|
506
453
|
/**
|
|
507
454
|
* Check if a line is actually an error message
|
|
508
455
|
*/
|
|
@@ -526,21 +473,40 @@ class EnhancedLogManager {
|
|
|
526
473
|
* Write a custom log entry
|
|
527
474
|
*/
|
|
528
475
|
log(level, message, metadata) {
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
+
}
|
|
535
496
|
}
|
|
536
497
|
/**
|
|
537
498
|
* Add a section marker
|
|
538
499
|
*/
|
|
539
500
|
section(title) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
+
}
|
|
544
510
|
}
|
|
545
511
|
/**
|
|
546
512
|
* Update task context
|
|
@@ -575,47 +541,39 @@ class EnhancedLogManager {
|
|
|
575
541
|
*/
|
|
576
542
|
getLogPaths() {
|
|
577
543
|
return {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
readable: this.readableLogPath,
|
|
544
|
+
jsonl: this.jsonlLogPath,
|
|
545
|
+
clean: this.jsonlLogPath, // Backward compatibility
|
|
581
546
|
};
|
|
582
547
|
}
|
|
583
548
|
/**
|
|
584
549
|
* Create file descriptors for process stdio redirection
|
|
585
550
|
*/
|
|
586
551
|
getFileDescriptors() {
|
|
587
|
-
const fd = this.
|
|
552
|
+
const fd = this.jsonlLogFd;
|
|
588
553
|
return { stdout: fd, stderr: fd };
|
|
589
554
|
}
|
|
590
555
|
/**
|
|
591
556
|
* Close all log files
|
|
592
557
|
*/
|
|
593
558
|
close() {
|
|
594
|
-
|
|
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) {
|
|
603
|
-
try {
|
|
604
|
-
fs.writeSync(this.rawLogFd, endMarker);
|
|
605
|
-
fs.fsyncSync(this.rawLogFd);
|
|
606
|
-
fs.closeSync(this.rawLogFd);
|
|
607
|
-
}
|
|
608
|
-
catch { }
|
|
609
|
-
this.rawLogFd = null;
|
|
610
|
-
}
|
|
611
|
-
if (this.readableLogFd !== null) {
|
|
559
|
+
if (this.jsonlLogFd !== null) {
|
|
612
560
|
try {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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);
|
|
616
574
|
}
|
|
617
575
|
catch { }
|
|
618
|
-
this.
|
|
576
|
+
this.jsonlLogFd = null;
|
|
619
577
|
}
|
|
620
578
|
}
|
|
621
579
|
/**
|
|
@@ -623,39 +581,25 @@ class EnhancedLogManager {
|
|
|
623
581
|
*/
|
|
624
582
|
getLastError() {
|
|
625
583
|
try {
|
|
626
|
-
if (!fs.existsSync(this.
|
|
584
|
+
if (!fs.existsSync(this.jsonlLogPath))
|
|
627
585
|
return null;
|
|
628
|
-
const content = fs.readFileSync(this.
|
|
629
|
-
const lines = content.split('\n').filter(l => l.
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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 { }
|
|
637
596
|
}
|
|
638
|
-
return
|
|
597
|
+
return null;
|
|
639
598
|
}
|
|
640
599
|
catch {
|
|
641
600
|
return null;
|
|
642
601
|
}
|
|
643
602
|
}
|
|
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
|
-
}
|
|
659
603
|
}
|
|
660
604
|
exports.EnhancedLogManager = EnhancedLogManager;
|
|
661
605
|
/**
|
|
@@ -672,19 +616,45 @@ function createLogManager(laneRunDir, laneName, config, onParsedMessage, laneInd
|
|
|
672
616
|
return new EnhancedLogManager(laneRunDir, session, config, onParsedMessage);
|
|
673
617
|
}
|
|
674
618
|
/**
|
|
675
|
-
* Read and parse JSON log file
|
|
619
|
+
* Read and parse JSON log file
|
|
676
620
|
*/
|
|
677
621
|
function readJsonLog(logPath) {
|
|
678
|
-
|
|
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
|
+
}
|
|
679
641
|
}
|
|
680
642
|
/**
|
|
681
|
-
* Export logs
|
|
643
|
+
* Export logs
|
|
682
644
|
*/
|
|
683
645
|
function exportLogs(laneRunDir, format, outputPath) {
|
|
684
|
-
const logPath = (0, paths_1.getLaneLogPath)(laneRunDir, '
|
|
646
|
+
const logPath = (0, paths_1.getLaneLogPath)(laneRunDir, 'jsonl');
|
|
685
647
|
let output = '';
|
|
686
648
|
if (fs.existsSync(logPath)) {
|
|
687
|
-
|
|
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
|
+
}
|
|
688
658
|
}
|
|
689
659
|
if (outputPath) {
|
|
690
660
|
fs.writeFileSync(outputPath, output, 'utf8');
|