@litmers/cursorflow-orchestrator 0.2.2 → 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 +8 -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/index.js +0 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/logs.js +51 -61
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +74 -46
- 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/cli/signal.js +33 -29
- package/dist/cli/signal.js.map +1 -1
- package/dist/core/auto-recovery.d.ts +2 -117
- package/dist/core/auto-recovery.js +4 -487
- package/dist/core/auto-recovery.js.map +1 -1
- package/dist/core/failure-policy.d.ts +0 -52
- package/dist/core/failure-policy.js +7 -174
- package/dist/core/failure-policy.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/intervention.d.ts +0 -6
- package/dist/core/intervention.js +1 -17
- package/dist/core/intervention.js.map +1 -1
- package/dist/core/orchestrator.js +18 -10
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.js +18 -15
- package/dist/core/runner/agent.js.map +1 -1
- package/dist/core/runner/pipeline.js +3 -3
- package/dist/core/runner/pipeline.js.map +1 -1
- package/dist/core/stall-detection.js +9 -7
- package/dist/core/stall-detection.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 +3 -14
- 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/index.ts +0 -6
- package/src/cli/logs.ts +46 -60
- package/src/cli/monitor.ts +82 -48
- package/src/cli/resume.ts +1 -1
- package/src/cli/signal.ts +38 -34
- package/src/core/auto-recovery.ts +13 -595
- package/src/core/failure-policy.ts +7 -228
- package/src/core/git-lifecycle-manager.ts +2 -2
- package/src/core/git-pipeline-coordinator.ts +25 -25
- package/src/core/intervention.ts +0 -18
- package/src/core/orchestrator.ts +20 -10
- package/src/core/runner/agent.ts +21 -16
- package/src/core/runner/pipeline.ts +3 -3
- package/src/core/stall-detection.ts +11 -9
- 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/cli/prepare.d.ts +0 -7
- package/dist/cli/prepare.js +0 -690
- package/dist/cli/prepare.js.map +0 -1
- 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/cli/prepare.ts +0 -777
- package/src/utils/log-formatter.ts +0 -287
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Enhanced Logger - Simplified terminal output capture
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - Raw log: Original output as-is
|
|
6
|
-
* - Readable log: Formatted with formatMessageForConsole style
|
|
2
|
+
* Enhanced Logger - Simplified JSONL terminal output capture
|
|
7
3
|
*/
|
|
8
4
|
|
|
9
5
|
import * as fs from 'fs';
|
|
10
6
|
import * as path from 'path';
|
|
11
7
|
import { EnhancedLogConfig, ParsedMessage, LogSession } from '../types';
|
|
12
|
-
import { formatMessageForConsole } from '
|
|
8
|
+
import { formatMessageForConsole, stripAnsi } from '../services/logging/formatter';
|
|
13
9
|
import { safeJoin } from './path';
|
|
14
10
|
import { getLaneLogPath } from '../services/logging/paths';
|
|
15
11
|
|
|
@@ -30,80 +26,22 @@ export interface JsonLogEntry {
|
|
|
30
26
|
export const DEFAULT_LOG_CONFIG: EnhancedLogConfig = {
|
|
31
27
|
enabled: true,
|
|
32
28
|
stripAnsi: true,
|
|
33
|
-
addTimestamps: true,
|
|
34
29
|
maxFileSize: 50 * 1024 * 1024, // 50MB
|
|
35
30
|
maxFiles: 5,
|
|
36
|
-
|
|
37
|
-
keepAbsoluteRawLogs: false,
|
|
38
|
-
raw: false,
|
|
39
|
-
writeJsonLog: false, // Disabled by default now
|
|
31
|
+
writeJsonLog: true,
|
|
40
32
|
timestampFormat: 'iso',
|
|
41
33
|
};
|
|
42
34
|
|
|
43
35
|
/**
|
|
44
|
-
*
|
|
45
|
-
*/
|
|
46
|
-
const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
|
47
|
-
const EXTENDED_ANSI_REGEX = /(?:\x1B[@-Z\\-_]|\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)|\x1B[PX^_][^\x1B]*\x1B\\|\x1B.)/g;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Strip ANSI escape sequences from text
|
|
51
|
-
*/
|
|
52
|
-
export function stripAnsi(text: string): string {
|
|
53
|
-
return text
|
|
54
|
-
.replace(EXTENDED_ANSI_REGEX, '')
|
|
55
|
-
.replace(ANSI_REGEX, '')
|
|
56
|
-
.replace(/\r[^\n]/g, '\n')
|
|
57
|
-
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Format timestamp
|
|
62
|
-
*/
|
|
63
|
-
export function formatTimestamp(format: 'iso' | 'relative' | 'short', startTime?: number): string {
|
|
64
|
-
const now = Date.now();
|
|
65
|
-
|
|
66
|
-
switch (format) {
|
|
67
|
-
case 'iso':
|
|
68
|
-
return new Date(now).toISOString();
|
|
69
|
-
case 'relative':
|
|
70
|
-
if (startTime) {
|
|
71
|
-
const elapsed = now - startTime;
|
|
72
|
-
const seconds = Math.floor(elapsed / 1000);
|
|
73
|
-
const minutes = Math.floor(seconds / 60);
|
|
74
|
-
const hours = Math.floor(minutes / 60);
|
|
75
|
-
|
|
76
|
-
if (hours > 0) {
|
|
77
|
-
return `+${hours}h${minutes % 60}m${seconds % 60}s`;
|
|
78
|
-
} else if (minutes > 0) {
|
|
79
|
-
return `+${minutes}m${seconds % 60}s`;
|
|
80
|
-
} else {
|
|
81
|
-
return `+${seconds}s`;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return new Date(now).toISOString();
|
|
85
|
-
case 'short':
|
|
86
|
-
return new Date(now).toLocaleTimeString('en-US', { hour12: false });
|
|
87
|
-
default:
|
|
88
|
-
return new Date(now).toISOString();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Simplified Log Manager - Only raw and readable logs
|
|
36
|
+
* Enhanced Log Manager - JSONL format only
|
|
94
37
|
*/
|
|
95
38
|
export class EnhancedLogManager {
|
|
96
39
|
private config: EnhancedLogConfig;
|
|
97
40
|
private session: LogSession;
|
|
98
41
|
private logDir: string;
|
|
99
42
|
|
|
100
|
-
private
|
|
101
|
-
private
|
|
102
|
-
|
|
103
|
-
private rawLogFd: number | null = null;
|
|
104
|
-
private readableLogFd: number | null = null;
|
|
105
|
-
|
|
106
|
-
private rawLogSize: number = 0;
|
|
43
|
+
private jsonlLogPath: string;
|
|
44
|
+
private jsonlLogFd: number | null = null;
|
|
107
45
|
|
|
108
46
|
private onParsedMessage?: (msg: ParsedMessage) => void;
|
|
109
47
|
|
|
@@ -117,10 +55,7 @@ export class EnhancedLogManager {
|
|
|
117
55
|
fs.mkdirSync(logDir, { recursive: true });
|
|
118
56
|
|
|
119
57
|
// Subprocess (lane) logs live in the lane run directory to avoid nesting with main logs.
|
|
120
|
-
|
|
121
|
-
// Set up log file paths (simplified)
|
|
122
|
-
this.rawLogPath = getLaneLogPath(logDir, 'raw');
|
|
123
|
-
this.readableLogPath = getLaneLogPath(logDir, 'readable');
|
|
58
|
+
this.jsonlLogPath = getLaneLogPath(logDir, 'jsonl');
|
|
124
59
|
|
|
125
60
|
// Initialize log files
|
|
126
61
|
this.initLogFiles();
|
|
@@ -147,47 +82,37 @@ export class EnhancedLogManager {
|
|
|
147
82
|
* Initialize log files and write session headers
|
|
148
83
|
*/
|
|
149
84
|
private initLogFiles(): void {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this.readableLogFd = fs.openSync(this.readableLogPath, 'a');
|
|
158
|
-
|
|
159
|
-
// Get initial file size for raw log if enabled
|
|
160
|
-
if (this.rawLogFd !== null) {
|
|
161
|
-
try {
|
|
162
|
-
this.rawLogSize = fs.statSync(this.rawLogPath).size;
|
|
163
|
-
} catch {
|
|
164
|
-
this.rawLogSize = 0;
|
|
165
|
-
}
|
|
85
|
+
if (this.config.writeJsonLog) {
|
|
86
|
+
// Ensure parent directory exists before opening file
|
|
87
|
+
const logDir = path.dirname(this.jsonlLogPath);
|
|
88
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
89
|
+
|
|
90
|
+
this.rotateIfNeeded(this.jsonlLogPath);
|
|
91
|
+
this.jsonlLogFd = fs.openSync(this.jsonlLogPath, 'a');
|
|
166
92
|
}
|
|
167
93
|
|
|
168
|
-
// Write session
|
|
169
|
-
this.
|
|
94
|
+
// Write session start to JSON log
|
|
95
|
+
this.writeSessionStart();
|
|
170
96
|
}
|
|
171
97
|
|
|
172
98
|
/**
|
|
173
|
-
* Write session
|
|
99
|
+
* Write session start to JSON log
|
|
174
100
|
*/
|
|
175
|
-
private
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
this.writeToReadableLog(header);
|
|
101
|
+
private writeSessionStart(): void {
|
|
102
|
+
if (this.config.writeJsonLog) {
|
|
103
|
+
this.writeToJsonLog({
|
|
104
|
+
type: 'session',
|
|
105
|
+
role: 'system',
|
|
106
|
+
content: 'Session started',
|
|
107
|
+
timestamp: this.session.startTime,
|
|
108
|
+
metadata: {
|
|
109
|
+
sessionId: this.session.id,
|
|
110
|
+
laneName: this.session.laneName,
|
|
111
|
+
taskName: this.session.taskName,
|
|
112
|
+
model: this.session.model,
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
191
116
|
}
|
|
192
117
|
|
|
193
118
|
/**
|
|
@@ -234,53 +159,37 @@ export class EnhancedLogManager {
|
|
|
234
159
|
}
|
|
235
160
|
|
|
236
161
|
/**
|
|
237
|
-
* Write to
|
|
162
|
+
* Write to JSONL log
|
|
238
163
|
*/
|
|
239
|
-
private
|
|
240
|
-
if (this.
|
|
241
|
-
|
|
242
|
-
const buffer = typeof data === 'string' ? Buffer.from(data) : data;
|
|
243
|
-
fs.writeSync(this.rawLogFd, buffer);
|
|
244
|
-
this.rawLogSize += buffer.length;
|
|
245
|
-
|
|
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
|
-
/**
|
|
256
|
-
* Write to readable log
|
|
257
|
-
*/
|
|
258
|
-
private writeToReadableLog(data: string): void {
|
|
259
|
-
if (this.readableLogFd === null) return;
|
|
164
|
+
private writeToJsonLog(msg: ParsedMessage): void {
|
|
165
|
+
if (this.jsonlLogFd === null) return;
|
|
260
166
|
|
|
261
167
|
try {
|
|
262
|
-
|
|
168
|
+
// Add lane and task context to JSON log
|
|
169
|
+
const entry = {
|
|
170
|
+
...msg,
|
|
171
|
+
lane: this.session.laneName,
|
|
172
|
+
task: this.session.taskName,
|
|
173
|
+
laneIndex: this.session.laneIndex,
|
|
174
|
+
taskIndex: this.session.taskIndex,
|
|
175
|
+
timestamp_iso: new Date(msg.timestamp).toISOString(),
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
fs.writeSync(this.jsonlLogFd, JSON.stringify(entry) + '\n');
|
|
263
179
|
} catch {
|
|
264
180
|
// Ignore write errors
|
|
265
181
|
}
|
|
266
182
|
}
|
|
267
183
|
|
|
268
184
|
/**
|
|
269
|
-
* Write a parsed message to the
|
|
185
|
+
* Write a parsed message to the JSON log and console
|
|
270
186
|
*/
|
|
271
187
|
public writeReadableMessage(msg: ParsedMessage): void {
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
// Strip ANSI codes and add short timestamp for file output
|
|
280
|
-
const clean = stripAnsi(formatted);
|
|
281
|
-
const ts = this.getShortTime();
|
|
282
|
-
this.writeToReadableLog(`[${ts}] ${clean}\n`);
|
|
283
|
-
|
|
188
|
+
// Write to JSON log
|
|
189
|
+
if (this.config.writeJsonLog) {
|
|
190
|
+
this.writeToJsonLog(msg);
|
|
191
|
+
}
|
|
192
|
+
|
|
284
193
|
// Callback for console output
|
|
285
194
|
if (this.onParsedMessage) {
|
|
286
195
|
this.onParsedMessage(msg);
|
|
@@ -292,12 +201,8 @@ export class EnhancedLogManager {
|
|
|
292
201
|
*/
|
|
293
202
|
public writeStdout(data: Buffer | string): void {
|
|
294
203
|
const text = data.toString();
|
|
295
|
-
|
|
296
|
-
// Write raw log (original data)
|
|
297
|
-
this.writeToRawLog(data);
|
|
298
|
-
|
|
299
|
-
// Parse JSON output and write to readable log
|
|
300
204
|
const lines = text.split('\n');
|
|
205
|
+
|
|
301
206
|
for (const line of lines) {
|
|
302
207
|
const trimmed = line.trim();
|
|
303
208
|
if (!trimmed) continue;
|
|
@@ -313,6 +218,12 @@ export class EnhancedLogManager {
|
|
|
313
218
|
}
|
|
314
219
|
// parseJsonToMessage returned null - create fallback message for known JSON
|
|
315
220
|
if (json.type) {
|
|
221
|
+
// Skip noisy events that we don't want to show
|
|
222
|
+
if (json.type === 'thinking' || json.type === 'call' ||
|
|
223
|
+
(json.type === 'tool_call' && !json.tool_call)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
316
227
|
const fallbackMsg: ParsedMessage = {
|
|
317
228
|
type: 'info',
|
|
318
229
|
role: 'system',
|
|
@@ -327,43 +238,31 @@ export class EnhancedLogManager {
|
|
|
327
238
|
}
|
|
328
239
|
}
|
|
329
240
|
|
|
330
|
-
// Non-JSON line
|
|
241
|
+
// Non-JSON line
|
|
331
242
|
const cleanLine = stripAnsi(trimmed);
|
|
332
243
|
if (cleanLine && !this.isNoiseLog(cleanLine)) {
|
|
333
|
-
|
|
334
|
-
const
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
// For file output, use clean line (no ANSI codes)
|
|
338
|
-
let fileFormattedLine: string;
|
|
339
|
-
if (hasTimestamp) {
|
|
340
|
-
fileFormattedLine = cleanLine.includes(`[${label}]`)
|
|
341
|
-
? cleanLine
|
|
342
|
-
: cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}]`);
|
|
343
|
-
} else {
|
|
344
|
-
fileFormattedLine = `[${ts}] [${label}] ${cleanLine}`;
|
|
345
|
-
}
|
|
244
|
+
// Detect if this looks like Git output
|
|
245
|
+
const isGit = this.isGitOutput(cleanLine);
|
|
246
|
+
const type = isGit ? 'git' : 'stdout';
|
|
346
247
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// For console output, preserve ANSI colors from original line
|
|
248
|
+
// Output to console
|
|
350
249
|
if (this.onParsedMessage) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
consoleFormattedLine = trimmed.includes(`[${label}]`)
|
|
354
|
-
? trimmed
|
|
355
|
-
: trimmed.replace(/^(\[[^\]]+\])/, `$1 [${label}]`);
|
|
356
|
-
} else {
|
|
357
|
-
consoleFormattedLine = `[${ts}] [${label}] ${trimmed}`;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const rawMsg: ParsedMessage = {
|
|
361
|
-
type: 'raw',
|
|
250
|
+
this.onParsedMessage({
|
|
251
|
+
type,
|
|
362
252
|
role: 'system',
|
|
363
|
-
content:
|
|
253
|
+
content: trimmed,
|
|
364
254
|
timestamp: Date.now(),
|
|
365
|
-
};
|
|
366
|
-
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Write to JSON log
|
|
259
|
+
if (this.config.writeJsonLog) {
|
|
260
|
+
this.writeToJsonLog({
|
|
261
|
+
type,
|
|
262
|
+
role: 'system',
|
|
263
|
+
content: cleanLine,
|
|
264
|
+
timestamp: Date.now(),
|
|
265
|
+
});
|
|
367
266
|
}
|
|
368
267
|
}
|
|
369
268
|
}
|
|
@@ -377,14 +276,33 @@ export class EnhancedLogManager {
|
|
|
377
276
|
const timestamp = json.timestamp_ms || Date.now();
|
|
378
277
|
|
|
379
278
|
switch (type) {
|
|
279
|
+
case 'info':
|
|
280
|
+
case 'warn':
|
|
281
|
+
case 'error':
|
|
282
|
+
case 'success':
|
|
283
|
+
case 'debug':
|
|
284
|
+
case 'progress':
|
|
380
285
|
case 'system':
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
286
|
+
case 'git':
|
|
287
|
+
// Handle internal logger JSON or cursor-agent system events
|
|
288
|
+
if (json.content && typeof json.content === 'string') {
|
|
289
|
+
return {
|
|
290
|
+
type: type as any,
|
|
291
|
+
role: 'system',
|
|
292
|
+
content: json.content,
|
|
293
|
+
timestamp,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (type === 'system') {
|
|
297
|
+
return {
|
|
298
|
+
type: 'system',
|
|
299
|
+
role: 'system',
|
|
300
|
+
content: `Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
|
|
301
|
+
timestamp,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
|
|
388
306
|
case 'user':
|
|
389
307
|
if (json.message?.content) {
|
|
390
308
|
const textContent = json.message.content
|
|
@@ -480,51 +398,73 @@ export class EnhancedLogManager {
|
|
|
480
398
|
*/
|
|
481
399
|
public writeStderr(data: Buffer | string): void {
|
|
482
400
|
const text = data.toString();
|
|
483
|
-
|
|
484
|
-
// Write raw log
|
|
485
|
-
this.writeToRawLog(data);
|
|
486
|
-
|
|
487
|
-
// Write to readable log - treat stderr same as stdout
|
|
488
|
-
// Git and many tools use stderr for non-error output
|
|
489
401
|
const lines = text.split('\n');
|
|
490
402
|
for (const line of lines) {
|
|
491
403
|
const cleanLine = stripAnsi(line).trim();
|
|
492
404
|
if (cleanLine && !this.isNoiseLog(cleanLine)) {
|
|
493
|
-
const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
|
|
494
|
-
const label = this.getLaneTaskLabel();
|
|
495
|
-
const ts = this.getShortTime();
|
|
496
|
-
|
|
497
|
-
// Determine if this is actually an error message
|
|
498
405
|
const isError = this.isErrorLine(cleanLine);
|
|
499
|
-
const
|
|
406
|
+
const isGit = this.isGitOutput(cleanLine);
|
|
407
|
+
const type = isError ? 'stderr' : (isGit ? 'git' : 'stderr');
|
|
500
408
|
|
|
501
|
-
|
|
502
|
-
if (
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
:
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
409
|
+
// Write to JSON log
|
|
410
|
+
if (this.config.writeJsonLog) {
|
|
411
|
+
this.writeToJsonLog({
|
|
412
|
+
type,
|
|
413
|
+
role: 'system',
|
|
414
|
+
content: cleanLine,
|
|
415
|
+
timestamp: Date.now(),
|
|
416
|
+
metadata: { isError, isGit }
|
|
417
|
+
});
|
|
510
418
|
}
|
|
511
419
|
|
|
512
|
-
this.writeToReadableLog(`${formattedLine}\n`);
|
|
513
|
-
|
|
514
420
|
// Output to console
|
|
515
421
|
if (this.onParsedMessage) {
|
|
516
|
-
|
|
517
|
-
type
|
|
422
|
+
this.onParsedMessage({
|
|
423
|
+
type,
|
|
518
424
|
role: 'system',
|
|
519
|
-
content:
|
|
425
|
+
content: line.trim(),
|
|
520
426
|
timestamp: Date.now(),
|
|
521
|
-
};
|
|
522
|
-
this.onParsedMessage(rawMsg);
|
|
427
|
+
});
|
|
523
428
|
}
|
|
524
429
|
}
|
|
525
430
|
}
|
|
526
431
|
}
|
|
527
432
|
|
|
433
|
+
/**
|
|
434
|
+
* Check if a line is likely Git output
|
|
435
|
+
*/
|
|
436
|
+
private isGitOutput(text: string): boolean {
|
|
437
|
+
const gitPatterns = [
|
|
438
|
+
/^Running: git /i,
|
|
439
|
+
/^git version /i,
|
|
440
|
+
/^On branch /i,
|
|
441
|
+
/^Your branch is /i,
|
|
442
|
+
/^Changes to be committed:/i,
|
|
443
|
+
/^Changes not staged for commit:/i,
|
|
444
|
+
/^Untracked files:/i,
|
|
445
|
+
/^\s*\((use "git |use --|use -)/i,
|
|
446
|
+
/^\s*modified:\s+/i,
|
|
447
|
+
/^\s*new file:\s+/i,
|
|
448
|
+
/^\s*deleted:\s+/i,
|
|
449
|
+
/^\s*renamed:\s+/i,
|
|
450
|
+
/^Switched to branch /i,
|
|
451
|
+
/^Switched to a new branch /i,
|
|
452
|
+
/^Merge made by the /i,
|
|
453
|
+
/^Everything up-to-date/i,
|
|
454
|
+
/^Already up to date/i,
|
|
455
|
+
/^branch '.*' set up to track remote branch '.*' from 'origin'/i,
|
|
456
|
+
/^Counting objects: /i,
|
|
457
|
+
/^Compressing objects: /i,
|
|
458
|
+
/^Writing objects: /i,
|
|
459
|
+
/^Total \d+ \(delta \d+\)/i,
|
|
460
|
+
/^remote: Resolving deltas:/i,
|
|
461
|
+
/^To .*\.git/i,
|
|
462
|
+
/^[a-f0-9]{7,40}\.\.[a-f0-9]{7,40}\s+.*->\s+.*/i,
|
|
463
|
+
];
|
|
464
|
+
|
|
465
|
+
return gitPatterns.some(p => p.test(text));
|
|
466
|
+
}
|
|
467
|
+
|
|
528
468
|
/**
|
|
529
469
|
* Check if a line is actually an error message
|
|
530
470
|
*/
|
|
@@ -550,24 +490,43 @@ export class EnhancedLogManager {
|
|
|
550
490
|
* Write a custom log entry
|
|
551
491
|
*/
|
|
552
492
|
public log(level: 'info' | 'error' | 'debug', message: string, metadata?: Record<string, any>): void {
|
|
553
|
-
const
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
493
|
+
const isGit = metadata?.context === 'git';
|
|
494
|
+
const type = isGit ? 'git' : (level as any);
|
|
495
|
+
|
|
496
|
+
if (this.config.writeJsonLog) {
|
|
497
|
+
this.writeToJsonLog({
|
|
498
|
+
type,
|
|
499
|
+
role: 'system',
|
|
500
|
+
content: message,
|
|
501
|
+
timestamp: Date.now(),
|
|
502
|
+
metadata
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (this.onParsedMessage) {
|
|
507
|
+
this.onParsedMessage({
|
|
508
|
+
type,
|
|
509
|
+
role: 'system',
|
|
510
|
+
content: message,
|
|
511
|
+
timestamp: Date.now(),
|
|
512
|
+
metadata
|
|
513
|
+
});
|
|
514
|
+
}
|
|
560
515
|
}
|
|
561
516
|
|
|
562
517
|
/**
|
|
563
518
|
* Add a section marker
|
|
564
519
|
*/
|
|
565
520
|
public section(title: string): void {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
521
|
+
if (this.config.writeJsonLog) {
|
|
522
|
+
this.writeToJsonLog({
|
|
523
|
+
type: 'info',
|
|
524
|
+
role: 'system',
|
|
525
|
+
content: `--- ${title} ---`,
|
|
526
|
+
timestamp: Date.now(),
|
|
527
|
+
metadata: { isSection: true }
|
|
528
|
+
});
|
|
529
|
+
}
|
|
571
530
|
}
|
|
572
531
|
|
|
573
532
|
/**
|
|
@@ -605,11 +564,10 @@ export class EnhancedLogManager {
|
|
|
605
564
|
/**
|
|
606
565
|
* Get paths to all log files
|
|
607
566
|
*/
|
|
608
|
-
public getLogPaths(): {
|
|
567
|
+
public getLogPaths(): { jsonl: string; clean: string } {
|
|
609
568
|
return {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
readable: this.readableLogPath,
|
|
569
|
+
jsonl: this.jsonlLogPath,
|
|
570
|
+
clean: this.jsonlLogPath, // Backward compatibility
|
|
613
571
|
};
|
|
614
572
|
}
|
|
615
573
|
|
|
@@ -617,7 +575,7 @@ export class EnhancedLogManager {
|
|
|
617
575
|
* Create file descriptors for process stdio redirection
|
|
618
576
|
*/
|
|
619
577
|
public getFileDescriptors(): { stdout: number; stderr: number } {
|
|
620
|
-
const fd = this.
|
|
578
|
+
const fd = this.jsonlLogFd!;
|
|
621
579
|
return { stdout: fd, stderr: fd };
|
|
622
580
|
}
|
|
623
581
|
|
|
@@ -625,31 +583,23 @@ export class EnhancedLogManager {
|
|
|
625
583
|
* Close all log files
|
|
626
584
|
*/
|
|
627
585
|
public close(): void {
|
|
628
|
-
|
|
629
|
-
const endMarker = `
|
|
630
|
-
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
631
|
-
║ Session Ended: ${new Date().toISOString().padEnd(60)}║
|
|
632
|
-
║ Duration: ${this.formatDuration(Date.now() - this.session.startTime).padEnd(65)}║
|
|
633
|
-
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
634
|
-
|
|
635
|
-
`;
|
|
636
|
-
|
|
637
|
-
if (this.rawLogFd !== null) {
|
|
586
|
+
if (this.jsonlLogFd !== null) {
|
|
638
587
|
try {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
fs.
|
|
588
|
+
if (this.config.writeJsonLog) {
|
|
589
|
+
this.writeToJsonLog({
|
|
590
|
+
type: 'session',
|
|
591
|
+
role: 'system',
|
|
592
|
+
content: 'Session ended',
|
|
593
|
+
timestamp: Date.now(),
|
|
594
|
+
metadata: {
|
|
595
|
+
duration: Date.now() - this.session.startTime,
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
fs.fsyncSync(this.jsonlLogFd);
|
|
600
|
+
fs.closeSync(this.jsonlLogFd);
|
|
651
601
|
} catch {}
|
|
652
|
-
this.
|
|
602
|
+
this.jsonlLogFd = null;
|
|
653
603
|
}
|
|
654
604
|
}
|
|
655
605
|
|
|
@@ -658,40 +608,22 @@ export class EnhancedLogManager {
|
|
|
658
608
|
*/
|
|
659
609
|
public getLastError(): string | null {
|
|
660
610
|
try {
|
|
661
|
-
if (!fs.existsSync(this.
|
|
662
|
-
const content = fs.readFileSync(this.
|
|
663
|
-
const lines = content.split('\n').filter(l =>
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const allLines = content.split('\n').filter(l => l.trim());
|
|
672
|
-
return allLines.slice(-5).join('\n');
|
|
611
|
+
if (!fs.existsSync(this.jsonlLogPath)) return null;
|
|
612
|
+
const content = fs.readFileSync(this.jsonlLogPath, 'utf8');
|
|
613
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
614
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
615
|
+
try {
|
|
616
|
+
const entry = JSON.parse(lines[i]!);
|
|
617
|
+
if (entry.type === 'error' || entry.type === 'stderr' && entry.metadata?.isError) {
|
|
618
|
+
return entry.content;
|
|
619
|
+
}
|
|
620
|
+
} catch {}
|
|
673
621
|
}
|
|
674
|
-
return
|
|
622
|
+
return null;
|
|
675
623
|
} catch {
|
|
676
624
|
return null;
|
|
677
625
|
}
|
|
678
626
|
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* Format duration for display
|
|
682
|
-
*/
|
|
683
|
-
private formatDuration(ms: number): string {
|
|
684
|
-
const seconds = Math.floor((ms / 1000) % 60);
|
|
685
|
-
const minutes = Math.floor((ms / (1000 * 60)) % 60);
|
|
686
|
-
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
687
|
-
|
|
688
|
-
if (hours > 0) {
|
|
689
|
-
return `${hours}h ${minutes}m ${seconds}s`;
|
|
690
|
-
} else if (minutes > 0) {
|
|
691
|
-
return `${minutes}m ${seconds}s`;
|
|
692
|
-
}
|
|
693
|
-
return `${seconds}s`;
|
|
694
|
-
}
|
|
695
627
|
}
|
|
696
628
|
|
|
697
629
|
/**
|
|
@@ -716,26 +648,43 @@ export function createLogManager(
|
|
|
716
648
|
}
|
|
717
649
|
|
|
718
650
|
/**
|
|
719
|
-
* Read and parse JSON log file
|
|
651
|
+
* Read and parse JSON log file
|
|
720
652
|
*/
|
|
721
|
-
export function readJsonLog(logPath: string):
|
|
722
|
-
|
|
653
|
+
export function readJsonLog(logPath: string): any[] {
|
|
654
|
+
try {
|
|
655
|
+
if (!fs.existsSync(logPath)) return [];
|
|
656
|
+
const content = fs.readFileSync(logPath, 'utf8');
|
|
657
|
+
return content.split('\n')
|
|
658
|
+
.filter(l => l.trim())
|
|
659
|
+
.map(l => {
|
|
660
|
+
try { return JSON.parse(l); } catch { return null; }
|
|
661
|
+
})
|
|
662
|
+
.filter(Boolean);
|
|
663
|
+
} catch {
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
723
666
|
}
|
|
724
667
|
|
|
725
668
|
/**
|
|
726
|
-
* Export logs
|
|
669
|
+
* Export logs
|
|
727
670
|
*/
|
|
728
671
|
export function exportLogs(
|
|
729
672
|
laneRunDir: string,
|
|
730
673
|
format: 'text' | 'json' | 'markdown' | 'html',
|
|
731
674
|
outputPath?: string
|
|
732
675
|
): string {
|
|
733
|
-
const logPath = getLaneLogPath(laneRunDir, '
|
|
676
|
+
const logPath = getLaneLogPath(laneRunDir, 'jsonl');
|
|
734
677
|
|
|
735
678
|
let output = '';
|
|
736
|
-
|
|
737
679
|
if (fs.existsSync(logPath)) {
|
|
738
|
-
|
|
680
|
+
if (format === 'json') {
|
|
681
|
+
const logs = readJsonLog(logPath);
|
|
682
|
+
output = JSON.stringify(logs, null, 2);
|
|
683
|
+
} else {
|
|
684
|
+
// Basic text conversion for other formats
|
|
685
|
+
const logs = readJsonLog(logPath);
|
|
686
|
+
output = logs.map(l => `[${l.timestamp_iso}] [${l.type}] ${l.content}`).join('\n');
|
|
687
|
+
}
|
|
739
688
|
}
|
|
740
689
|
|
|
741
690
|
if (outputPath) {
|