@litmers/cursorflow-orchestrator 0.1.20 → 0.1.28
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 +20 -0
- package/commands/cursorflow-clean.md +19 -0
- package/commands/cursorflow-runs.md +59 -0
- package/commands/cursorflow-stop.md +55 -0
- package/dist/cli/clean.js +171 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +83 -42
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1007 -189
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +87 -3
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +188 -236
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +125 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.d.ts +5 -0
- package/dist/cli/runs.js +214 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/setup-commands.js +0 -0
- package/dist/cli/signal.js +1 -1
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/stop.d.ts +5 -0
- package/dist/cli/stop.js +215 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/tasks.d.ts +10 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +212 -0
- package/dist/core/auto-recovery.js +737 -0
- package/dist/core/auto-recovery.js.map +1 -0
- package/dist/core/failure-policy.d.ts +156 -0
- package/dist/core/failure-policy.js +488 -0
- package/dist/core/failure-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +15 -2
- package/dist/core/orchestrator.js +397 -15
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +2 -0
- package/dist/core/reviewer.js +2 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +33 -10
- package/dist/core/runner.js +321 -146
- package/dist/core/runner.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +67 -0
- package/dist/services/logging/buffer.js +309 -0
- package/dist/services/logging/buffer.js.map +1 -0
- package/dist/services/logging/console.d.ts +89 -0
- package/dist/services/logging/console.js +169 -0
- package/dist/services/logging/console.js.map +1 -0
- package/dist/services/logging/file-writer.d.ts +71 -0
- package/dist/services/logging/file-writer.js +516 -0
- package/dist/services/logging/file-writer.js.map +1 -0
- package/dist/services/logging/formatter.d.ts +39 -0
- package/dist/services/logging/formatter.js +227 -0
- package/dist/services/logging/formatter.js.map +1 -0
- package/dist/services/logging/index.d.ts +11 -0
- package/dist/services/logging/index.js +30 -0
- package/dist/services/logging/index.js.map +1 -0
- package/dist/services/logging/parser.d.ts +31 -0
- package/dist/services/logging/parser.js +222 -0
- package/dist/services/logging/parser.js.map +1 -0
- package/dist/services/process/index.d.ts +59 -0
- package/dist/services/process/index.js +257 -0
- package/dist/services/process/index.js.map +1 -0
- package/dist/types/agent.d.ts +20 -0
- package/dist/types/agent.js +6 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/config.d.ts +65 -0
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +125 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lane.d.ts +43 -0
- package/dist/types/lane.js +6 -0
- package/dist/types/lane.js.map +1 -0
- package/dist/types/logging.d.ts +71 -0
- package/dist/types/logging.js +16 -0
- package/dist/types/logging.js.map +1 -0
- package/dist/types/review.d.ts +17 -0
- package/dist/types/review.js +6 -0
- package/dist/types/review.js.map +1 -0
- package/dist/types/run.d.ts +32 -0
- package/dist/types/run.js +6 -0
- package/dist/types/run.js.map +1 -0
- package/dist/types/task.d.ts +71 -0
- package/dist/types/task.js +6 -0
- package/dist/types/task.js.map +1 -0
- package/dist/ui/components.d.ts +134 -0
- package/dist/ui/components.js +389 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/log-viewer.d.ts +49 -0
- package/dist/ui/log-viewer.js +449 -0
- package/dist/ui/log-viewer.js.map +1 -0
- package/dist/utils/checkpoint.d.ts +87 -0
- package/dist/utils/checkpoint.js +317 -0
- package/dist/utils/checkpoint.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.js +11 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/dependency.d.ts +74 -0
- package/dist/utils/dependency.js +420 -0
- package/dist/utils/dependency.js.map +1 -0
- package/dist/utils/doctor.js +10 -5
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +94 -9
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +322 -2
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.d.ts +91 -0
- package/dist/utils/health.js +556 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/lock.d.ts +95 -0
- package/dist/utils/lock.js +332 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/log-buffer.d.ts +17 -0
- package/dist/utils/log-buffer.js +14 -0
- package/dist/utils/log-buffer.js.map +1 -0
- package/dist/utils/log-constants.d.ts +23 -0
- package/dist/utils/log-constants.js +28 -0
- package/dist/utils/log-constants.js.map +1 -0
- package/dist/utils/log-formatter.d.ts +9 -0
- package/dist/utils/log-formatter.js +113 -70
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/log-service.d.ts +19 -0
- package/dist/utils/log-service.js +47 -0
- package/dist/utils/log-service.js.map +1 -0
- package/dist/utils/logger.d.ts +46 -27
- package/dist/utils/logger.js +82 -60
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/process-manager.d.ts +21 -0
- package/dist/utils/process-manager.js +138 -0
- package/dist/utils/process-manager.js.map +1 -0
- package/dist/utils/retry.d.ts +121 -0
- package/dist/utils/retry.js +374 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/run-service.d.ts +88 -0
- package/dist/utils/run-service.js +412 -0
- package/dist/utils/run-service.js.map +1 -0
- package/dist/utils/state.d.ts +58 -2
- package/dist/utils/state.js +306 -3
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +82 -0
- package/dist/utils/task-service.js +348 -0
- package/dist/utils/task-service.js.map +1 -0
- package/dist/utils/types.d.ts +2 -272
- package/dist/utils/types.js +16 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +38 -23
- package/scripts/ai-security-check.js +0 -1
- package/scripts/local-security-gate.sh +0 -0
- package/scripts/monitor-lanes.sh +94 -0
- package/scripts/patches/test-cursor-agent.js +0 -1
- package/scripts/release.sh +0 -0
- package/scripts/setup-security.sh +0 -0
- package/scripts/stream-logs.sh +72 -0
- package/scripts/verify-and-fix.sh +0 -0
- package/src/cli/clean.ts +180 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/init.ts +1 -1
- package/src/cli/logs.ts +79 -42
- package/src/cli/monitor.ts +1815 -899
- package/src/cli/prepare.ts +97 -3
- package/src/cli/resume.ts +220 -277
- package/src/cli/run.ts +154 -3
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +1 -1
- package/src/cli/stop.ts +209 -0
- package/src/cli/tasks.ts +154 -0
- package/src/core/auto-recovery.ts +909 -0
- package/src/core/failure-policy.ts +592 -0
- package/src/core/orchestrator.ts +1136 -675
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +1443 -1217
- package/src/services/logging/buffer.ts +326 -0
- package/src/services/logging/console.ts +193 -0
- package/src/services/logging/file-writer.ts +526 -0
- package/src/services/logging/formatter.ts +268 -0
- package/src/services/logging/index.ts +16 -0
- package/src/services/logging/parser.ts +232 -0
- package/src/services/process/index.ts +261 -0
- package/src/types/agent.ts +24 -0
- package/src/types/config.ts +79 -0
- package/src/types/events.ts +156 -0
- package/src/types/index.ts +29 -0
- package/src/types/lane.ts +56 -0
- package/src/types/logging.ts +96 -0
- package/src/types/review.ts +20 -0
- package/src/types/run.ts +37 -0
- package/src/types/task.ts +79 -0
- package/src/ui/components.ts +430 -0
- package/src/ui/log-viewer.ts +485 -0
- package/src/utils/checkpoint.ts +374 -0
- package/src/utils/config.ts +11 -2
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +11 -5
- package/src/utils/enhanced-logger.ts +108 -49
- package/src/utils/git.ts +871 -499
- package/src/utils/health.ts +596 -0
- package/src/utils/lock.ts +346 -0
- package/src/utils/log-buffer.ts +28 -0
- package/src/utils/log-constants.ts +26 -0
- package/src/utils/log-formatter.ts +120 -37
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/process-manager.ts +100 -0
- package/src/utils/retry.ts +413 -0
- package/src/utils/run-service.ts +433 -0
- package/src/utils/state.ts +369 -3
- package/src/utils/task-service.ts +370 -0
- package/src/utils/types.ts +2 -315
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Formatter - Human-readable log formatting
|
|
3
|
+
*
|
|
4
|
+
* Formats log messages for console display with various styles.
|
|
5
|
+
*
|
|
6
|
+
* Rules:
|
|
7
|
+
* - Box format only for: user, assistant, system, result
|
|
8
|
+
* - Compact format for: tool, tool_result, thinking (gray/dim)
|
|
9
|
+
* - Tool names simplified: ShellToolCall → Shell
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { COLORS } from './console';
|
|
13
|
+
import { ParsedMessage, MessageType } from '../../types/logging';
|
|
14
|
+
|
|
15
|
+
// Types that should use box format
|
|
16
|
+
const BOX_TYPES = new Set(['user', 'assistant', 'system', 'result']);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Strip ANSI escape sequences from text
|
|
20
|
+
*/
|
|
21
|
+
export function stripAnsi(text: string): string {
|
|
22
|
+
const EXTENDED_ANSI_REGEX = /(?:\x1B[@-Z\\-_]|\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)|\x1B[PX^_][^\x1B]*\x1B\\|\x1B.)/g;
|
|
23
|
+
const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
|
24
|
+
|
|
25
|
+
return text
|
|
26
|
+
.replace(EXTENDED_ANSI_REGEX, '')
|
|
27
|
+
.replace(ANSI_REGEX, '')
|
|
28
|
+
.replace(/\r[^\n]/g, '\n')
|
|
29
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Simplify tool names (ShellToolCall → Shell, etc.)
|
|
34
|
+
*/
|
|
35
|
+
function simplifyToolName(name: string): string {
|
|
36
|
+
return name
|
|
37
|
+
.replace(/ToolCall$/i, '')
|
|
38
|
+
.replace(/Tool$/i, '')
|
|
39
|
+
.replace(/^run_terminal_cmd$/i, 'shell')
|
|
40
|
+
.replace(/^search_replace$/i, 'edit');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Format options for console display
|
|
45
|
+
*/
|
|
46
|
+
export interface FormatOptions {
|
|
47
|
+
includeTimestamp?: boolean;
|
|
48
|
+
laneLabel?: string;
|
|
49
|
+
compact?: boolean;
|
|
50
|
+
showBorders?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Format a parsed message for console display
|
|
55
|
+
*/
|
|
56
|
+
export function formatMessageForConsole(
|
|
57
|
+
msg: ParsedMessage,
|
|
58
|
+
options: FormatOptions = {}
|
|
59
|
+
): string {
|
|
60
|
+
const { includeTimestamp = true, laneLabel = '', compact = false, showBorders = true } = options;
|
|
61
|
+
|
|
62
|
+
const ts = includeTimestamp
|
|
63
|
+
? new Date(msg.timestamp).toLocaleTimeString('en-US', { hour12: false })
|
|
64
|
+
: '';
|
|
65
|
+
const tsPrefix = ts ? `${COLORS.gray}[${ts}]${COLORS.reset} ` : '';
|
|
66
|
+
|
|
67
|
+
// Lane label max 16 chars
|
|
68
|
+
const truncatedLabel = laneLabel.length > 16 ? laneLabel.substring(0, 16) : laneLabel;
|
|
69
|
+
const labelPrefix = truncatedLabel
|
|
70
|
+
? `${COLORS.magenta}${truncatedLabel.padEnd(16)}${COLORS.reset} `
|
|
71
|
+
: '';
|
|
72
|
+
|
|
73
|
+
// Determine if should use box format
|
|
74
|
+
const useBox = !compact && showBorders && BOX_TYPES.has(msg.type);
|
|
75
|
+
const { typePrefix, formattedContent } = formatMessageContent(msg, !useBox);
|
|
76
|
+
|
|
77
|
+
if (!typePrefix) return `${tsPrefix}${labelPrefix}${formattedContent}`;
|
|
78
|
+
|
|
79
|
+
if (!useBox) {
|
|
80
|
+
return `${tsPrefix}${labelPrefix}${typePrefix.padEnd(12)} ${formattedContent}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Multi-line box format (only for user, assistant, system, result)
|
|
84
|
+
// Emoji width is 2, so we need to account for that in indent calculation
|
|
85
|
+
const lines = formattedContent.split('\n');
|
|
86
|
+
const fullPrefix = `${tsPrefix}${labelPrefix}`;
|
|
87
|
+
const strippedPrefix = stripAnsi(typePrefix);
|
|
88
|
+
// Count emojis (they take 2 terminal columns but 1-2 chars in string)
|
|
89
|
+
const emojiCount = (strippedPrefix.match(/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2300}-\u{23FF}]|[\u{2B50}-\u{2B55}]|[\u{231A}-\u{231B}]|[\u{23E9}-\u{23F3}]|[\u{23F8}-\u{23FA}]|✅|❌|⚙️|ℹ️|⚠️|🔧|📄|🤔|🧑|🤖/gu) || []).length;
|
|
90
|
+
const visualWidth = strippedPrefix.length + emojiCount; // emoji adds 1 extra width
|
|
91
|
+
|
|
92
|
+
const boxWidth = 60;
|
|
93
|
+
const header = `${typePrefix}┌${'─'.repeat(boxWidth)}`;
|
|
94
|
+
let result = `${fullPrefix}${header}\n`;
|
|
95
|
+
|
|
96
|
+
const indent = ' '.repeat(visualWidth);
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
result += `${fullPrefix}${indent}│ ${line}\n`;
|
|
99
|
+
}
|
|
100
|
+
result += `${fullPrefix}${indent}└${'─'.repeat(boxWidth)}`;
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function formatMessageContent(msg: ParsedMessage, forceCompact: boolean): { typePrefix: string; formattedContent: string } {
|
|
106
|
+
let typePrefix = '';
|
|
107
|
+
let formattedContent = msg.content;
|
|
108
|
+
|
|
109
|
+
// For thinking: collapse multiple newlines
|
|
110
|
+
if (msg.type === 'thinking') {
|
|
111
|
+
formattedContent = formattedContent.replace(/\n\s*\n/g, ' ').replace(/\n/g, ' ').trim();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
switch (msg.type) {
|
|
115
|
+
case 'user':
|
|
116
|
+
typePrefix = `${COLORS.cyan}🧑 USER${COLORS.reset}`;
|
|
117
|
+
if (forceCompact) formattedContent = truncate(formattedContent.replace(/\n/g, ' '), 100);
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'assistant':
|
|
121
|
+
typePrefix = `${COLORS.green}🤖 ASST${COLORS.reset}`;
|
|
122
|
+
if (forceCompact) formattedContent = truncate(formattedContent.replace(/\n/g, ' '), 100);
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case 'tool':
|
|
126
|
+
// Tool calls are always gray
|
|
127
|
+
typePrefix = `${COLORS.gray}🔧 TOOL${COLORS.reset}`;
|
|
128
|
+
formattedContent = formatToolCall(formattedContent);
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case 'tool_result':
|
|
132
|
+
// Tool results are always gray
|
|
133
|
+
typePrefix = `${COLORS.gray}📄 RESL${COLORS.reset}`;
|
|
134
|
+
const resMatch = formattedContent.match(/\[Tool Result: ([^\]]+)\]/);
|
|
135
|
+
if (resMatch) {
|
|
136
|
+
const simpleName = simplifyToolName(resMatch[1]!);
|
|
137
|
+
formattedContent = `${COLORS.gray}${simpleName} OK${COLORS.reset}`;
|
|
138
|
+
} else {
|
|
139
|
+
formattedContent = `${COLORS.gray}result${COLORS.reset}`;
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case 'result':
|
|
144
|
+
typePrefix = `${COLORS.green}✅ DONE${COLORS.reset}`;
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case 'system':
|
|
148
|
+
typePrefix = `${COLORS.gray}⚙️ SYS${COLORS.reset}`;
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case 'thinking':
|
|
152
|
+
// Thinking is always gray and compact
|
|
153
|
+
typePrefix = `${COLORS.gray}🤔 THNK${COLORS.reset}`;
|
|
154
|
+
formattedContent = `${COLORS.gray}${truncate(formattedContent, 100)}${COLORS.reset}`;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { typePrefix, formattedContent };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function formatToolCall(content: string): string {
|
|
162
|
+
const toolMatch = content.match(/\[Tool: ([^\]]+)\] (.*)/);
|
|
163
|
+
if (!toolMatch) return content;
|
|
164
|
+
|
|
165
|
+
const [, rawName, args] = toolMatch;
|
|
166
|
+
const name = simplifyToolName(rawName!);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const parsedArgs = JSON.parse(args!);
|
|
170
|
+
let argStr = '';
|
|
171
|
+
|
|
172
|
+
if (rawName === 'read_file' && parsedArgs.target_file) {
|
|
173
|
+
argStr = parsedArgs.target_file;
|
|
174
|
+
} else if (rawName === 'run_terminal_cmd' && parsedArgs.command) {
|
|
175
|
+
argStr = parsedArgs.command;
|
|
176
|
+
} else if (rawName === 'write' && parsedArgs.file_path) {
|
|
177
|
+
argStr = parsedArgs.file_path;
|
|
178
|
+
} else if (rawName === 'search_replace' && parsedArgs.file_path) {
|
|
179
|
+
argStr = parsedArgs.file_path;
|
|
180
|
+
} else {
|
|
181
|
+
const keys = Object.keys(parsedArgs);
|
|
182
|
+
if (keys.length > 0) {
|
|
183
|
+
argStr = String(parsedArgs[keys[0]]).substring(0, 50);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return `${COLORS.gray}${name}${COLORS.reset}(${COLORS.gray}${argStr}${COLORS.reset})`;
|
|
188
|
+
} catch {
|
|
189
|
+
return `${COLORS.gray}${name}${COLORS.reset}: ${args}`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function truncate(str: string, maxLength: number): string {
|
|
194
|
+
if (str.length <= maxLength) return str;
|
|
195
|
+
return str.substring(0, maxLength) + '...';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Format a readable log entry for display
|
|
200
|
+
*/
|
|
201
|
+
export function formatReadableEntry(
|
|
202
|
+
timestamp: Date,
|
|
203
|
+
laneName: string,
|
|
204
|
+
type: MessageType,
|
|
205
|
+
content: string,
|
|
206
|
+
options: { showLane?: boolean; maxWidth?: number } = {}
|
|
207
|
+
): string {
|
|
208
|
+
const { showLane = true, maxWidth = 100 } = options;
|
|
209
|
+
|
|
210
|
+
const ts = timestamp.toLocaleTimeString('en-US', { hour12: false });
|
|
211
|
+
const laneStr = showLane ? `[${laneName.padEnd(12)}] ` : '';
|
|
212
|
+
|
|
213
|
+
const typeInfo = getTypeInfo(type);
|
|
214
|
+
const truncatedContent = content.length > maxWidth
|
|
215
|
+
? content.substring(0, maxWidth - 3) + '...'
|
|
216
|
+
: content;
|
|
217
|
+
|
|
218
|
+
return `${COLORS.gray}[${ts}]${COLORS.reset} ${laneStr}${typeInfo.color}[${typeInfo.label}]${COLORS.reset} ${truncatedContent}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getTypeInfo(type: MessageType): { label: string; color: string } {
|
|
222
|
+
const typeMap: Record<MessageType, { label: string; color: string }> = {
|
|
223
|
+
user: { label: 'USER ', color: COLORS.cyan },
|
|
224
|
+
assistant: { label: 'ASST ', color: COLORS.green },
|
|
225
|
+
tool: { label: 'TOOL ', color: COLORS.yellow },
|
|
226
|
+
tool_result: { label: 'RESULT', color: COLORS.gray },
|
|
227
|
+
result: { label: 'DONE ', color: COLORS.green },
|
|
228
|
+
system: { label: 'SYSTEM', color: COLORS.gray },
|
|
229
|
+
thinking: { label: 'THINK ', color: COLORS.gray },
|
|
230
|
+
success: { label: 'OK ', color: COLORS.green },
|
|
231
|
+
info: { label: 'INFO ', color: COLORS.cyan },
|
|
232
|
+
warn: { label: 'WARN ', color: COLORS.yellow },
|
|
233
|
+
error: { label: 'ERROR ', color: COLORS.red },
|
|
234
|
+
stdout: { label: 'STDOUT', color: COLORS.white },
|
|
235
|
+
stderr: { label: 'STDERR', color: COLORS.red },
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return typeMap[type] || { label: type.toUpperCase().padEnd(6), color: COLORS.white };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Format a timestamp for log files
|
|
243
|
+
*/
|
|
244
|
+
export function formatTimestampISO(format: 'iso' | 'relative' | 'short', startTime?: number): string {
|
|
245
|
+
const now = Date.now();
|
|
246
|
+
|
|
247
|
+
switch (format) {
|
|
248
|
+
case 'iso':
|
|
249
|
+
return new Date(now).toISOString();
|
|
250
|
+
case 'relative':
|
|
251
|
+
if (startTime) {
|
|
252
|
+
const elapsed = now - startTime;
|
|
253
|
+
const seconds = Math.floor(elapsed / 1000);
|
|
254
|
+
const minutes = Math.floor(seconds / 60);
|
|
255
|
+
const hours = Math.floor(minutes / 60);
|
|
256
|
+
|
|
257
|
+
if (hours > 0) return `+${hours}h${minutes % 60}m${seconds % 60}s`;
|
|
258
|
+
if (minutes > 0) return `+${minutes}m${seconds % 60}s`;
|
|
259
|
+
return `+${seconds}s`;
|
|
260
|
+
}
|
|
261
|
+
return new Date(now).toISOString();
|
|
262
|
+
case 'short':
|
|
263
|
+
return new Date(now).toLocaleTimeString('en-US', { hour12: false });
|
|
264
|
+
default:
|
|
265
|
+
return new Date(now).toISOString();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging Service - Unified logging module for CursorFlow
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all logging-related functionality from a single entry point.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Core logging utilities
|
|
8
|
+
export * from './console';
|
|
9
|
+
export * from './formatter';
|
|
10
|
+
export * from './parser';
|
|
11
|
+
export * from './buffer';
|
|
12
|
+
export * from './file-writer';
|
|
13
|
+
|
|
14
|
+
// Re-export types
|
|
15
|
+
export * from '../../types/logging';
|
|
16
|
+
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Parser - Parse cursor-agent streaming output
|
|
3
|
+
*
|
|
4
|
+
* Parses JSON output from cursor-agent and combines tokens into readable messages.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ParsedMessage, MessageType } from '../../types/logging';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Streaming message parser for cursor-agent output
|
|
11
|
+
*/
|
|
12
|
+
export class StreamingMessageParser {
|
|
13
|
+
private currentMessage = '';
|
|
14
|
+
private currentRole = '';
|
|
15
|
+
private messageStartTime = 0;
|
|
16
|
+
private onMessage: (msg: ParsedMessage) => void;
|
|
17
|
+
|
|
18
|
+
constructor(onMessage: (msg: ParsedMessage) => void) {
|
|
19
|
+
this.onMessage = onMessage;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse a line of JSON output
|
|
24
|
+
*/
|
|
25
|
+
parseLine(line: string): void {
|
|
26
|
+
const trimmed = line.trim();
|
|
27
|
+
if (!trimmed || !trimmed.startsWith('{')) return;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const json = JSON.parse(trimmed);
|
|
31
|
+
this.handleJsonMessage(json);
|
|
32
|
+
} catch {
|
|
33
|
+
// Not valid JSON, ignore
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private handleJsonMessage(json: any): void {
|
|
38
|
+
const type = json.type;
|
|
39
|
+
|
|
40
|
+
switch (type) {
|
|
41
|
+
case 'system':
|
|
42
|
+
this.emitMessage({
|
|
43
|
+
type: 'system',
|
|
44
|
+
role: 'system',
|
|
45
|
+
content: `[System] Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
|
|
46
|
+
timestamp: json.timestamp_ms || Date.now(),
|
|
47
|
+
});
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case 'user':
|
|
51
|
+
if (json.message?.content) {
|
|
52
|
+
const textContent = this.extractTextContent(json.message.content);
|
|
53
|
+
this.emitMessage({
|
|
54
|
+
type: 'user',
|
|
55
|
+
role: 'user',
|
|
56
|
+
content: textContent,
|
|
57
|
+
timestamp: json.timestamp_ms || Date.now(),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
case 'assistant':
|
|
63
|
+
if (json.message?.content) {
|
|
64
|
+
const textContent = this.extractTextContent(json.message.content);
|
|
65
|
+
|
|
66
|
+
if (this.currentRole !== 'assistant') {
|
|
67
|
+
this.flush();
|
|
68
|
+
this.currentRole = 'assistant';
|
|
69
|
+
this.messageStartTime = json.timestamp_ms || Date.now();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.currentMessage += textContent;
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
case 'tool_call':
|
|
77
|
+
if (json.subtype === 'started' && json.tool_call) {
|
|
78
|
+
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
79
|
+
const toolArgs = json.tool_call[toolName]?.args || {};
|
|
80
|
+
|
|
81
|
+
this.flush();
|
|
82
|
+
|
|
83
|
+
this.emitMessage({
|
|
84
|
+
type: 'tool',
|
|
85
|
+
role: 'tool',
|
|
86
|
+
content: `[Tool: ${toolName}] ${JSON.stringify(toolArgs)}`,
|
|
87
|
+
timestamp: json.timestamp_ms || Date.now(),
|
|
88
|
+
metadata: { callId: json.call_id, toolName },
|
|
89
|
+
});
|
|
90
|
+
} else if (json.subtype === 'completed' && json.tool_call) {
|
|
91
|
+
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
92
|
+
const result = json.tool_call[toolName]?.result;
|
|
93
|
+
|
|
94
|
+
if (result?.success) {
|
|
95
|
+
const content = result.success.content || '';
|
|
96
|
+
const truncated = content.length > 500
|
|
97
|
+
? content.substring(0, 500) + '... (truncated)'
|
|
98
|
+
: content;
|
|
99
|
+
|
|
100
|
+
this.emitMessage({
|
|
101
|
+
type: 'tool_result',
|
|
102
|
+
role: 'tool',
|
|
103
|
+
content: `[Tool Result: ${toolName}] ${truncated}`,
|
|
104
|
+
timestamp: json.timestamp_ms || Date.now(),
|
|
105
|
+
metadata: { callId: json.call_id, toolName, lines: result.success.totalLines },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
case 'result':
|
|
112
|
+
this.flush();
|
|
113
|
+
this.emitMessage({
|
|
114
|
+
type: 'result',
|
|
115
|
+
role: 'assistant',
|
|
116
|
+
content: json.result || '',
|
|
117
|
+
timestamp: json.timestamp_ms || Date.now(),
|
|
118
|
+
metadata: {
|
|
119
|
+
duration_ms: json.duration_ms,
|
|
120
|
+
is_error: json.is_error,
|
|
121
|
+
subtype: json.subtype,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case 'thinking':
|
|
127
|
+
if (json.subtype === 'delta' && json.text) {
|
|
128
|
+
if (this.currentRole !== 'thinking') {
|
|
129
|
+
this.flush();
|
|
130
|
+
this.currentRole = 'thinking';
|
|
131
|
+
this.messageStartTime = json.timestamp_ms || Date.now();
|
|
132
|
+
}
|
|
133
|
+
this.currentMessage += json.text;
|
|
134
|
+
} else if (json.subtype === 'completed') {
|
|
135
|
+
this.flush();
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private extractTextContent(content: any[]): string {
|
|
142
|
+
return content
|
|
143
|
+
.filter((c: any) => c.type === 'text')
|
|
144
|
+
.map((c: any) => c.text)
|
|
145
|
+
.join('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Flush accumulated message
|
|
150
|
+
*/
|
|
151
|
+
flush(): void {
|
|
152
|
+
if (this.currentMessage && this.currentRole) {
|
|
153
|
+
this.emitMessage({
|
|
154
|
+
type: this.currentRole as MessageType,
|
|
155
|
+
role: this.currentRole,
|
|
156
|
+
content: this.currentMessage,
|
|
157
|
+
timestamp: this.messageStartTime,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
this.currentMessage = '';
|
|
161
|
+
this.currentRole = '';
|
|
162
|
+
this.messageStartTime = 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private emitMessage(msg: ParsedMessage): void {
|
|
166
|
+
if (msg.content.trim()) {
|
|
167
|
+
this.onMessage(msg);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Parse a raw JSON log file line by line
|
|
174
|
+
*/
|
|
175
|
+
export function parseJsonLogLine(line: string): ParsedMessage | null {
|
|
176
|
+
const trimmed = line.trim();
|
|
177
|
+
if (!trimmed || !trimmed.startsWith('{')) return null;
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const json = JSON.parse(trimmed);
|
|
181
|
+
return parseJsonToMessage(json);
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function parseJsonToMessage(json: any): ParsedMessage | null {
|
|
188
|
+
const type = json.type;
|
|
189
|
+
if (!type) return null;
|
|
190
|
+
|
|
191
|
+
let messageType: MessageType;
|
|
192
|
+
let content = '';
|
|
193
|
+
|
|
194
|
+
if (type === 'thinking' && (json.text || json.thought)) {
|
|
195
|
+
content = json.text || json.thought;
|
|
196
|
+
messageType = 'thinking';
|
|
197
|
+
} else if (type === 'assistant' && json.message?.content) {
|
|
198
|
+
content = json.message.content
|
|
199
|
+
.filter((c: any) => c.type === 'text')
|
|
200
|
+
.map((c: any) => c.text)
|
|
201
|
+
.join('');
|
|
202
|
+
messageType = 'assistant';
|
|
203
|
+
} else if (type === 'user' && json.message?.content) {
|
|
204
|
+
content = json.message.content
|
|
205
|
+
.filter((c: any) => c.type === 'text')
|
|
206
|
+
.map((c: any) => c.text)
|
|
207
|
+
.join('');
|
|
208
|
+
messageType = 'user';
|
|
209
|
+
} else if (type === 'tool_call' && json.subtype === 'started') {
|
|
210
|
+
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
211
|
+
const args = json.tool_call[toolName]?.args || {};
|
|
212
|
+
content = `[Tool: ${toolName}] ${JSON.stringify(args)}`;
|
|
213
|
+
messageType = 'tool';
|
|
214
|
+
} else if (type === 'tool_call' && json.subtype === 'completed') {
|
|
215
|
+
const toolName = Object.keys(json.tool_call)[0] || 'unknown';
|
|
216
|
+
content = `[Tool Result: ${toolName}]`;
|
|
217
|
+
messageType = 'tool_result';
|
|
218
|
+
} else if (type === 'result') {
|
|
219
|
+
content = json.result || 'Task completed';
|
|
220
|
+
messageType = 'result';
|
|
221
|
+
} else {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
type: messageType,
|
|
227
|
+
role: messageType,
|
|
228
|
+
content,
|
|
229
|
+
timestamp: json.timestamp_ms || Date.now(),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|