@litmers/cursorflow-orchestrator 0.1.31 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +182 -59
- package/commands/cursorflow-add.md +159 -0
- package/commands/cursorflow-doctor.md +45 -23
- package/commands/cursorflow-monitor.md +23 -2
- package/commands/cursorflow-new.md +87 -0
- package/commands/cursorflow-run.md +60 -111
- package/dist/cli/add.d.ts +7 -0
- package/dist/cli/add.js +377 -0
- package/dist/cli/add.js.map +1 -0
- package/dist/cli/clean.js +1 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/config.d.ts +7 -0
- package/dist/cli/config.js +181 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/doctor.js +47 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +34 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/logs.js +17 -34
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +62 -65
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/new.d.ts +7 -0
- package/dist/cli/new.js +232 -0
- package/dist/cli/new.js.map +1 -0
- package/dist/cli/prepare.js +95 -193
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +57 -68
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +60 -30
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/stop.js +6 -0
- package/dist/cli/stop.js.map +1 -1
- package/dist/cli/tasks.d.ts +5 -3
- package/dist/cli/tasks.js +181 -29
- package/dist/cli/tasks.js.map +1 -1
- package/dist/core/failure-policy.d.ts +9 -0
- package/dist/core/failure-policy.js +9 -0
- package/dist/core/failure-policy.js.map +1 -1
- package/dist/core/orchestrator.d.ts +20 -6
- package/dist/core/orchestrator.js +215 -334
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.d.ts +27 -0
- package/dist/core/runner/agent.js +294 -0
- package/dist/core/runner/agent.js.map +1 -0
- package/dist/core/runner/index.d.ts +5 -0
- package/dist/core/runner/index.js +22 -0
- package/dist/core/runner/index.js.map +1 -0
- package/dist/core/runner/pipeline.d.ts +9 -0
- package/dist/core/runner/pipeline.js +539 -0
- package/dist/core/runner/pipeline.js.map +1 -0
- package/dist/core/runner/prompt.d.ts +25 -0
- package/dist/core/runner/prompt.js +175 -0
- package/dist/core/runner/prompt.js.map +1 -0
- package/dist/core/runner/task.d.ts +26 -0
- package/dist/core/runner/task.js +283 -0
- package/dist/core/runner/task.js.map +1 -0
- package/dist/core/runner/utils.d.ts +37 -0
- package/dist/core/runner/utils.js +161 -0
- package/dist/core/runner/utils.js.map +1 -0
- package/dist/core/runner.d.ts +2 -96
- package/dist/core/runner.js +11 -1136
- package/dist/core/runner.js.map +1 -1
- package/dist/core/stall-detection.d.ts +326 -0
- package/dist/core/stall-detection.js +781 -0
- package/dist/core/stall-detection.js.map +1 -0
- package/dist/services/logging/console.js +2 -1
- package/dist/services/logging/console.js.map +1 -1
- package/dist/types/config.d.ts +6 -6
- package/dist/types/flow.d.ts +84 -0
- package/dist/types/flow.js +10 -0
- package/dist/types/flow.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +3 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/lane.d.ts +0 -2
- package/dist/types/logging.d.ts +5 -1
- package/dist/types/task.d.ts +7 -11
- package/dist/utils/config.d.ts +5 -1
- package/dist/utils/config.js +15 -16
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/dependency.d.ts +36 -1
- package/dist/utils/dependency.js +256 -1
- package/dist/utils/dependency.js.map +1 -1
- package/dist/utils/doctor.js +40 -8
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +45 -82
- package/dist/utils/enhanced-logger.js +239 -844
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/flow.d.ts +9 -0
- package/dist/utils/flow.js +73 -0
- package/dist/utils/flow.js.map +1 -0
- package/dist/utils/git.d.ts +29 -0
- package/dist/utils/git.js +115 -5
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/state.js +0 -2
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +2 -2
- package/dist/utils/task-service.js +40 -31
- package/dist/utils/task-service.js.map +1 -1
- package/package.json +4 -3
- package/src/cli/add.ts +397 -0
- package/src/cli/clean.ts +1 -0
- package/src/cli/config.ts +177 -0
- package/src/cli/doctor.ts +48 -4
- package/src/cli/index.ts +36 -32
- package/src/cli/logs.ts +20 -33
- package/src/cli/monitor.ts +70 -75
- package/src/cli/new.ts +235 -0
- package/src/cli/prepare.ts +98 -205
- package/src/cli/resume.ts +61 -76
- package/src/cli/run.ts +333 -306
- package/src/cli/stop.ts +8 -0
- package/src/cli/tasks.ts +200 -21
- package/src/core/failure-policy.ts +9 -0
- package/src/core/orchestrator.ts +279 -379
- package/src/core/runner/agent.ts +314 -0
- package/src/core/runner/index.ts +6 -0
- package/src/core/runner/pipeline.ts +567 -0
- package/src/core/runner/prompt.ts +174 -0
- package/src/core/runner/task.ts +320 -0
- package/src/core/runner/utils.ts +142 -0
- package/src/core/runner.ts +8 -1347
- package/src/core/stall-detection.ts +936 -0
- package/src/services/logging/console.ts +2 -1
- package/src/types/config.ts +6 -6
- package/src/types/flow.ts +91 -0
- package/src/types/index.ts +15 -3
- package/src/types/lane.ts +0 -2
- package/src/types/logging.ts +5 -1
- package/src/types/task.ts +7 -11
- package/src/utils/config.ts +16 -17
- package/src/utils/dependency.ts +311 -2
- package/src/utils/doctor.ts +36 -8
- package/src/utils/enhanced-logger.ts +264 -927
- package/src/utils/flow.ts +42 -0
- package/src/utils/git.ts +145 -5
- package/src/utils/state.ts +0 -2
- package/src/utils/task-service.ts +48 -40
- package/commands/cursorflow-review.md +0 -56
- package/commands/cursorflow-runs.md +0 -59
- package/dist/cli/runs.d.ts +0 -5
- package/dist/cli/runs.js +0 -214
- package/dist/cli/runs.js.map +0 -1
- package/dist/core/reviewer.d.ts +0 -66
- package/dist/core/reviewer.js +0 -265
- package/dist/core/reviewer.js.map +0 -1
- package/src/cli/runs.ts +0 -212
- package/src/core/reviewer.ts +0 -285
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Enhanced Logger -
|
|
3
|
+
* Enhanced Logger - Simplified terminal output capture
|
|
4
4
|
*
|
|
5
5
|
* Features:
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - Log rotation and size management
|
|
9
|
-
* - Session headers with context
|
|
10
|
-
* - Raw and clean log file options
|
|
11
|
-
* - Structured JSON logs for programmatic access
|
|
12
|
-
* - Streaming output support for real-time capture
|
|
6
|
+
* - Raw log: Original output as-is
|
|
7
|
+
* - Readable log: Formatted with formatMessageForConsole style
|
|
13
8
|
*/
|
|
14
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
10
|
if (k2 === undefined) k2 = k;
|
|
@@ -45,7 +40,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
45
40
|
};
|
|
46
41
|
})();
|
|
47
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
-
exports.
|
|
43
|
+
exports.CleanLogTransform = exports.StreamingMessageParser = exports.EnhancedLogManager = exports.DEFAULT_LOG_CONFIG = void 0;
|
|
49
44
|
exports.stripAnsi = stripAnsi;
|
|
50
45
|
exports.formatTimestamp = formatTimestamp;
|
|
51
46
|
exports.createLogManager = createLogManager;
|
|
@@ -53,8 +48,7 @@ exports.readJsonLog = readJsonLog;
|
|
|
53
48
|
exports.exportLogs = exportLogs;
|
|
54
49
|
const fs = __importStar(require("fs"));
|
|
55
50
|
const path = __importStar(require("path"));
|
|
56
|
-
const
|
|
57
|
-
const logger = __importStar(require("./logger"));
|
|
51
|
+
const log_formatter_1 = require("./log-formatter");
|
|
58
52
|
const path_1 = require("./path");
|
|
59
53
|
exports.DEFAULT_LOG_CONFIG = {
|
|
60
54
|
enabled: true,
|
|
@@ -65,181 +59,13 @@ exports.DEFAULT_LOG_CONFIG = {
|
|
|
65
59
|
keepRawLogs: true,
|
|
66
60
|
keepAbsoluteRawLogs: false,
|
|
67
61
|
raw: false,
|
|
68
|
-
writeJsonLog:
|
|
62
|
+
writeJsonLog: false, // Disabled by default now
|
|
69
63
|
timestampFormat: 'iso',
|
|
70
64
|
};
|
|
71
|
-
/**
|
|
72
|
-
* Streaming JSON Parser - Parses cursor-agent stream-json output
|
|
73
|
-
* and combines tokens into readable messages
|
|
74
|
-
*/
|
|
75
|
-
class StreamingMessageParser {
|
|
76
|
-
currentMessage = '';
|
|
77
|
-
currentRole = '';
|
|
78
|
-
messageStartTime = 0;
|
|
79
|
-
onMessage;
|
|
80
|
-
constructor(onMessage) {
|
|
81
|
-
this.onMessage = onMessage;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Parse a line of JSON output from cursor-agent
|
|
85
|
-
*/
|
|
86
|
-
parseLine(line) {
|
|
87
|
-
const trimmed = line.trim();
|
|
88
|
-
if (!trimmed || !trimmed.startsWith('{'))
|
|
89
|
-
return;
|
|
90
|
-
try {
|
|
91
|
-
const json = JSON.parse(trimmed);
|
|
92
|
-
this.handleJsonMessage(json);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
// Not valid JSON, ignore
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
handleJsonMessage(json) {
|
|
99
|
-
const type = json.type;
|
|
100
|
-
switch (type) {
|
|
101
|
-
case 'system':
|
|
102
|
-
// System init message
|
|
103
|
-
this.emitMessage({
|
|
104
|
-
type: 'system',
|
|
105
|
-
role: 'system',
|
|
106
|
-
content: `[System] Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
|
|
107
|
-
timestamp: json.timestamp_ms || Date.now(),
|
|
108
|
-
});
|
|
109
|
-
break;
|
|
110
|
-
case 'user':
|
|
111
|
-
// User message - emit as complete message
|
|
112
|
-
if (json.message?.content) {
|
|
113
|
-
const textContent = json.message.content
|
|
114
|
-
.filter((c) => c.type === 'text')
|
|
115
|
-
.map((c) => c.text)
|
|
116
|
-
.join('');
|
|
117
|
-
this.emitMessage({
|
|
118
|
-
type: 'user',
|
|
119
|
-
role: 'user',
|
|
120
|
-
content: textContent,
|
|
121
|
-
timestamp: json.timestamp_ms || Date.now(),
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
break;
|
|
125
|
-
case 'assistant':
|
|
126
|
-
// Streaming assistant message - accumulate tokens
|
|
127
|
-
if (json.message?.content) {
|
|
128
|
-
const textContent = json.message.content
|
|
129
|
-
.filter((c) => c.type === 'text')
|
|
130
|
-
.map((c) => c.text)
|
|
131
|
-
.join('');
|
|
132
|
-
// Check if this is a new message or continuation
|
|
133
|
-
if (this.currentRole !== 'assistant') {
|
|
134
|
-
// Flush previous message if any
|
|
135
|
-
this.flush();
|
|
136
|
-
this.currentRole = 'assistant';
|
|
137
|
-
this.messageStartTime = json.timestamp_ms || Date.now();
|
|
138
|
-
}
|
|
139
|
-
this.currentMessage += textContent;
|
|
140
|
-
}
|
|
141
|
-
break;
|
|
142
|
-
case 'tool_call':
|
|
143
|
-
// Tool call - emit as formatted message
|
|
144
|
-
if (json.subtype === 'started' && json.tool_call) {
|
|
145
|
-
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
146
|
-
const toolArgs = json.tool_call[toolName]?.args || {};
|
|
147
|
-
this.flush(); // Flush any pending assistant message
|
|
148
|
-
this.emitMessage({
|
|
149
|
-
type: 'tool',
|
|
150
|
-
role: 'tool',
|
|
151
|
-
content: `[Tool: ${toolName}] ${JSON.stringify(toolArgs)}`,
|
|
152
|
-
timestamp: json.timestamp_ms || Date.now(),
|
|
153
|
-
metadata: { callId: json.call_id, toolName },
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
else if (json.subtype === 'completed' && json.tool_call) {
|
|
157
|
-
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
158
|
-
const result = json.tool_call[toolName]?.result;
|
|
159
|
-
if (result?.success) {
|
|
160
|
-
// Truncate large results
|
|
161
|
-
const content = result.success.content || '';
|
|
162
|
-
const truncated = content.length > 500
|
|
163
|
-
? content.substring(0, 500) + '... (truncated)'
|
|
164
|
-
: content;
|
|
165
|
-
this.emitMessage({
|
|
166
|
-
type: 'tool_result',
|
|
167
|
-
role: 'tool',
|
|
168
|
-
content: `[Tool Result: ${toolName}] ${truncated}`,
|
|
169
|
-
timestamp: json.timestamp_ms || Date.now(),
|
|
170
|
-
metadata: { callId: json.call_id, toolName, lines: result.success.totalLines },
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
break;
|
|
175
|
-
case 'result':
|
|
176
|
-
// Final result - flush any pending and emit result
|
|
177
|
-
this.flush();
|
|
178
|
-
this.emitMessage({
|
|
179
|
-
type: 'result',
|
|
180
|
-
role: 'assistant',
|
|
181
|
-
content: json.result || '',
|
|
182
|
-
timestamp: json.timestamp_ms || Date.now(),
|
|
183
|
-
metadata: {
|
|
184
|
-
duration_ms: json.duration_ms,
|
|
185
|
-
is_error: json.is_error,
|
|
186
|
-
subtype: json.subtype,
|
|
187
|
-
},
|
|
188
|
-
});
|
|
189
|
-
break;
|
|
190
|
-
case 'thinking':
|
|
191
|
-
// Thinking message (Claude 3.7+ etc.)
|
|
192
|
-
if (json.subtype === 'delta' && json.text) {
|
|
193
|
-
// Check if this is a new message or continuation
|
|
194
|
-
if (this.currentRole !== 'thinking') {
|
|
195
|
-
// Flush previous message if any
|
|
196
|
-
this.flush();
|
|
197
|
-
this.currentRole = 'thinking';
|
|
198
|
-
this.messageStartTime = json.timestamp_ms || Date.now();
|
|
199
|
-
}
|
|
200
|
-
this.currentMessage += json.text;
|
|
201
|
-
}
|
|
202
|
-
else if (json.subtype === 'completed') {
|
|
203
|
-
// Thinking completed - flush immediately
|
|
204
|
-
this.flush();
|
|
205
|
-
}
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Flush accumulated message
|
|
211
|
-
*/
|
|
212
|
-
flush() {
|
|
213
|
-
if (this.currentMessage && this.currentRole) {
|
|
214
|
-
this.emitMessage({
|
|
215
|
-
type: this.currentRole,
|
|
216
|
-
role: this.currentRole,
|
|
217
|
-
content: this.currentMessage,
|
|
218
|
-
timestamp: this.messageStartTime,
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
this.currentMessage = '';
|
|
222
|
-
this.currentRole = '';
|
|
223
|
-
this.messageStartTime = 0;
|
|
224
|
-
}
|
|
225
|
-
emitMessage(msg) {
|
|
226
|
-
if (msg.content.trim()) {
|
|
227
|
-
this.onMessage(msg);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
exports.StreamingMessageParser = StreamingMessageParser;
|
|
232
65
|
/**
|
|
233
66
|
* ANSI escape sequence regex pattern
|
|
234
|
-
* Matches:
|
|
235
|
-
* - CSI sequences (colors, cursor movement, etc.)
|
|
236
|
-
* - OSC sequences (terminal titles, etc.)
|
|
237
|
-
* - Single-character escape codes
|
|
238
67
|
*/
|
|
239
68
|
const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
|
240
|
-
/**
|
|
241
|
-
* Extended ANSI regex for more complete stripping
|
|
242
|
-
*/
|
|
243
69
|
const EXTENDED_ANSI_REGEX = /(?:\x1B[@-Z\\-_]|\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)|\x1B[PX^_][^\x1B]*\x1B\\|\x1B.)/g;
|
|
244
70
|
/**
|
|
245
71
|
* Strip ANSI escape sequences from text
|
|
@@ -248,13 +74,11 @@ function stripAnsi(text) {
|
|
|
248
74
|
return text
|
|
249
75
|
.replace(EXTENDED_ANSI_REGEX, '')
|
|
250
76
|
.replace(ANSI_REGEX, '')
|
|
251
|
-
// Also remove carriage returns that overwrite lines (progress bars, etc.)
|
|
252
77
|
.replace(/\r[^\n]/g, '\n')
|
|
253
|
-
// Clean up any remaining control characters except newlines/tabs
|
|
254
78
|
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
255
79
|
}
|
|
256
80
|
/**
|
|
257
|
-
* Format timestamp
|
|
81
|
+
* Format timestamp
|
|
258
82
|
*/
|
|
259
83
|
function formatTimestamp(format, startTime) {
|
|
260
84
|
const now = Date.now();
|
|
@@ -285,239 +109,68 @@ function formatTimestamp(format, startTime) {
|
|
|
285
109
|
}
|
|
286
110
|
}
|
|
287
111
|
/**
|
|
288
|
-
*
|
|
289
|
-
* Matches:
|
|
290
|
-
* - ISO format: [2024-01-01T12:34:56...]
|
|
291
|
-
* - Short format: [12:34:56]
|
|
292
|
-
*/
|
|
293
|
-
const EXISTING_TIMESTAMP_REGEX = /^\[(\d{4}-\d{2}-\d{2}T)?\d{2}:\d{2}:\d{2}/;
|
|
294
|
-
/**
|
|
295
|
-
* Check if a line already has a timestamp
|
|
296
|
-
*/
|
|
297
|
-
function hasExistingTimestamp(line) {
|
|
298
|
-
return EXISTING_TIMESTAMP_REGEX.test(line.trim());
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Transform stream that strips ANSI and adds timestamps
|
|
302
|
-
*/
|
|
303
|
-
class CleanLogTransform extends stream_1.Transform {
|
|
304
|
-
config;
|
|
305
|
-
session;
|
|
306
|
-
buffer = '';
|
|
307
|
-
constructor(config, session) {
|
|
308
|
-
super({ encoding: 'utf8' });
|
|
309
|
-
this.config = config;
|
|
310
|
-
this.session = session;
|
|
311
|
-
}
|
|
312
|
-
_transform(chunk, encoding, callback) {
|
|
313
|
-
let text = chunk.toString();
|
|
314
|
-
// Buffer partial lines
|
|
315
|
-
this.buffer += text;
|
|
316
|
-
const lines = this.buffer.split('\n');
|
|
317
|
-
// Keep the last incomplete line in buffer
|
|
318
|
-
this.buffer = lines.pop() || '';
|
|
319
|
-
for (const line of lines) {
|
|
320
|
-
let processed = line;
|
|
321
|
-
// Strip ANSI if enabled
|
|
322
|
-
if (this.config.stripAnsi) {
|
|
323
|
-
processed = stripAnsi(processed);
|
|
324
|
-
}
|
|
325
|
-
// Add timestamp if enabled AND line doesn't already have one
|
|
326
|
-
if (this.config.addTimestamps && processed.trim() && !hasExistingTimestamp(processed)) {
|
|
327
|
-
const ts = formatTimestamp(this.config.timestampFormat, this.session.startTime);
|
|
328
|
-
processed = `[${ts}] ${processed}`;
|
|
329
|
-
}
|
|
330
|
-
this.push(processed + '\n');
|
|
331
|
-
}
|
|
332
|
-
callback();
|
|
333
|
-
}
|
|
334
|
-
_flush(callback) {
|
|
335
|
-
// Process any remaining buffered content
|
|
336
|
-
if (this.buffer.trim()) {
|
|
337
|
-
let processed = this.buffer;
|
|
338
|
-
if (this.config.stripAnsi) {
|
|
339
|
-
processed = stripAnsi(processed);
|
|
340
|
-
}
|
|
341
|
-
if (this.config.addTimestamps && processed.trim() && !hasExistingTimestamp(processed)) {
|
|
342
|
-
const ts = formatTimestamp(this.config.timestampFormat, this.session.startTime);
|
|
343
|
-
processed = `[${ts}] ${processed}`;
|
|
344
|
-
}
|
|
345
|
-
this.push(processed + '\n');
|
|
346
|
-
}
|
|
347
|
-
callback();
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
exports.CleanLogTransform = CleanLogTransform;
|
|
351
|
-
/**
|
|
352
|
-
* Enhanced Log Manager - Manages log files with rotation and multiple outputs
|
|
112
|
+
* Simplified Log Manager - Only raw and readable logs
|
|
353
113
|
*/
|
|
354
114
|
class EnhancedLogManager {
|
|
355
115
|
config;
|
|
356
116
|
session;
|
|
357
117
|
logDir;
|
|
358
|
-
cleanLogPath;
|
|
359
118
|
rawLogPath;
|
|
360
|
-
absoluteRawLogPath;
|
|
361
|
-
jsonLogPath;
|
|
362
119
|
readableLogPath;
|
|
363
|
-
cleanLogFd = null;
|
|
364
120
|
rawLogFd = null;
|
|
365
|
-
absoluteRawLogFd = null;
|
|
366
|
-
jsonLogFd = null;
|
|
367
121
|
readableLogFd = null;
|
|
368
|
-
cleanLogSize = 0;
|
|
369
122
|
rawLogSize = 0;
|
|
370
|
-
absoluteRawLogSize = 0;
|
|
371
|
-
cleanTransform = null;
|
|
372
|
-
streamingParser = null;
|
|
373
|
-
lineBuffer = '';
|
|
374
123
|
onParsedMessage;
|
|
375
124
|
constructor(logDir, session, config = {}, onParsedMessage) {
|
|
376
125
|
this.config = { ...exports.DEFAULT_LOG_CONFIG, ...config };
|
|
377
|
-
// Support 'raw' as alias for 'keepAbsoluteRawLogs'
|
|
378
|
-
if (this.config.raw) {
|
|
379
|
-
this.config.keepAbsoluteRawLogs = true;
|
|
380
|
-
}
|
|
381
126
|
this.session = session;
|
|
382
127
|
this.logDir = logDir;
|
|
383
128
|
this.onParsedMessage = onParsedMessage;
|
|
384
129
|
// Ensure log directory exists
|
|
385
130
|
fs.mkdirSync(logDir, { recursive: true });
|
|
386
|
-
// Set up log file paths
|
|
387
|
-
this.cleanLogPath = (0, path_1.safeJoin)(logDir, 'terminal.log');
|
|
131
|
+
// Set up log file paths (simplified)
|
|
388
132
|
this.rawLogPath = (0, path_1.safeJoin)(logDir, 'terminal-raw.log');
|
|
389
|
-
this.absoluteRawLogPath = (0, path_1.safeJoin)(logDir, 'terminal-absolute-raw.log');
|
|
390
|
-
this.jsonLogPath = (0, path_1.safeJoin)(logDir, 'terminal.jsonl');
|
|
391
133
|
this.readableLogPath = (0, path_1.safeJoin)(logDir, 'terminal-readable.log');
|
|
392
|
-
if (this.config.raw) {
|
|
393
|
-
logger.info(`[${session.laneName}] 📄 Raw data capture enabled: ${this.absoluteRawLogPath}`);
|
|
394
|
-
}
|
|
395
134
|
// Initialize log files
|
|
396
135
|
this.initLogFiles();
|
|
397
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Get short time format (HH:MM:SS)
|
|
139
|
+
*/
|
|
140
|
+
getShortTime() {
|
|
141
|
+
return new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get lane-task label like [L1-T2-lanename10]
|
|
145
|
+
*/
|
|
146
|
+
getLaneTaskLabel() {
|
|
147
|
+
const laneNum = (this.session.laneIndex ?? 0) + 1;
|
|
148
|
+
const taskNum = (this.session.taskIndex ?? 0) + 1;
|
|
149
|
+
const shortLaneName = this.session.laneName.substring(0, 10);
|
|
150
|
+
return `L${laneNum}-T${taskNum}-${shortLaneName}`;
|
|
151
|
+
}
|
|
398
152
|
/**
|
|
399
153
|
* Initialize log files and write session headers
|
|
400
154
|
*/
|
|
401
155
|
initLogFiles() {
|
|
402
156
|
// Check and rotate if necessary
|
|
403
|
-
this.rotateIfNeeded(this.cleanLogPath, 'clean');
|
|
404
|
-
if (this.config.keepRawLogs) {
|
|
405
|
-
this.rotateIfNeeded(this.rawLogPath, 'raw');
|
|
406
|
-
}
|
|
407
|
-
if (this.config.keepAbsoluteRawLogs) {
|
|
408
|
-
this.rotateIfNeeded(this.absoluteRawLogPath, 'raw');
|
|
409
|
-
}
|
|
410
|
-
// Open file descriptors
|
|
411
|
-
this.cleanLogFd = fs.openSync(this.cleanLogPath, 'a');
|
|
412
157
|
if (this.config.keepRawLogs) {
|
|
158
|
+
this.rotateIfNeeded(this.rawLogPath);
|
|
413
159
|
this.rawLogFd = fs.openSync(this.rawLogPath, 'a');
|
|
414
160
|
}
|
|
415
|
-
|
|
416
|
-
this.absoluteRawLogFd = fs.openSync(this.absoluteRawLogPath, 'a');
|
|
417
|
-
}
|
|
418
|
-
if (this.config.writeJsonLog) {
|
|
419
|
-
this.jsonLogFd = fs.openSync(this.jsonLogPath, 'a');
|
|
420
|
-
}
|
|
421
|
-
// Open readable log file (for parsed streaming output)
|
|
161
|
+
this.rotateIfNeeded(this.readableLogPath);
|
|
422
162
|
this.readableLogFd = fs.openSync(this.readableLogPath, 'a');
|
|
423
|
-
// Get initial file
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
if (this.config.keepRawLogs) {
|
|
163
|
+
// Get initial file size for raw log if enabled
|
|
164
|
+
if (this.rawLogFd !== null) {
|
|
165
|
+
try {
|
|
427
166
|
this.rawLogSize = fs.statSync(this.rawLogPath).size;
|
|
428
167
|
}
|
|
429
|
-
|
|
430
|
-
this.
|
|
168
|
+
catch {
|
|
169
|
+
this.rawLogSize = 0;
|
|
431
170
|
}
|
|
432
171
|
}
|
|
433
|
-
catch {
|
|
434
|
-
this.cleanLogSize = 0;
|
|
435
|
-
this.rawLogSize = 0;
|
|
436
|
-
this.absoluteRawLogSize = 0;
|
|
437
|
-
}
|
|
438
172
|
// Write session header
|
|
439
173
|
this.writeSessionHeader();
|
|
440
|
-
// Create transform stream
|
|
441
|
-
this.cleanTransform = new CleanLogTransform(this.config, this.session);
|
|
442
|
-
this.cleanTransform.on('data', (data) => {
|
|
443
|
-
this.writeToCleanLog(data);
|
|
444
|
-
});
|
|
445
|
-
// Create streaming parser for readable log
|
|
446
|
-
this.streamingParser = new StreamingMessageParser((msg) => {
|
|
447
|
-
this.writeReadableMessage(msg);
|
|
448
|
-
if (this.onParsedMessage) {
|
|
449
|
-
this.onParsedMessage(msg);
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Write a parsed message to the readable log
|
|
455
|
-
*/
|
|
456
|
-
writeReadableMessage(msg) {
|
|
457
|
-
if (this.readableLogFd === null)
|
|
458
|
-
return;
|
|
459
|
-
const ts = new Date(msg.timestamp).toISOString();
|
|
460
|
-
let formatted;
|
|
461
|
-
switch (msg.type) {
|
|
462
|
-
case 'system':
|
|
463
|
-
formatted = `[${ts}] ⚙️ SYSTEM: ${msg.content}\n`;
|
|
464
|
-
break;
|
|
465
|
-
case 'user':
|
|
466
|
-
case 'assistant':
|
|
467
|
-
case 'result':
|
|
468
|
-
// Format with brackets and line (compact)
|
|
469
|
-
const isUser = msg.type === 'user';
|
|
470
|
-
const isResult = msg.type === 'result';
|
|
471
|
-
const headerText = isUser ? '🧑 USER' : isResult ? '🤖 ASSISTANT (Final)' : '🤖 ASSISTANT';
|
|
472
|
-
const duration = msg.metadata?.duration_ms
|
|
473
|
-
? ` (${Math.round(msg.metadata.duration_ms / 1000)}s)`
|
|
474
|
-
: '';
|
|
475
|
-
const label = `[ ${headerText}${duration} ] `;
|
|
476
|
-
const totalWidth = 80;
|
|
477
|
-
const topBorder = `┌─${label}${'─'.repeat(Math.max(0, totalWidth - label.length - 2))}`;
|
|
478
|
-
const bottomBorder = `└─${'─'.repeat(totalWidth - 2)}`;
|
|
479
|
-
const lines = msg.content.split('\n');
|
|
480
|
-
formatted = `[${ts}] ${topBorder}\n`;
|
|
481
|
-
for (const line of lines) {
|
|
482
|
-
formatted += `[${ts}] │ ${line}\n`;
|
|
483
|
-
}
|
|
484
|
-
formatted += `[${ts}] ${bottomBorder}\n`;
|
|
485
|
-
break;
|
|
486
|
-
case 'tool':
|
|
487
|
-
formatted = `[${ts}] 🔧 TOOL: ${msg.content}\n`;
|
|
488
|
-
break;
|
|
489
|
-
case 'tool_result':
|
|
490
|
-
const toolResultLines = msg.metadata?.lines ? ` (${msg.metadata.lines} lines)` : '';
|
|
491
|
-
formatted = `[${ts}] 📄 RESL: ${msg.metadata?.toolName || 'Tool'}${toolResultLines}\n`;
|
|
492
|
-
break;
|
|
493
|
-
case 'thinking':
|
|
494
|
-
// Format thinking block
|
|
495
|
-
const thinkLabel = `[ 🤔 THINKING ] `;
|
|
496
|
-
const thinkWidth = 80;
|
|
497
|
-
const thinkTop = `┌─${thinkLabel}${'─'.repeat(Math.max(0, thinkWidth - thinkLabel.length - 2))}`;
|
|
498
|
-
const thinkBottom = `└─${'─'.repeat(thinkWidth - 2)}`;
|
|
499
|
-
const thinkLines = msg.content.trim().split('\n');
|
|
500
|
-
formatted = `[${ts}] ${thinkTop}\n`;
|
|
501
|
-
for (const line of thinkLines) {
|
|
502
|
-
formatted += `[${ts}] │ ${line}\n`;
|
|
503
|
-
}
|
|
504
|
-
formatted += `[${ts}] ${thinkBottom}\n`;
|
|
505
|
-
break;
|
|
506
|
-
default:
|
|
507
|
-
formatted = `[${ts}] ${msg.content}\n`;
|
|
508
|
-
}
|
|
509
|
-
try {
|
|
510
|
-
fs.writeSync(this.readableLogFd, formatted);
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
// Ignore write errors
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Indent text with a prefix
|
|
518
|
-
*/
|
|
519
|
-
indentText(text, prefix) {
|
|
520
|
-
return text.split('\n').map(line => prefix + line).join('\n');
|
|
521
174
|
}
|
|
522
175
|
/**
|
|
523
176
|
* Write session header to logs
|
|
@@ -535,29 +188,13 @@ class EnhancedLogManager {
|
|
|
535
188
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
536
189
|
|
|
537
190
|
`;
|
|
538
|
-
this.
|
|
539
|
-
|
|
540
|
-
fs.writeSync(this.rawLogFd, header);
|
|
541
|
-
}
|
|
542
|
-
// Write JSON session entry
|
|
543
|
-
this.writeJsonEntry({
|
|
544
|
-
timestamp: new Date(this.session.startTime).toISOString(),
|
|
545
|
-
level: 'session',
|
|
546
|
-
source: 'system',
|
|
547
|
-
lane: this.session.laneName,
|
|
548
|
-
task: this.session.taskName,
|
|
549
|
-
message: 'Session started',
|
|
550
|
-
metadata: {
|
|
551
|
-
sessionId: this.session.id,
|
|
552
|
-
model: this.session.model,
|
|
553
|
-
...this.session.metadata,
|
|
554
|
-
},
|
|
555
|
-
});
|
|
191
|
+
this.writeToRawLog(header);
|
|
192
|
+
this.writeToReadableLog(header);
|
|
556
193
|
}
|
|
557
194
|
/**
|
|
558
195
|
* Rotate log file if it exceeds max size
|
|
559
196
|
*/
|
|
560
|
-
rotateIfNeeded(logPath
|
|
197
|
+
rotateIfNeeded(logPath) {
|
|
561
198
|
if (!fs.existsSync(logPath))
|
|
562
199
|
return;
|
|
563
200
|
try {
|
|
@@ -594,30 +231,13 @@ class EnhancedLogManager {
|
|
|
594
231
|
const rotatedPath = (0, path_1.safeJoin)(dir, `${base}.1${ext}`);
|
|
595
232
|
fs.renameSync(logPath, rotatedPath);
|
|
596
233
|
}
|
|
597
|
-
/**
|
|
598
|
-
* Write to clean log with size tracking
|
|
599
|
-
*/
|
|
600
|
-
writeToCleanLog(data) {
|
|
601
|
-
if (this.cleanLogFd === null)
|
|
602
|
-
return;
|
|
603
|
-
const buffer = Buffer.from(data);
|
|
604
|
-
fs.writeSync(this.cleanLogFd, buffer);
|
|
605
|
-
this.cleanLogSize += buffer.length;
|
|
606
|
-
// Check if rotation needed
|
|
607
|
-
if (this.cleanLogSize >= this.config.maxFileSize) {
|
|
608
|
-
fs.closeSync(this.cleanLogFd);
|
|
609
|
-
this.rotateLog(this.cleanLogPath);
|
|
610
|
-
this.cleanLogFd = fs.openSync(this.cleanLogPath, 'a');
|
|
611
|
-
this.cleanLogSize = 0;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
234
|
/**
|
|
615
235
|
* Write to raw log with size tracking
|
|
616
236
|
*/
|
|
617
237
|
writeToRawLog(data) {
|
|
618
238
|
if (this.rawLogFd === null)
|
|
619
239
|
return;
|
|
620
|
-
const buffer = Buffer.from(data);
|
|
240
|
+
const buffer = typeof data === 'string' ? Buffer.from(data) : data;
|
|
621
241
|
fs.writeSync(this.rawLogFd, buffer);
|
|
622
242
|
this.rawLogSize += buffer.length;
|
|
623
243
|
// Check if rotation needed
|
|
@@ -629,212 +249,197 @@ class EnhancedLogManager {
|
|
|
629
249
|
}
|
|
630
250
|
}
|
|
631
251
|
/**
|
|
632
|
-
* Write to
|
|
252
|
+
* Write to readable log
|
|
633
253
|
*/
|
|
634
|
-
|
|
635
|
-
if (this.
|
|
254
|
+
writeToReadableLog(data) {
|
|
255
|
+
if (this.readableLogFd === null)
|
|
636
256
|
return;
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
fs.closeSync(this.absoluteRawLogFd);
|
|
643
|
-
this.rotateLog(this.absoluteRawLogPath);
|
|
644
|
-
this.absoluteRawLogFd = fs.openSync(this.absoluteRawLogPath, 'a');
|
|
645
|
-
this.absoluteRawLogSize = 0;
|
|
257
|
+
try {
|
|
258
|
+
fs.writeSync(this.readableLogFd, data);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
// Ignore write errors
|
|
646
262
|
}
|
|
647
263
|
}
|
|
648
264
|
/**
|
|
649
|
-
* Write a
|
|
265
|
+
* Write a parsed message to the readable log using formatMessageForConsole style
|
|
650
266
|
*/
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
267
|
+
writeReadableMessage(msg) {
|
|
268
|
+
// Use formatMessageForConsole for consistent formatting
|
|
269
|
+
// Use short lane-task label like [L01-T02]
|
|
270
|
+
const formatted = (0, log_formatter_1.formatMessageForConsole)(msg, {
|
|
271
|
+
laneLabel: `[${this.getLaneTaskLabel()}]`,
|
|
272
|
+
includeTimestamp: false, // We'll add our own short timestamp
|
|
273
|
+
});
|
|
274
|
+
// Strip ANSI codes and add short timestamp for file output
|
|
275
|
+
const clean = stripAnsi(formatted);
|
|
276
|
+
const ts = this.getShortTime();
|
|
277
|
+
this.writeToReadableLog(`[${ts}] ${clean}\n`);
|
|
278
|
+
// Callback for console output
|
|
279
|
+
if (this.onParsedMessage) {
|
|
280
|
+
this.onParsedMessage(msg);
|
|
281
|
+
}
|
|
656
282
|
}
|
|
657
283
|
/**
|
|
658
284
|
* Write stdout data
|
|
659
285
|
*/
|
|
660
286
|
writeStdout(data) {
|
|
661
287
|
const text = data.toString();
|
|
662
|
-
// Write
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
// Write raw log
|
|
667
|
-
if (this.config.keepRawLogs) {
|
|
668
|
-
this.writeToRawLog(text);
|
|
669
|
-
}
|
|
670
|
-
// Process through transform for clean log
|
|
671
|
-
if (this.cleanTransform) {
|
|
672
|
-
this.cleanTransform.write(data);
|
|
673
|
-
}
|
|
674
|
-
// Process lines for readable log and JSON entries
|
|
675
|
-
this.lineBuffer += text;
|
|
676
|
-
const lines = this.lineBuffer.split('\n');
|
|
677
|
-
this.lineBuffer = lines.pop() || '';
|
|
288
|
+
// Write raw log (original data)
|
|
289
|
+
this.writeToRawLog(data);
|
|
290
|
+
// Parse JSON output and write to readable log
|
|
291
|
+
const lines = text.split('\n');
|
|
678
292
|
for (const line of lines) {
|
|
679
|
-
const
|
|
680
|
-
if (!
|
|
293
|
+
const trimmed = line.trim();
|
|
294
|
+
if (!trimmed)
|
|
681
295
|
continue;
|
|
682
|
-
//
|
|
683
|
-
if (
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const json = JSON.parse(cleanLine);
|
|
691
|
-
let displayMsg = cleanLine;
|
|
692
|
-
let metadata = { ...json };
|
|
693
|
-
// Extract cleaner text for significant AI message types
|
|
694
|
-
if ((json.type === 'thinking' || json.type === 'thought') && (json.text || json.thought)) {
|
|
695
|
-
displayMsg = json.text || json.thought;
|
|
696
|
-
// Clean up any double newlines at the end of deltas
|
|
697
|
-
displayMsg = displayMsg.replace(/\n+$/, '\n');
|
|
698
|
-
}
|
|
699
|
-
else if (json.type === 'assistant' && json.message?.content) {
|
|
700
|
-
displayMsg = json.message.content
|
|
701
|
-
.filter((c) => c.type === 'text')
|
|
702
|
-
.map((c) => c.text)
|
|
703
|
-
.join('');
|
|
704
|
-
}
|
|
705
|
-
else if (json.type === 'user' && json.message?.content) {
|
|
706
|
-
displayMsg = json.message.content
|
|
707
|
-
.filter((c) => c.type === 'text')
|
|
708
|
-
.map((c) => c.text)
|
|
709
|
-
.join('');
|
|
710
|
-
}
|
|
711
|
-
else if (json.type === 'tool_call' && json.subtype === 'started') {
|
|
712
|
-
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
713
|
-
const args = json.tool_call[toolName]?.args || {};
|
|
714
|
-
displayMsg = `🔧 CALL: ${toolName}(${JSON.stringify(args)})`;
|
|
715
|
-
}
|
|
716
|
-
else if (json.type === 'tool_call' && json.subtype === 'completed') {
|
|
717
|
-
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
718
|
-
displayMsg = `📄 RESL: ${toolName}`;
|
|
719
|
-
}
|
|
720
|
-
else if (json.type === 'result') {
|
|
721
|
-
displayMsg = json.result || 'Task completed';
|
|
722
|
-
}
|
|
723
|
-
this.writeJsonEntry({
|
|
724
|
-
timestamp: new Date().toISOString(),
|
|
725
|
-
level: 'stdout',
|
|
726
|
-
lane: this.session.laneName,
|
|
727
|
-
task: this.session.taskName,
|
|
728
|
-
message: displayMsg.substring(0, 2000), // Larger limit for AI text
|
|
729
|
-
metadata,
|
|
730
|
-
});
|
|
731
|
-
continue; // Already logged this JSON line
|
|
732
|
-
}
|
|
733
|
-
catch {
|
|
734
|
-
// Not valid JSON or error, fall through to regular logging
|
|
296
|
+
// Try to parse as JSON (cursor-agent output)
|
|
297
|
+
if (trimmed.startsWith('{')) {
|
|
298
|
+
try {
|
|
299
|
+
const json = JSON.parse(trimmed);
|
|
300
|
+
const msg = this.parseJsonToMessage(json);
|
|
301
|
+
if (msg) {
|
|
302
|
+
this.writeReadableMessage(msg);
|
|
303
|
+
continue;
|
|
735
304
|
}
|
|
736
305
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
// Parse standard text logs into ParsedMessage
|
|
740
|
-
// Format: [HH:MM:SS] [LANE] ℹ️ INFO message
|
|
741
|
-
const textLogRegex = /^\[(\d{2}:\d{2}:\d{2})\]\s+\[(.*?)\]\s+(.*?)\s+(INFO|WARN|ERROR|SUCCESS|DONE|PROGRESS)\s+(.*)/;
|
|
742
|
-
const match = cleanLine.match(textLogRegex);
|
|
743
|
-
if (match && this.onParsedMessage) {
|
|
744
|
-
const [, time, , emoji, level, content] = match;
|
|
745
|
-
// Convert HH:MM:SS to a timestamp for today
|
|
746
|
-
const [h, m, s] = time.split(':').map(Number);
|
|
747
|
-
const timestamp = new Date().setHours(h, m, s, 0);
|
|
748
|
-
this.onParsedMessage({
|
|
749
|
-
type: level.toLowerCase().replace('done', 'result').replace('success', 'result'),
|
|
750
|
-
role: 'system',
|
|
751
|
-
content: `${emoji} ${content}`,
|
|
752
|
-
timestamp,
|
|
753
|
-
});
|
|
306
|
+
catch {
|
|
307
|
+
// Not valid JSON, fall through
|
|
754
308
|
}
|
|
755
309
|
}
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
catch { }
|
|
310
|
+
// Non-JSON line - write as-is with short timestamp and lane-task label
|
|
311
|
+
const cleanLine = stripAnsi(trimmed);
|
|
312
|
+
if (cleanLine && !this.isNoiseLog(cleanLine)) {
|
|
313
|
+
const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
|
|
314
|
+
const label = this.getLaneTaskLabel();
|
|
315
|
+
if (hasTimestamp) {
|
|
316
|
+
// If already has timestamp, just ensure label is present
|
|
317
|
+
const formatted = cleanLine.includes(`[${label}]`)
|
|
318
|
+
? cleanLine
|
|
319
|
+
: cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}]`);
|
|
320
|
+
this.writeToReadableLog(`${formatted}\n`);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const ts = this.getShortTime();
|
|
324
|
+
this.writeToReadableLog(`[${ts}] [${label}] ${cleanLine}\n`);
|
|
775
325
|
}
|
|
776
|
-
}
|
|
777
|
-
// Write regular non-JSON lines to terminal.jsonl
|
|
778
|
-
if (this.config.writeJsonLog && !this.isNoiseLog(cleanLine)) {
|
|
779
|
-
this.writeJsonEntry({
|
|
780
|
-
timestamp: new Date().toISOString(),
|
|
781
|
-
level: 'stdout',
|
|
782
|
-
lane: this.session.laneName,
|
|
783
|
-
task: this.session.taskName,
|
|
784
|
-
message: cleanLine.substring(0, 1000),
|
|
785
|
-
raw: this.config.keepRawLogs ? undefined : line.substring(0, 1000),
|
|
786
|
-
});
|
|
787
326
|
}
|
|
788
327
|
}
|
|
789
328
|
}
|
|
790
329
|
/**
|
|
791
|
-
* Parse
|
|
330
|
+
* Parse cursor-agent JSON output to ParsedMessage
|
|
792
331
|
*/
|
|
793
|
-
|
|
794
|
-
|
|
332
|
+
parseJsonToMessage(json) {
|
|
333
|
+
const type = json.type;
|
|
334
|
+
const timestamp = json.timestamp_ms || Date.now();
|
|
335
|
+
switch (type) {
|
|
336
|
+
case 'system':
|
|
337
|
+
return {
|
|
338
|
+
type: 'system',
|
|
339
|
+
role: 'system',
|
|
340
|
+
content: `Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
|
|
341
|
+
timestamp,
|
|
342
|
+
};
|
|
343
|
+
case 'user':
|
|
344
|
+
if (json.message?.content) {
|
|
345
|
+
const textContent = json.message.content
|
|
346
|
+
.filter((c) => c.type === 'text')
|
|
347
|
+
.map((c) => c.text)
|
|
348
|
+
.join('');
|
|
349
|
+
return {
|
|
350
|
+
type: 'user',
|
|
351
|
+
role: 'user',
|
|
352
|
+
content: textContent,
|
|
353
|
+
timestamp,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
case 'assistant':
|
|
358
|
+
if (json.message?.content) {
|
|
359
|
+
const textContent = json.message.content
|
|
360
|
+
.filter((c) => c.type === 'text')
|
|
361
|
+
.map((c) => c.text)
|
|
362
|
+
.join('');
|
|
363
|
+
return {
|
|
364
|
+
type: 'assistant',
|
|
365
|
+
role: 'assistant',
|
|
366
|
+
content: textContent,
|
|
367
|
+
timestamp,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return null;
|
|
371
|
+
case 'tool_call':
|
|
372
|
+
if (json.subtype === 'started' && json.tool_call) {
|
|
373
|
+
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
374
|
+
const toolArgs = json.tool_call[toolName]?.args || {};
|
|
375
|
+
return {
|
|
376
|
+
type: 'tool',
|
|
377
|
+
role: 'tool',
|
|
378
|
+
content: `[Tool: ${toolName}] ${JSON.stringify(toolArgs)}`,
|
|
379
|
+
timestamp,
|
|
380
|
+
metadata: { callId: json.call_id, toolName },
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
else if (json.subtype === 'completed' && json.tool_call) {
|
|
384
|
+
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
385
|
+
return {
|
|
386
|
+
type: 'tool_result',
|
|
387
|
+
role: 'tool',
|
|
388
|
+
content: `[Tool Result: ${toolName}]`,
|
|
389
|
+
timestamp,
|
|
390
|
+
metadata: { callId: json.call_id, toolName },
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
case 'result':
|
|
395
|
+
return {
|
|
396
|
+
type: 'result',
|
|
397
|
+
role: 'assistant',
|
|
398
|
+
content: json.result || '',
|
|
399
|
+
timestamp,
|
|
400
|
+
metadata: {
|
|
401
|
+
duration_ms: json.duration_ms,
|
|
402
|
+
is_error: json.is_error,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
case 'thinking':
|
|
406
|
+
if (json.text) {
|
|
407
|
+
return {
|
|
408
|
+
type: 'thinking',
|
|
409
|
+
role: 'assistant',
|
|
410
|
+
content: json.text,
|
|
411
|
+
timestamp,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
default:
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
795
418
|
}
|
|
796
419
|
/**
|
|
797
420
|
* Write stderr data
|
|
798
421
|
*/
|
|
799
422
|
writeStderr(data) {
|
|
800
423
|
const text = data.toString();
|
|
801
|
-
// Write absolute raw log
|
|
802
|
-
if (this.config.keepAbsoluteRawLogs) {
|
|
803
|
-
this.writeToAbsoluteRawLog(data);
|
|
804
|
-
}
|
|
805
424
|
// Write raw log
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
this.
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
catch { }
|
|
425
|
+
this.writeToRawLog(data);
|
|
426
|
+
// Write to readable log with error prefix
|
|
427
|
+
const lines = text.split('\n');
|
|
428
|
+
for (const line of lines) {
|
|
429
|
+
const cleanLine = stripAnsi(line).trim();
|
|
430
|
+
if (cleanLine && !this.isNoiseLog(cleanLine)) {
|
|
431
|
+
const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
|
|
432
|
+
const label = this.getLaneTaskLabel();
|
|
433
|
+
if (hasTimestamp) {
|
|
434
|
+
const formatted = cleanLine.includes(`[${label}]`)
|
|
435
|
+
? cleanLine
|
|
436
|
+
: cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}] ❌ ERR`);
|
|
437
|
+
this.writeToReadableLog(`${formatted}\n`);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
const ts = this.getShortTime();
|
|
441
|
+
this.writeToReadableLog(`[${ts}] [${label}] ❌ ERR ${cleanLine}\n`);
|
|
824
442
|
}
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
// Write JSON entry
|
|
828
|
-
if (this.config.writeJsonLog) {
|
|
829
|
-
const cleanText = stripAnsi(text).trim();
|
|
830
|
-
if (cleanText) {
|
|
831
|
-
this.writeJsonEntry({
|
|
832
|
-
timestamp: new Date().toISOString(),
|
|
833
|
-
level: 'stderr',
|
|
834
|
-
lane: this.session.laneName,
|
|
835
|
-
task: this.session.taskName,
|
|
836
|
-
message: cleanText.substring(0, 1000),
|
|
837
|
-
});
|
|
838
443
|
}
|
|
839
444
|
}
|
|
840
445
|
}
|
|
@@ -842,35 +447,12 @@ class EnhancedLogManager {
|
|
|
842
447
|
* Write a custom log entry
|
|
843
448
|
*/
|
|
844
449
|
log(level, message, metadata) {
|
|
845
|
-
const ts =
|
|
846
|
-
const
|
|
847
|
-
const
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
}
|
|
852
|
-
if (this.config.keepAbsoluteRawLogs) {
|
|
853
|
-
this.writeToAbsoluteRawLog(line);
|
|
854
|
-
}
|
|
855
|
-
// Write to readable log (compact)
|
|
856
|
-
if (this.readableLogFd !== null) {
|
|
857
|
-
const typeLabel = level === 'error' ? '❌ ERROR' : level === 'info' ? 'ℹ️ INFO' : '🔍 DEBUG';
|
|
858
|
-
const formatted = `${new Date().toISOString()} ${typeLabel}: ${message}\n`;
|
|
859
|
-
try {
|
|
860
|
-
fs.writeSync(this.readableLogFd, formatted);
|
|
861
|
-
}
|
|
862
|
-
catch { }
|
|
863
|
-
}
|
|
864
|
-
if (this.config.writeJsonLog) {
|
|
865
|
-
this.writeJsonEntry({
|
|
866
|
-
timestamp: new Date().toISOString(),
|
|
867
|
-
level,
|
|
868
|
-
lane: this.session.laneName,
|
|
869
|
-
task: this.session.taskName,
|
|
870
|
-
message,
|
|
871
|
-
metadata,
|
|
872
|
-
});
|
|
873
|
-
}
|
|
450
|
+
const ts = this.getShortTime();
|
|
451
|
+
const label = this.getLaneTaskLabel();
|
|
452
|
+
const emoji = level === 'error' ? '❌' : level === 'info' ? 'ℹ️' : '🔍';
|
|
453
|
+
const line = `[${ts}] [${label}] ${emoji} ${level.toUpperCase()} ${message}\n`;
|
|
454
|
+
this.writeToRawLog(line);
|
|
455
|
+
this.writeToReadableLog(line);
|
|
874
456
|
}
|
|
875
457
|
/**
|
|
876
458
|
* Add a section marker
|
|
@@ -878,58 +460,34 @@ class EnhancedLogManager {
|
|
|
878
460
|
section(title) {
|
|
879
461
|
const divider = '═'.repeat(78);
|
|
880
462
|
const line = `\n${divider}\n ${title}\n${divider}\n`;
|
|
881
|
-
this.
|
|
882
|
-
|
|
883
|
-
this.writeToRawLog(line);
|
|
884
|
-
}
|
|
885
|
-
if (this.config.keepAbsoluteRawLogs) {
|
|
886
|
-
this.writeToAbsoluteRawLog(line);
|
|
887
|
-
}
|
|
888
|
-
// Write to readable log (compact)
|
|
889
|
-
if (this.readableLogFd !== null) {
|
|
890
|
-
const ts = new Date().toISOString();
|
|
891
|
-
const formatted = `[${ts}] ━━━ ${title} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
892
|
-
try {
|
|
893
|
-
fs.writeSync(this.readableLogFd, formatted);
|
|
894
|
-
}
|
|
895
|
-
catch { }
|
|
896
|
-
}
|
|
463
|
+
this.writeToRawLog(line);
|
|
464
|
+
this.writeToReadableLog(line);
|
|
897
465
|
}
|
|
898
466
|
/**
|
|
899
467
|
* Update task context
|
|
900
468
|
*/
|
|
901
|
-
setTask(taskName, model) {
|
|
469
|
+
setTask(taskName, model, taskIndex) {
|
|
902
470
|
this.session.taskName = taskName;
|
|
903
471
|
if (model) {
|
|
904
472
|
this.session.model = model;
|
|
905
473
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
this.writeJsonEntry({
|
|
909
|
-
timestamp: new Date().toISOString(),
|
|
910
|
-
level: 'info',
|
|
911
|
-
source: 'system',
|
|
912
|
-
lane: this.session.laneName,
|
|
913
|
-
task: taskName,
|
|
914
|
-
message: `Task started: ${taskName}`,
|
|
915
|
-
metadata: { model },
|
|
916
|
-
});
|
|
474
|
+
if (taskIndex !== undefined) {
|
|
475
|
+
this.session.taskIndex = taskIndex;
|
|
917
476
|
}
|
|
477
|
+
this.section(`Task: ${taskName}${model ? ` (Model: ${model})` : ''}`);
|
|
918
478
|
}
|
|
919
479
|
/**
|
|
920
|
-
* Check if a log line is noise
|
|
480
|
+
* Check if a log line is noise
|
|
921
481
|
*/
|
|
922
482
|
isNoiseLog(text) {
|
|
923
|
-
// Skip empty or whitespace-only
|
|
924
483
|
if (!text.trim())
|
|
925
484
|
return true;
|
|
926
|
-
// Skip common progress/spinner patterns
|
|
927
485
|
const noisePatterns = [
|
|
928
|
-
/^[\s│├└─┌┐┘┴┬┤]+$/,
|
|
929
|
-
/^[.\s]+$/,
|
|
930
|
-
/^[=>\s-]+$/,
|
|
931
|
-
/^\d+%$/,
|
|
932
|
-
/^⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/,
|
|
486
|
+
/^[\s│├└─┌┐┘┴┬┤]+$/,
|
|
487
|
+
/^[.\s]+$/,
|
|
488
|
+
/^[=>\s-]+$/,
|
|
489
|
+
/^\d+%$/,
|
|
490
|
+
/^⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/,
|
|
933
491
|
];
|
|
934
492
|
return noisePatterns.some(p => p.test(text));
|
|
935
493
|
}
|
|
@@ -938,10 +496,8 @@ class EnhancedLogManager {
|
|
|
938
496
|
*/
|
|
939
497
|
getLogPaths() {
|
|
940
498
|
return {
|
|
941
|
-
clean: this.
|
|
942
|
-
raw: this.
|
|
943
|
-
absoluteRaw: this.config.keepAbsoluteRawLogs ? this.absoluteRawLogPath : undefined,
|
|
944
|
-
json: this.config.writeJsonLog ? this.jsonLogPath : undefined,
|
|
499
|
+
clean: this.readableLogPath, // For backward compatibility
|
|
500
|
+
raw: this.rawLogPath,
|
|
945
501
|
readable: this.readableLogPath,
|
|
946
502
|
};
|
|
947
503
|
}
|
|
@@ -949,26 +505,13 @@ class EnhancedLogManager {
|
|
|
949
505
|
* Create file descriptors for process stdio redirection
|
|
950
506
|
*/
|
|
951
507
|
getFileDescriptors() {
|
|
952
|
-
|
|
953
|
-
const fd = this.rawLogFd !== null ? this.rawLogFd : this.cleanLogFd;
|
|
508
|
+
const fd = this.rawLogFd;
|
|
954
509
|
return { stdout: fd, stderr: fd };
|
|
955
510
|
}
|
|
956
511
|
/**
|
|
957
|
-
* Close all log files
|
|
512
|
+
* Close all log files
|
|
958
513
|
*/
|
|
959
514
|
close() {
|
|
960
|
-
// Flush transform stream
|
|
961
|
-
if (this.cleanTransform) {
|
|
962
|
-
this.cleanTransform.end();
|
|
963
|
-
}
|
|
964
|
-
// Flush streaming parser
|
|
965
|
-
if (this.streamingParser) {
|
|
966
|
-
// Parse any remaining buffered data
|
|
967
|
-
if (this.lineBuffer.trim()) {
|
|
968
|
-
this.streamingParser.parseLine(this.lineBuffer);
|
|
969
|
-
}
|
|
970
|
-
this.streamingParser.flush();
|
|
971
|
-
}
|
|
972
515
|
// Write session end marker
|
|
973
516
|
const endMarker = `
|
|
974
517
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
@@ -977,15 +520,6 @@ class EnhancedLogManager {
|
|
|
977
520
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
978
521
|
|
|
979
522
|
`;
|
|
980
|
-
if (this.cleanLogFd !== null) {
|
|
981
|
-
try {
|
|
982
|
-
fs.writeSync(this.cleanLogFd, endMarker);
|
|
983
|
-
fs.fsyncSync(this.cleanLogFd);
|
|
984
|
-
fs.closeSync(this.cleanLogFd);
|
|
985
|
-
}
|
|
986
|
-
catch { }
|
|
987
|
-
this.cleanLogFd = null;
|
|
988
|
-
}
|
|
989
523
|
if (this.rawLogFd !== null) {
|
|
990
524
|
try {
|
|
991
525
|
fs.writeSync(this.rawLogFd, endMarker);
|
|
@@ -995,34 +529,6 @@ class EnhancedLogManager {
|
|
|
995
529
|
catch { }
|
|
996
530
|
this.rawLogFd = null;
|
|
997
531
|
}
|
|
998
|
-
if (this.absoluteRawLogFd !== null) {
|
|
999
|
-
try {
|
|
1000
|
-
fs.fsyncSync(this.absoluteRawLogFd);
|
|
1001
|
-
fs.closeSync(this.absoluteRawLogFd);
|
|
1002
|
-
}
|
|
1003
|
-
catch { }
|
|
1004
|
-
this.absoluteRawLogFd = null;
|
|
1005
|
-
}
|
|
1006
|
-
if (this.jsonLogFd !== null) {
|
|
1007
|
-
try {
|
|
1008
|
-
this.writeJsonEntry({
|
|
1009
|
-
timestamp: new Date().toISOString(),
|
|
1010
|
-
level: 'session',
|
|
1011
|
-
source: 'system',
|
|
1012
|
-
lane: this.session.laneName,
|
|
1013
|
-
message: 'Session ended',
|
|
1014
|
-
metadata: {
|
|
1015
|
-
sessionId: this.session.id,
|
|
1016
|
-
duration: Date.now() - this.session.startTime,
|
|
1017
|
-
},
|
|
1018
|
-
});
|
|
1019
|
-
fs.fsyncSync(this.jsonLogFd);
|
|
1020
|
-
fs.closeSync(this.jsonLogFd);
|
|
1021
|
-
}
|
|
1022
|
-
catch { }
|
|
1023
|
-
this.jsonLogFd = null;
|
|
1024
|
-
}
|
|
1025
|
-
// Close readable log
|
|
1026
532
|
if (this.readableLogFd !== null) {
|
|
1027
533
|
try {
|
|
1028
534
|
fs.writeSync(this.readableLogFd, endMarker);
|
|
@@ -1034,21 +540,19 @@ class EnhancedLogManager {
|
|
|
1034
540
|
}
|
|
1035
541
|
}
|
|
1036
542
|
/**
|
|
1037
|
-
* Extract the last error message from the
|
|
543
|
+
* Extract the last error message from the log
|
|
1038
544
|
*/
|
|
1039
545
|
getLastError() {
|
|
1040
546
|
try {
|
|
1041
|
-
if (!fs.existsSync(this.
|
|
547
|
+
if (!fs.existsSync(this.readableLogPath))
|
|
1042
548
|
return null;
|
|
1043
|
-
const content = fs.readFileSync(this.
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
l.includes('❌') ||
|
|
549
|
+
const content = fs.readFileSync(this.readableLogPath, 'utf8');
|
|
550
|
+
const lines = content.split('\n').filter(l => l.includes('❌') ||
|
|
551
|
+
l.includes('[ERROR]') ||
|
|
1047
552
|
l.includes('error:') ||
|
|
1048
553
|
l.includes('Fatal') ||
|
|
1049
554
|
l.includes('fail'));
|
|
1050
555
|
if (lines.length === 0) {
|
|
1051
|
-
// Fallback to last 5 lines if no specific error marker found
|
|
1052
556
|
const allLines = content.split('\n').filter(l => l.trim());
|
|
1053
557
|
return allLines.slice(-5).join('\n');
|
|
1054
558
|
}
|
|
@@ -1078,154 +582,45 @@ exports.EnhancedLogManager = EnhancedLogManager;
|
|
|
1078
582
|
/**
|
|
1079
583
|
* Create a log manager for a lane
|
|
1080
584
|
*/
|
|
1081
|
-
function createLogManager(laneRunDir, laneName, config, onParsedMessage) {
|
|
585
|
+
function createLogManager(laneRunDir, laneName, config, onParsedMessage, laneIndex) {
|
|
1082
586
|
const session = {
|
|
1083
587
|
id: `${laneName}-${Date.now().toString(36)}`,
|
|
1084
588
|
laneName,
|
|
1085
589
|
startTime: Date.now(),
|
|
590
|
+
laneIndex: laneIndex ?? 0,
|
|
591
|
+
taskIndex: 0,
|
|
1086
592
|
};
|
|
1087
593
|
return new EnhancedLogManager(laneRunDir, session, config, onParsedMessage);
|
|
1088
594
|
}
|
|
1089
595
|
/**
|
|
1090
|
-
* Read and parse JSON log file
|
|
596
|
+
* Read and parse JSON log file (legacy compatibility - returns empty array)
|
|
1091
597
|
*/
|
|
1092
598
|
function readJsonLog(logPath) {
|
|
1093
|
-
|
|
1094
|
-
return [];
|
|
1095
|
-
}
|
|
1096
|
-
try {
|
|
1097
|
-
const content = fs.readFileSync(logPath, 'utf8');
|
|
1098
|
-
return content
|
|
1099
|
-
.split('\n')
|
|
1100
|
-
.filter(line => line.trim())
|
|
1101
|
-
.map(line => {
|
|
1102
|
-
try {
|
|
1103
|
-
return JSON.parse(line);
|
|
1104
|
-
}
|
|
1105
|
-
catch {
|
|
1106
|
-
return null;
|
|
1107
|
-
}
|
|
1108
|
-
})
|
|
1109
|
-
.filter((entry) => entry !== null);
|
|
1110
|
-
}
|
|
1111
|
-
catch {
|
|
1112
|
-
return [];
|
|
1113
|
-
}
|
|
599
|
+
return [];
|
|
1114
600
|
}
|
|
1115
601
|
/**
|
|
1116
|
-
* Export logs
|
|
602
|
+
* Export logs (legacy compatibility)
|
|
1117
603
|
*/
|
|
1118
604
|
function exportLogs(laneRunDir, format, outputPath) {
|
|
1119
|
-
const
|
|
1120
|
-
const jsonLogPath = (0, path_1.safeJoin)(laneRunDir, 'terminal.jsonl');
|
|
605
|
+
const readableLogPath = (0, path_1.safeJoin)(laneRunDir, 'terminal-readable.log');
|
|
1121
606
|
let output = '';
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
if (fs.existsSync(cleanLogPath)) {
|
|
1125
|
-
output = fs.readFileSync(cleanLogPath, 'utf8');
|
|
1126
|
-
}
|
|
1127
|
-
break;
|
|
1128
|
-
case 'json':
|
|
1129
|
-
const entries = readJsonLog(jsonLogPath);
|
|
1130
|
-
output = JSON.stringify(entries, null, 2);
|
|
1131
|
-
break;
|
|
1132
|
-
case 'markdown':
|
|
1133
|
-
output = exportToMarkdown(jsonLogPath, cleanLogPath);
|
|
1134
|
-
break;
|
|
1135
|
-
case 'html':
|
|
1136
|
-
output = exportToHtml(jsonLogPath, cleanLogPath);
|
|
1137
|
-
break;
|
|
607
|
+
if (fs.existsSync(readableLogPath)) {
|
|
608
|
+
output = fs.readFileSync(readableLogPath, 'utf8');
|
|
1138
609
|
}
|
|
1139
610
|
if (outputPath) {
|
|
1140
611
|
fs.writeFileSync(outputPath, output, 'utf8');
|
|
1141
612
|
}
|
|
1142
613
|
return output;
|
|
1143
614
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
let md = '# CursorFlow Session Log\n\n';
|
|
1150
|
-
// Find session info
|
|
1151
|
-
const sessionStart = entries.find(e => e.level === 'session' && e.message === 'Session started');
|
|
1152
|
-
if (sessionStart?.metadata) {
|
|
1153
|
-
md += '## Session Info\n\n';
|
|
1154
|
-
md += `- **Session ID**: ${sessionStart.metadata.sessionId}\n`;
|
|
1155
|
-
md += `- **Lane**: ${sessionStart.lane}\n`;
|
|
1156
|
-
md += `- **Model**: ${sessionStart.metadata.model || 'default'}\n`;
|
|
1157
|
-
md += `- **Started**: ${sessionStart.timestamp}\n\n`;
|
|
1158
|
-
}
|
|
1159
|
-
md += '## Log Entries\n\n';
|
|
1160
|
-
// Group by task
|
|
1161
|
-
const byTask = new Map();
|
|
1162
|
-
for (const entry of entries) {
|
|
1163
|
-
const task = entry.task || '(no task)';
|
|
1164
|
-
if (!byTask.has(task)) {
|
|
1165
|
-
byTask.set(task, []);
|
|
1166
|
-
}
|
|
1167
|
-
byTask.get(task).push(entry);
|
|
1168
|
-
}
|
|
1169
|
-
for (const [task, taskEntries] of byTask) {
|
|
1170
|
-
md += `### Task: ${task}\n\n`;
|
|
1171
|
-
md += '```\n';
|
|
1172
|
-
for (const entry of taskEntries) {
|
|
1173
|
-
const level = entry.level || 'info';
|
|
1174
|
-
const message = entry.message || '';
|
|
1175
|
-
if (level !== 'session') {
|
|
1176
|
-
md += `[${entry.timestamp}] [${level.toUpperCase()}] ${message}\n`;
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
md += '```\n\n';
|
|
1180
|
-
}
|
|
1181
|
-
return md;
|
|
1182
|
-
}
|
|
1183
|
-
/**
|
|
1184
|
-
* Export logs to HTML format
|
|
1185
|
-
*/
|
|
1186
|
-
function exportToHtml(jsonLogPath, cleanLogPath) {
|
|
1187
|
-
const entries = readJsonLog(jsonLogPath);
|
|
1188
|
-
let html = `<!DOCTYPE html>
|
|
1189
|
-
<html>
|
|
1190
|
-
<head>
|
|
1191
|
-
<title>CursorFlow Session Log</title>
|
|
1192
|
-
<style>
|
|
1193
|
-
body { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; margin: 20px; background: #1e1e1e; color: #d4d4d4; }
|
|
1194
|
-
h1, h2 { color: #569cd6; }
|
|
1195
|
-
.entry { padding: 4px 8px; margin: 2px 0; border-radius: 4px; }
|
|
1196
|
-
.entry.stdout { background: #252526; }
|
|
1197
|
-
.entry.stderr { background: #3c1f1f; color: #f48771; }
|
|
1198
|
-
.entry.info { background: #1e3a5f; color: #9cdcfe; }
|
|
1199
|
-
.entry.error { background: #5f1e1e; color: #f48771; }
|
|
1200
|
-
.entry.session { background: #1e4620; color: #6a9955; }
|
|
1201
|
-
.timestamp { color: #808080; font-size: 0.9em; }
|
|
1202
|
-
.level { font-weight: bold; text-transform: uppercase; }
|
|
1203
|
-
.task { color: #dcdcaa; }
|
|
1204
|
-
pre { white-space: pre-wrap; word-wrap: break-word; }
|
|
1205
|
-
</style>
|
|
1206
|
-
</head>
|
|
1207
|
-
<body>
|
|
1208
|
-
<h1>CursorFlow Session Log</h1>
|
|
1209
|
-
`;
|
|
1210
|
-
for (const entry of entries) {
|
|
1211
|
-
const level = entry.level || 'info';
|
|
1212
|
-
const message = entry.message || '';
|
|
1213
|
-
html += ` <div class="entry ${level}">
|
|
1214
|
-
<span class="timestamp">${entry.timestamp}</span>
|
|
1215
|
-
<span class="level">[${level}]</span>
|
|
1216
|
-
${entry.task ? `<span class="task">[${entry.task}]</span>` : ''}
|
|
1217
|
-
<pre>${escapeHtml(message)}</pre>
|
|
1218
|
-
</div>\n`;
|
|
1219
|
-
}
|
|
1220
|
-
html += '</body></html>';
|
|
1221
|
-
return html;
|
|
615
|
+
// Legacy exports for backward compatibility
|
|
616
|
+
class StreamingMessageParser {
|
|
617
|
+
constructor(onMessage) { }
|
|
618
|
+
parseLine(line) { }
|
|
619
|
+
flush() { }
|
|
1222
620
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
.replace(/</g, '<')
|
|
1227
|
-
.replace(/>/g, '>')
|
|
1228
|
-
.replace(/"/g, '"')
|
|
1229
|
-
.replace(/'/g, ''');
|
|
621
|
+
exports.StreamingMessageParser = StreamingMessageParser;
|
|
622
|
+
class CleanLogTransform {
|
|
623
|
+
constructor(config, session) { }
|
|
1230
624
|
}
|
|
625
|
+
exports.CleanLogTransform = CleanLogTransform;
|
|
1231
626
|
//# sourceMappingURL=enhanced-logger.js.map
|