@litmers/cursorflow-orchestrator 0.1.30 → 0.1.34
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/README.md +144 -52
- package/commands/cursorflow-add.md +159 -0
- package/commands/cursorflow-monitor.md +23 -2
- package/commands/cursorflow-new.md +87 -0
- 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/index.js +34 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/logs.js +7 -33
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +51 -62
- 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 +11 -47
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +27 -22
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/tasks.js +1 -2
- 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 +217 -331
- 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/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.js +7 -15
- 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/enhanced-logger.d.ts +45 -82
- package/dist/utils/enhanced-logger.js +238 -844
- package/dist/utils/enhanced-logger.js.map +1 -1
- 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/index.ts +36 -32
- package/src/cli/logs.ts +7 -31
- package/src/cli/monitor.ts +55 -71
- package/src/cli/new.ts +235 -0
- package/src/cli/prepare.ts +98 -205
- package/src/cli/resume.ts +13 -56
- package/src/cli/run.ts +311 -306
- package/src/cli/tasks.ts +1 -2
- package/src/core/failure-policy.ts +9 -0
- package/src/core/orchestrator.ts +281 -375
- 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/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 +8 -16
- package/src/utils/dependency.ts +311 -2
- package/src/utils/enhanced-logger.ts +263 -927
- 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,67 @@ 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 [L01-T02]
|
|
145
|
+
*/
|
|
146
|
+
getLaneTaskLabel() {
|
|
147
|
+
const laneNum = (this.session.laneIndex ?? 0) + 1;
|
|
148
|
+
const taskNum = (this.session.taskIndex ?? 0) + 1;
|
|
149
|
+
return `L${laneNum.toString().padStart(2, '0')}-T${taskNum.toString().padStart(2, '0')}`;
|
|
150
|
+
}
|
|
398
151
|
/**
|
|
399
152
|
* Initialize log files and write session headers
|
|
400
153
|
*/
|
|
401
154
|
initLogFiles() {
|
|
402
155
|
// 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
156
|
if (this.config.keepRawLogs) {
|
|
157
|
+
this.rotateIfNeeded(this.rawLogPath);
|
|
413
158
|
this.rawLogFd = fs.openSync(this.rawLogPath, 'a');
|
|
414
159
|
}
|
|
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)
|
|
160
|
+
this.rotateIfNeeded(this.readableLogPath);
|
|
422
161
|
this.readableLogFd = fs.openSync(this.readableLogPath, 'a');
|
|
423
|
-
// Get initial file
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
if (this.config.keepRawLogs) {
|
|
162
|
+
// Get initial file size for raw log if enabled
|
|
163
|
+
if (this.rawLogFd !== null) {
|
|
164
|
+
try {
|
|
427
165
|
this.rawLogSize = fs.statSync(this.rawLogPath).size;
|
|
428
166
|
}
|
|
429
|
-
|
|
430
|
-
this.
|
|
167
|
+
catch {
|
|
168
|
+
this.rawLogSize = 0;
|
|
431
169
|
}
|
|
432
170
|
}
|
|
433
|
-
catch {
|
|
434
|
-
this.cleanLogSize = 0;
|
|
435
|
-
this.rawLogSize = 0;
|
|
436
|
-
this.absoluteRawLogSize = 0;
|
|
437
|
-
}
|
|
438
171
|
// Write session header
|
|
439
172
|
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
173
|
}
|
|
522
174
|
/**
|
|
523
175
|
* Write session header to logs
|
|
@@ -535,29 +187,13 @@ class EnhancedLogManager {
|
|
|
535
187
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
536
188
|
|
|
537
189
|
`;
|
|
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
|
-
});
|
|
190
|
+
this.writeToRawLog(header);
|
|
191
|
+
this.writeToReadableLog(header);
|
|
556
192
|
}
|
|
557
193
|
/**
|
|
558
194
|
* Rotate log file if it exceeds max size
|
|
559
195
|
*/
|
|
560
|
-
rotateIfNeeded(logPath
|
|
196
|
+
rotateIfNeeded(logPath) {
|
|
561
197
|
if (!fs.existsSync(logPath))
|
|
562
198
|
return;
|
|
563
199
|
try {
|
|
@@ -594,30 +230,13 @@ class EnhancedLogManager {
|
|
|
594
230
|
const rotatedPath = (0, path_1.safeJoin)(dir, `${base}.1${ext}`);
|
|
595
231
|
fs.renameSync(logPath, rotatedPath);
|
|
596
232
|
}
|
|
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
233
|
/**
|
|
615
234
|
* Write to raw log with size tracking
|
|
616
235
|
*/
|
|
617
236
|
writeToRawLog(data) {
|
|
618
237
|
if (this.rawLogFd === null)
|
|
619
238
|
return;
|
|
620
|
-
const buffer = Buffer.from(data);
|
|
239
|
+
const buffer = typeof data === 'string' ? Buffer.from(data) : data;
|
|
621
240
|
fs.writeSync(this.rawLogFd, buffer);
|
|
622
241
|
this.rawLogSize += buffer.length;
|
|
623
242
|
// Check if rotation needed
|
|
@@ -629,212 +248,197 @@ class EnhancedLogManager {
|
|
|
629
248
|
}
|
|
630
249
|
}
|
|
631
250
|
/**
|
|
632
|
-
* Write to
|
|
251
|
+
* Write to readable log
|
|
633
252
|
*/
|
|
634
|
-
|
|
635
|
-
if (this.
|
|
253
|
+
writeToReadableLog(data) {
|
|
254
|
+
if (this.readableLogFd === null)
|
|
636
255
|
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;
|
|
256
|
+
try {
|
|
257
|
+
fs.writeSync(this.readableLogFd, data);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// Ignore write errors
|
|
646
261
|
}
|
|
647
262
|
}
|
|
648
263
|
/**
|
|
649
|
-
* Write a
|
|
264
|
+
* Write a parsed message to the readable log using formatMessageForConsole style
|
|
650
265
|
*/
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
266
|
+
writeReadableMessage(msg) {
|
|
267
|
+
// Use formatMessageForConsole for consistent formatting
|
|
268
|
+
// Use short lane-task label like [L01-T02]
|
|
269
|
+
const formatted = (0, log_formatter_1.formatMessageForConsole)(msg, {
|
|
270
|
+
laneLabel: `[${this.getLaneTaskLabel()}]`,
|
|
271
|
+
includeTimestamp: false, // We'll add our own short timestamp
|
|
272
|
+
});
|
|
273
|
+
// Strip ANSI codes and add short timestamp for file output
|
|
274
|
+
const clean = stripAnsi(formatted);
|
|
275
|
+
const ts = this.getShortTime();
|
|
276
|
+
this.writeToReadableLog(`[${ts}] ${clean}\n`);
|
|
277
|
+
// Callback for console output
|
|
278
|
+
if (this.onParsedMessage) {
|
|
279
|
+
this.onParsedMessage(msg);
|
|
280
|
+
}
|
|
656
281
|
}
|
|
657
282
|
/**
|
|
658
283
|
* Write stdout data
|
|
659
284
|
*/
|
|
660
285
|
writeStdout(data) {
|
|
661
286
|
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() || '';
|
|
287
|
+
// Write raw log (original data)
|
|
288
|
+
this.writeToRawLog(data);
|
|
289
|
+
// Parse JSON output and write to readable log
|
|
290
|
+
const lines = text.split('\n');
|
|
678
291
|
for (const line of lines) {
|
|
679
|
-
const
|
|
680
|
-
if (!
|
|
292
|
+
const trimmed = line.trim();
|
|
293
|
+
if (!trimmed)
|
|
681
294
|
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
|
|
295
|
+
// Try to parse as JSON (cursor-agent output)
|
|
296
|
+
if (trimmed.startsWith('{')) {
|
|
297
|
+
try {
|
|
298
|
+
const json = JSON.parse(trimmed);
|
|
299
|
+
const msg = this.parseJsonToMessage(json);
|
|
300
|
+
if (msg) {
|
|
301
|
+
this.writeReadableMessage(msg);
|
|
302
|
+
continue;
|
|
735
303
|
}
|
|
736
304
|
}
|
|
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
|
-
});
|
|
305
|
+
catch {
|
|
306
|
+
// Not valid JSON, fall through
|
|
754
307
|
}
|
|
755
308
|
}
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
catch { }
|
|
309
|
+
// Non-JSON line - write as-is with short timestamp and lane-task label
|
|
310
|
+
const cleanLine = stripAnsi(trimmed);
|
|
311
|
+
if (cleanLine && !this.isNoiseLog(cleanLine)) {
|
|
312
|
+
const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
|
|
313
|
+
const label = this.getLaneTaskLabel();
|
|
314
|
+
if (hasTimestamp) {
|
|
315
|
+
// If already has timestamp, just ensure label is present
|
|
316
|
+
const formatted = cleanLine.includes(`[${label}]`)
|
|
317
|
+
? cleanLine
|
|
318
|
+
: cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}]`);
|
|
319
|
+
this.writeToReadableLog(`${formatted}\n`);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
const ts = this.getShortTime();
|
|
323
|
+
this.writeToReadableLog(`[${ts}] [${label}] ${cleanLine}\n`);
|
|
775
324
|
}
|
|
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
325
|
}
|
|
788
326
|
}
|
|
789
327
|
}
|
|
790
328
|
/**
|
|
791
|
-
* Parse
|
|
329
|
+
* Parse cursor-agent JSON output to ParsedMessage
|
|
792
330
|
*/
|
|
793
|
-
|
|
794
|
-
|
|
331
|
+
parseJsonToMessage(json) {
|
|
332
|
+
const type = json.type;
|
|
333
|
+
const timestamp = json.timestamp_ms || Date.now();
|
|
334
|
+
switch (type) {
|
|
335
|
+
case 'system':
|
|
336
|
+
return {
|
|
337
|
+
type: 'system',
|
|
338
|
+
role: 'system',
|
|
339
|
+
content: `Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
|
|
340
|
+
timestamp,
|
|
341
|
+
};
|
|
342
|
+
case 'user':
|
|
343
|
+
if (json.message?.content) {
|
|
344
|
+
const textContent = json.message.content
|
|
345
|
+
.filter((c) => c.type === 'text')
|
|
346
|
+
.map((c) => c.text)
|
|
347
|
+
.join('');
|
|
348
|
+
return {
|
|
349
|
+
type: 'user',
|
|
350
|
+
role: 'user',
|
|
351
|
+
content: textContent,
|
|
352
|
+
timestamp,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
case 'assistant':
|
|
357
|
+
if (json.message?.content) {
|
|
358
|
+
const textContent = json.message.content
|
|
359
|
+
.filter((c) => c.type === 'text')
|
|
360
|
+
.map((c) => c.text)
|
|
361
|
+
.join('');
|
|
362
|
+
return {
|
|
363
|
+
type: 'assistant',
|
|
364
|
+
role: 'assistant',
|
|
365
|
+
content: textContent,
|
|
366
|
+
timestamp,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
case 'tool_call':
|
|
371
|
+
if (json.subtype === 'started' && json.tool_call) {
|
|
372
|
+
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
373
|
+
const toolArgs = json.tool_call[toolName]?.args || {};
|
|
374
|
+
return {
|
|
375
|
+
type: 'tool',
|
|
376
|
+
role: 'tool',
|
|
377
|
+
content: `[Tool: ${toolName}] ${JSON.stringify(toolArgs)}`,
|
|
378
|
+
timestamp,
|
|
379
|
+
metadata: { callId: json.call_id, toolName },
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
else if (json.subtype === 'completed' && json.tool_call) {
|
|
383
|
+
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
384
|
+
return {
|
|
385
|
+
type: 'tool_result',
|
|
386
|
+
role: 'tool',
|
|
387
|
+
content: `[Tool Result: ${toolName}]`,
|
|
388
|
+
timestamp,
|
|
389
|
+
metadata: { callId: json.call_id, toolName },
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
case 'result':
|
|
394
|
+
return {
|
|
395
|
+
type: 'result',
|
|
396
|
+
role: 'assistant',
|
|
397
|
+
content: json.result || '',
|
|
398
|
+
timestamp,
|
|
399
|
+
metadata: {
|
|
400
|
+
duration_ms: json.duration_ms,
|
|
401
|
+
is_error: json.is_error,
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
case 'thinking':
|
|
405
|
+
if (json.text) {
|
|
406
|
+
return {
|
|
407
|
+
type: 'thinking',
|
|
408
|
+
role: 'assistant',
|
|
409
|
+
content: json.text,
|
|
410
|
+
timestamp,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
default:
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
795
417
|
}
|
|
796
418
|
/**
|
|
797
419
|
* Write stderr data
|
|
798
420
|
*/
|
|
799
421
|
writeStderr(data) {
|
|
800
422
|
const text = data.toString();
|
|
801
|
-
// Write absolute raw log
|
|
802
|
-
if (this.config.keepAbsoluteRawLogs) {
|
|
803
|
-
this.writeToAbsoluteRawLog(data);
|
|
804
|
-
}
|
|
805
423
|
// Write raw log
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
this.
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
catch { }
|
|
424
|
+
this.writeToRawLog(data);
|
|
425
|
+
// Write to readable log with error prefix
|
|
426
|
+
const lines = text.split('\n');
|
|
427
|
+
for (const line of lines) {
|
|
428
|
+
const cleanLine = stripAnsi(line).trim();
|
|
429
|
+
if (cleanLine && !this.isNoiseLog(cleanLine)) {
|
|
430
|
+
const hasTimestamp = /^\[(\d{4}-\d{2}-\d{2}T|\d{2}:\d{2}:\d{2})\]/.test(cleanLine);
|
|
431
|
+
const label = this.getLaneTaskLabel();
|
|
432
|
+
if (hasTimestamp) {
|
|
433
|
+
const formatted = cleanLine.includes(`[${label}]`)
|
|
434
|
+
? cleanLine
|
|
435
|
+
: cleanLine.replace(/^(\[[^\]]+\])/, `$1 [${label}] ❌ ERR`);
|
|
436
|
+
this.writeToReadableLog(`${formatted}\n`);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
const ts = this.getShortTime();
|
|
440
|
+
this.writeToReadableLog(`[${ts}] [${label}] ❌ ERR ${cleanLine}\n`);
|
|
824
441
|
}
|
|
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
442
|
}
|
|
839
443
|
}
|
|
840
444
|
}
|
|
@@ -842,35 +446,12 @@ class EnhancedLogManager {
|
|
|
842
446
|
* Write a custom log entry
|
|
843
447
|
*/
|
|
844
448
|
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
|
-
}
|
|
449
|
+
const ts = this.getShortTime();
|
|
450
|
+
const label = this.getLaneTaskLabel();
|
|
451
|
+
const emoji = level === 'error' ? '❌' : level === 'info' ? 'ℹ️' : '🔍';
|
|
452
|
+
const line = `[${ts}] [${label}] ${emoji} ${level.toUpperCase()} ${message}\n`;
|
|
453
|
+
this.writeToRawLog(line);
|
|
454
|
+
this.writeToReadableLog(line);
|
|
874
455
|
}
|
|
875
456
|
/**
|
|
876
457
|
* Add a section marker
|
|
@@ -878,58 +459,34 @@ class EnhancedLogManager {
|
|
|
878
459
|
section(title) {
|
|
879
460
|
const divider = '═'.repeat(78);
|
|
880
461
|
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
|
-
}
|
|
462
|
+
this.writeToRawLog(line);
|
|
463
|
+
this.writeToReadableLog(line);
|
|
897
464
|
}
|
|
898
465
|
/**
|
|
899
466
|
* Update task context
|
|
900
467
|
*/
|
|
901
|
-
setTask(taskName, model) {
|
|
468
|
+
setTask(taskName, model, taskIndex) {
|
|
902
469
|
this.session.taskName = taskName;
|
|
903
470
|
if (model) {
|
|
904
471
|
this.session.model = model;
|
|
905
472
|
}
|
|
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
|
-
});
|
|
473
|
+
if (taskIndex !== undefined) {
|
|
474
|
+
this.session.taskIndex = taskIndex;
|
|
917
475
|
}
|
|
476
|
+
this.section(`Task: ${taskName}${model ? ` (Model: ${model})` : ''}`);
|
|
918
477
|
}
|
|
919
478
|
/**
|
|
920
|
-
* Check if a log line is noise
|
|
479
|
+
* Check if a log line is noise
|
|
921
480
|
*/
|
|
922
481
|
isNoiseLog(text) {
|
|
923
|
-
// Skip empty or whitespace-only
|
|
924
482
|
if (!text.trim())
|
|
925
483
|
return true;
|
|
926
|
-
// Skip common progress/spinner patterns
|
|
927
484
|
const noisePatterns = [
|
|
928
|
-
/^[\s│├└─┌┐┘┴┬┤]+$/,
|
|
929
|
-
/^[.\s]+$/,
|
|
930
|
-
/^[=>\s-]+$/,
|
|
931
|
-
/^\d+%$/,
|
|
932
|
-
/^⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/,
|
|
485
|
+
/^[\s│├└─┌┐┘┴┬┤]+$/,
|
|
486
|
+
/^[.\s]+$/,
|
|
487
|
+
/^[=>\s-]+$/,
|
|
488
|
+
/^\d+%$/,
|
|
489
|
+
/^⠋|⠙|⠹|⠸|⠼|⠴|⠦|⠧|⠇|⠏/,
|
|
933
490
|
];
|
|
934
491
|
return noisePatterns.some(p => p.test(text));
|
|
935
492
|
}
|
|
@@ -938,10 +495,8 @@ class EnhancedLogManager {
|
|
|
938
495
|
*/
|
|
939
496
|
getLogPaths() {
|
|
940
497
|
return {
|
|
941
|
-
clean: this.
|
|
942
|
-
raw: this.
|
|
943
|
-
absoluteRaw: this.config.keepAbsoluteRawLogs ? this.absoluteRawLogPath : undefined,
|
|
944
|
-
json: this.config.writeJsonLog ? this.jsonLogPath : undefined,
|
|
498
|
+
clean: this.readableLogPath, // For backward compatibility
|
|
499
|
+
raw: this.rawLogPath,
|
|
945
500
|
readable: this.readableLogPath,
|
|
946
501
|
};
|
|
947
502
|
}
|
|
@@ -949,26 +504,13 @@ class EnhancedLogManager {
|
|
|
949
504
|
* Create file descriptors for process stdio redirection
|
|
950
505
|
*/
|
|
951
506
|
getFileDescriptors() {
|
|
952
|
-
|
|
953
|
-
const fd = this.rawLogFd !== null ? this.rawLogFd : this.cleanLogFd;
|
|
507
|
+
const fd = this.rawLogFd;
|
|
954
508
|
return { stdout: fd, stderr: fd };
|
|
955
509
|
}
|
|
956
510
|
/**
|
|
957
|
-
* Close all log files
|
|
511
|
+
* Close all log files
|
|
958
512
|
*/
|
|
959
513
|
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
514
|
// Write session end marker
|
|
973
515
|
const endMarker = `
|
|
974
516
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
@@ -977,15 +519,6 @@ class EnhancedLogManager {
|
|
|
977
519
|
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
978
520
|
|
|
979
521
|
`;
|
|
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
522
|
if (this.rawLogFd !== null) {
|
|
990
523
|
try {
|
|
991
524
|
fs.writeSync(this.rawLogFd, endMarker);
|
|
@@ -995,34 +528,6 @@ class EnhancedLogManager {
|
|
|
995
528
|
catch { }
|
|
996
529
|
this.rawLogFd = null;
|
|
997
530
|
}
|
|
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
531
|
if (this.readableLogFd !== null) {
|
|
1027
532
|
try {
|
|
1028
533
|
fs.writeSync(this.readableLogFd, endMarker);
|
|
@@ -1034,21 +539,19 @@ class EnhancedLogManager {
|
|
|
1034
539
|
}
|
|
1035
540
|
}
|
|
1036
541
|
/**
|
|
1037
|
-
* Extract the last error message from the
|
|
542
|
+
* Extract the last error message from the log
|
|
1038
543
|
*/
|
|
1039
544
|
getLastError() {
|
|
1040
545
|
try {
|
|
1041
|
-
if (!fs.existsSync(this.
|
|
546
|
+
if (!fs.existsSync(this.readableLogPath))
|
|
1042
547
|
return null;
|
|
1043
|
-
const content = fs.readFileSync(this.
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
l.includes('❌') ||
|
|
548
|
+
const content = fs.readFileSync(this.readableLogPath, 'utf8');
|
|
549
|
+
const lines = content.split('\n').filter(l => l.includes('❌') ||
|
|
550
|
+
l.includes('[ERROR]') ||
|
|
1047
551
|
l.includes('error:') ||
|
|
1048
552
|
l.includes('Fatal') ||
|
|
1049
553
|
l.includes('fail'));
|
|
1050
554
|
if (lines.length === 0) {
|
|
1051
|
-
// Fallback to last 5 lines if no specific error marker found
|
|
1052
555
|
const allLines = content.split('\n').filter(l => l.trim());
|
|
1053
556
|
return allLines.slice(-5).join('\n');
|
|
1054
557
|
}
|
|
@@ -1078,154 +581,45 @@ exports.EnhancedLogManager = EnhancedLogManager;
|
|
|
1078
581
|
/**
|
|
1079
582
|
* Create a log manager for a lane
|
|
1080
583
|
*/
|
|
1081
|
-
function createLogManager(laneRunDir, laneName, config, onParsedMessage) {
|
|
584
|
+
function createLogManager(laneRunDir, laneName, config, onParsedMessage, laneIndex) {
|
|
1082
585
|
const session = {
|
|
1083
586
|
id: `${laneName}-${Date.now().toString(36)}`,
|
|
1084
587
|
laneName,
|
|
1085
588
|
startTime: Date.now(),
|
|
589
|
+
laneIndex: laneIndex ?? 0,
|
|
590
|
+
taskIndex: 0,
|
|
1086
591
|
};
|
|
1087
592
|
return new EnhancedLogManager(laneRunDir, session, config, onParsedMessage);
|
|
1088
593
|
}
|
|
1089
594
|
/**
|
|
1090
|
-
* Read and parse JSON log file
|
|
595
|
+
* Read and parse JSON log file (legacy compatibility - returns empty array)
|
|
1091
596
|
*/
|
|
1092
597
|
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
|
-
}
|
|
598
|
+
return [];
|
|
1114
599
|
}
|
|
1115
600
|
/**
|
|
1116
|
-
* Export logs
|
|
601
|
+
* Export logs (legacy compatibility)
|
|
1117
602
|
*/
|
|
1118
603
|
function exportLogs(laneRunDir, format, outputPath) {
|
|
1119
|
-
const
|
|
1120
|
-
const jsonLogPath = (0, path_1.safeJoin)(laneRunDir, 'terminal.jsonl');
|
|
604
|
+
const readableLogPath = (0, path_1.safeJoin)(laneRunDir, 'terminal-readable.log');
|
|
1121
605
|
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;
|
|
606
|
+
if (fs.existsSync(readableLogPath)) {
|
|
607
|
+
output = fs.readFileSync(readableLogPath, 'utf8');
|
|
1138
608
|
}
|
|
1139
609
|
if (outputPath) {
|
|
1140
610
|
fs.writeFileSync(outputPath, output, 'utf8');
|
|
1141
611
|
}
|
|
1142
612
|
return output;
|
|
1143
613
|
}
|
|
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;
|
|
614
|
+
// Legacy exports for backward compatibility
|
|
615
|
+
class StreamingMessageParser {
|
|
616
|
+
constructor(onMessage) { }
|
|
617
|
+
parseLine(line) { }
|
|
618
|
+
flush() { }
|
|
1222
619
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
.replace(/</g, '<')
|
|
1227
|
-
.replace(/>/g, '>')
|
|
1228
|
-
.replace(/"/g, '"')
|
|
1229
|
-
.replace(/'/g, ''');
|
|
620
|
+
exports.StreamingMessageParser = StreamingMessageParser;
|
|
621
|
+
class CleanLogTransform {
|
|
622
|
+
constructor(config, session) { }
|
|
1230
623
|
}
|
|
624
|
+
exports.CleanLogTransform = CleanLogTransform;
|
|
1231
625
|
//# sourceMappingURL=enhanced-logger.js.map
|