@litmers/cursorflow-orchestrator 0.1.20 → 0.1.26
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 +9 -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 +4 -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 +8 -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 +392 -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 +4 -3
- package/src/cli/resume.ts +220 -277
- package/src/cli/run.ts +9 -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 +1131 -675
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +388 -162
- 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 +374 -2
- 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,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Buffer Service - In-memory log buffer for TUI
|
|
3
|
+
*
|
|
4
|
+
* Provides real-time log streaming and filtering for the interactive monitor.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { EventEmitter } from 'events';
|
|
10
|
+
import { LogImportance, JsonLogEntry, BufferedLogEntry as BufferedLogEntryType, MessageType } from '../../types/logging';
|
|
11
|
+
import { COLORS } from './console';
|
|
12
|
+
|
|
13
|
+
// Re-export types for convenience
|
|
14
|
+
export type { BufferedLogEntry } from '../../types/logging';
|
|
15
|
+
|
|
16
|
+
export interface LogBufferOptions {
|
|
17
|
+
maxEntries?: number;
|
|
18
|
+
pollInterval?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LogFilter {
|
|
22
|
+
lane?: string;
|
|
23
|
+
importance?: LogImportance;
|
|
24
|
+
search?: string;
|
|
25
|
+
type?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LogBufferState {
|
|
29
|
+
totalEntries: number;
|
|
30
|
+
filteredCount: number;
|
|
31
|
+
newCount: number;
|
|
32
|
+
isStreaming: boolean;
|
|
33
|
+
lanes: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const LANE_COLORS = [
|
|
37
|
+
COLORS.cyan,
|
|
38
|
+
COLORS.magenta,
|
|
39
|
+
COLORS.yellow,
|
|
40
|
+
COLORS.green,
|
|
41
|
+
COLORS.blue,
|
|
42
|
+
COLORS.red,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export class LogBufferService extends EventEmitter {
|
|
46
|
+
private runDir: string;
|
|
47
|
+
private options: Required<LogBufferOptions>;
|
|
48
|
+
private entries: BufferedLogEntryType[] = [];
|
|
49
|
+
private entryIdCounter = 0;
|
|
50
|
+
private isStreaming = false;
|
|
51
|
+
private pollTimer: NodeJS.Timeout | null = null;
|
|
52
|
+
private filePositions = new Map<string, number>();
|
|
53
|
+
private lanes: string[] = [];
|
|
54
|
+
private laneColorMap = new Map<string, string>();
|
|
55
|
+
private newEntriesCount = 0;
|
|
56
|
+
private lastAcknowledgedId = 0;
|
|
57
|
+
|
|
58
|
+
constructor(runDir: string, options: LogBufferOptions = {}) {
|
|
59
|
+
super();
|
|
60
|
+
this.runDir = runDir;
|
|
61
|
+
this.options = {
|
|
62
|
+
maxEntries: options.maxEntries ?? 10000,
|
|
63
|
+
pollInterval: options.pollInterval ?? 100,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
startStreaming(): void {
|
|
68
|
+
if (this.isStreaming) return;
|
|
69
|
+
this.isStreaming = true;
|
|
70
|
+
this.discoverLanes();
|
|
71
|
+
this.pollLogs();
|
|
72
|
+
this.pollTimer = setInterval(() => this.pollLogs(), this.options.pollInterval);
|
|
73
|
+
this.emit('started');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
stopStreaming(): void {
|
|
77
|
+
if (!this.isStreaming) return;
|
|
78
|
+
this.isStreaming = false;
|
|
79
|
+
if (this.pollTimer) {
|
|
80
|
+
clearInterval(this.pollTimer);
|
|
81
|
+
this.pollTimer = null;
|
|
82
|
+
}
|
|
83
|
+
this.emit('stopped');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private discoverLanes(): void {
|
|
87
|
+
const lanesDir = path.join(this.runDir, 'lanes');
|
|
88
|
+
if (!fs.existsSync(lanesDir)) return;
|
|
89
|
+
|
|
90
|
+
const dirs = fs.readdirSync(lanesDir).filter(name => {
|
|
91
|
+
const dirPath = path.join(lanesDir, name);
|
|
92
|
+
return fs.statSync(dirPath).isDirectory();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.lanes = dirs.sort();
|
|
96
|
+
this.lanes.forEach((lane, index) => {
|
|
97
|
+
this.laneColorMap.set(lane, LANE_COLORS[index % LANE_COLORS.length]);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private pollLogs(): void {
|
|
102
|
+
const lanesDir = path.join(this.runDir, 'lanes');
|
|
103
|
+
if (!fs.existsSync(lanesDir)) return;
|
|
104
|
+
|
|
105
|
+
// Re-discover lanes to pick up new ones
|
|
106
|
+
this.discoverLanes();
|
|
107
|
+
|
|
108
|
+
const newEntries: BufferedLogEntryType[] = [];
|
|
109
|
+
|
|
110
|
+
for (const laneName of this.lanes) {
|
|
111
|
+
const jsonlPath = path.join(lanesDir, laneName, 'terminal.jsonl');
|
|
112
|
+
|
|
113
|
+
let fd: number | null = null;
|
|
114
|
+
try {
|
|
115
|
+
// Read file content atomically to avoid TOCTOU race condition
|
|
116
|
+
const lastPos = this.filePositions.get(jsonlPath) || 0;
|
|
117
|
+
fd = fs.openSync(jsonlPath, 'r');
|
|
118
|
+
const stat = fs.fstatSync(fd); // Use fstat on open fd to avoid race
|
|
119
|
+
|
|
120
|
+
if (stat.size > lastPos) {
|
|
121
|
+
const buffer = Buffer.alloc(stat.size - lastPos);
|
|
122
|
+
fs.readSync(fd, buffer, 0, buffer.length, lastPos);
|
|
123
|
+
|
|
124
|
+
const newContent = buffer.toString('utf-8');
|
|
125
|
+
const lines = newContent.split('\n').filter(line => line.trim());
|
|
126
|
+
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
try {
|
|
129
|
+
const entry = JSON.parse(line) as JsonLogEntry;
|
|
130
|
+
const processed = this.processEntry(entry, laneName);
|
|
131
|
+
if (processed) newEntries.push(processed);
|
|
132
|
+
} catch { /* Skip invalid JSON */ }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.filePositions.set(jsonlPath, stat.size);
|
|
136
|
+
}
|
|
137
|
+
} catch { /* File in use, skip */ }
|
|
138
|
+
finally {
|
|
139
|
+
if (fd !== null) {
|
|
140
|
+
try { fs.closeSync(fd); } catch { /* ignore */ }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (newEntries.length > 0) {
|
|
146
|
+
newEntries.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
147
|
+
this.entries.push(...newEntries);
|
|
148
|
+
this.newEntriesCount += newEntries.length;
|
|
149
|
+
|
|
150
|
+
if (this.entries.length > this.options.maxEntries) {
|
|
151
|
+
const excess = this.entries.length - this.options.maxEntries;
|
|
152
|
+
this.entries.splice(0, excess);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
this.emit('update', newEntries);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private processEntry(entry: JsonLogEntry, laneName: string): BufferedLogEntryType | null {
|
|
160
|
+
const timestamp = new Date(entry.timestamp || Date.now());
|
|
161
|
+
const type = (entry.type || 'unknown') as MessageType;
|
|
162
|
+
const level = entry.level || this.inferLevel(type);
|
|
163
|
+
const message = entry.content || entry.message || JSON.stringify(entry);
|
|
164
|
+
const importance = this.inferImportance(type, level);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
id: ++this.entryIdCounter,
|
|
168
|
+
timestamp,
|
|
169
|
+
laneName,
|
|
170
|
+
level,
|
|
171
|
+
type,
|
|
172
|
+
message: this.truncateMessage(message),
|
|
173
|
+
importance,
|
|
174
|
+
laneColor: this.laneColorMap.get(laneName) || COLORS.white,
|
|
175
|
+
raw: entry,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private inferLevel(type: string): string {
|
|
180
|
+
switch (type.toLowerCase()) {
|
|
181
|
+
case 'error':
|
|
182
|
+
case 'stderr':
|
|
183
|
+
return 'error';
|
|
184
|
+
case 'warning':
|
|
185
|
+
return 'warn';
|
|
186
|
+
case 'debug':
|
|
187
|
+
case 'thinking':
|
|
188
|
+
return 'debug';
|
|
189
|
+
default:
|
|
190
|
+
return 'info';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private inferImportance(type: string, level: string): LogImportance {
|
|
195
|
+
if (level === 'error' || type === 'error' || type === 'result') return LogImportance.HIGH;
|
|
196
|
+
if (type === 'tool' || type === 'tool_result') return LogImportance.MEDIUM;
|
|
197
|
+
if (type === 'thinking' || level === 'debug') return LogImportance.DEBUG;
|
|
198
|
+
if (type === 'assistant' || type === 'user') return LogImportance.MEDIUM;
|
|
199
|
+
return LogImportance.INFO;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private truncateMessage(message: string, maxLength = 500): string {
|
|
203
|
+
if (message.length <= maxLength) return message;
|
|
204
|
+
return message.substring(0, maxLength) + '...';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
getEntries(options: {
|
|
208
|
+
offset?: number;
|
|
209
|
+
limit?: number;
|
|
210
|
+
filter?: LogFilter;
|
|
211
|
+
fromEnd?: boolean;
|
|
212
|
+
} = {}): BufferedLogEntryType[] {
|
|
213
|
+
const { offset = 0, limit = 100, filter, fromEnd = true } = options;
|
|
214
|
+
let filtered = this.applyFilter(this.entries, filter);
|
|
215
|
+
|
|
216
|
+
if (fromEnd) {
|
|
217
|
+
const start = Math.max(0, filtered.length - offset - limit);
|
|
218
|
+
const end = Math.max(0, filtered.length - offset);
|
|
219
|
+
return filtered.slice(start, end);
|
|
220
|
+
}
|
|
221
|
+
return filtered.slice(offset, offset + limit);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private applyFilter(entries: BufferedLogEntryType[], filter?: LogFilter): BufferedLogEntryType[] {
|
|
225
|
+
if (!filter) return entries;
|
|
226
|
+
|
|
227
|
+
return entries.filter(entry => {
|
|
228
|
+
if (filter.lane && entry.laneName !== filter.lane) return false;
|
|
229
|
+
|
|
230
|
+
if (filter.importance) {
|
|
231
|
+
const order = [LogImportance.DEBUG, LogImportance.INFO, LogImportance.LOW,
|
|
232
|
+
LogImportance.MEDIUM, LogImportance.HIGH, LogImportance.CRITICAL];
|
|
233
|
+
if (order.indexOf(entry.importance) < order.indexOf(filter.importance)) return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (filter.type && entry.type !== filter.type) return false;
|
|
237
|
+
|
|
238
|
+
if (filter.search) {
|
|
239
|
+
const searchLower = filter.search.toLowerCase();
|
|
240
|
+
if (!entry.message.toLowerCase().includes(searchLower) &&
|
|
241
|
+
!entry.type.toLowerCase().includes(searchLower)) return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return true;
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
getTotalCount(filter?: LogFilter): number {
|
|
249
|
+
if (!filter) return this.entries.length;
|
|
250
|
+
return this.applyFilter(this.entries, filter).length;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
getNewEntriesCount(): number {
|
|
254
|
+
return this.newEntriesCount;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
acknowledgeNewEntries(): void {
|
|
258
|
+
this.newEntriesCount = 0;
|
|
259
|
+
this.lastAcknowledgedId = this.entryIdCounter;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
getLanes(): string[] {
|
|
263
|
+
return [...this.lanes];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getState(): LogBufferState {
|
|
267
|
+
return {
|
|
268
|
+
totalEntries: this.entries.length,
|
|
269
|
+
filteredCount: this.entries.length,
|
|
270
|
+
newCount: this.newEntriesCount,
|
|
271
|
+
isStreaming: this.isStreaming,
|
|
272
|
+
lanes: this.getLanes(),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
getLaneColor(laneName: string): string {
|
|
277
|
+
return this.laneColorMap.get(laneName) || COLORS.white;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
clear(): void {
|
|
281
|
+
this.entries = [];
|
|
282
|
+
this.entryIdCounter = 0;
|
|
283
|
+
this.newEntriesCount = 0;
|
|
284
|
+
this.lastAcknowledgedId = 0;
|
|
285
|
+
this.filePositions.clear();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
formatEntry(entry: BufferedLogEntryType, options: { showLane?: boolean; showTimestamp?: boolean } = {}): string {
|
|
289
|
+
const { showLane = true, showTimestamp = true } = options;
|
|
290
|
+
const parts: string[] = [];
|
|
291
|
+
|
|
292
|
+
if (showTimestamp) {
|
|
293
|
+
const ts = entry.timestamp.toLocaleTimeString('en-US', { hour12: false });
|
|
294
|
+
parts.push(`${COLORS.gray}[${ts}]${COLORS.reset}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (showLane) {
|
|
298
|
+
parts.push(`${entry.laneColor}[${entry.laneName.padEnd(12)}]${COLORS.reset}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
parts.push(this.getTypeIndicator(entry.type));
|
|
302
|
+
parts.push(entry.message);
|
|
303
|
+
|
|
304
|
+
return parts.join(' ');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private getTypeIndicator(type: string): string {
|
|
308
|
+
const indicators: Record<string, string> = {
|
|
309
|
+
user: `${COLORS.cyan}[USER ]${COLORS.reset}`,
|
|
310
|
+
assistant: `${COLORS.green}[ASST ]${COLORS.reset}`,
|
|
311
|
+
tool: `${COLORS.yellow}[TOOL ]${COLORS.reset}`,
|
|
312
|
+
tool_result: `${COLORS.gray}[RESULT]${COLORS.reset}`,
|
|
313
|
+
error: `${COLORS.red}[ERROR ]${COLORS.reset}`,
|
|
314
|
+
stderr: `${COLORS.red}[ERROR ]${COLORS.reset}`,
|
|
315
|
+
thinking: `${COLORS.gray}[THINK ]${COLORS.reset}`,
|
|
316
|
+
result: `${COLORS.green}[DONE ]${COLORS.reset}`,
|
|
317
|
+
stdout: `${COLORS.white}[STDOUT]${COLORS.reset}`,
|
|
318
|
+
};
|
|
319
|
+
return indicators[type.toLowerCase()] || `${COLORS.gray}[${type.toUpperCase().padEnd(6)}]${COLORS.reset}`;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function createLogBuffer(runDir: string, options?: LogBufferOptions): LogBufferService {
|
|
324
|
+
return new LogBufferService(runDir, options);
|
|
325
|
+
}
|
|
326
|
+
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console Logging Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides formatted console output with colors, timestamps, and context support.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export enum LogLevel {
|
|
8
|
+
error = 0,
|
|
9
|
+
warn = 1,
|
|
10
|
+
info = 2,
|
|
11
|
+
debug = 3,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const COLORS = {
|
|
15
|
+
reset: '\x1b[0m',
|
|
16
|
+
red: '\x1b[31m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
green: '\x1b[32m',
|
|
19
|
+
blue: '\x1b[34m',
|
|
20
|
+
cyan: '\x1b[36m',
|
|
21
|
+
magenta: '\x1b[35m',
|
|
22
|
+
gray: '\x1b[90m',
|
|
23
|
+
bold: '\x1b[1m',
|
|
24
|
+
white: '\x1b[37m',
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
let currentLogLevel: number = LogLevel.info;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set log level
|
|
31
|
+
*/
|
|
32
|
+
export function setLogLevel(level: string | number): void {
|
|
33
|
+
if (typeof level === 'string') {
|
|
34
|
+
currentLogLevel = (LogLevel as any)[level] ?? LogLevel.info;
|
|
35
|
+
} else {
|
|
36
|
+
currentLogLevel = level;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get current log level
|
|
42
|
+
*/
|
|
43
|
+
export function getLogLevel(): number {
|
|
44
|
+
return currentLogLevel;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Format timestamp as [HH:MM:SS] in local time
|
|
49
|
+
*/
|
|
50
|
+
export function formatTimestamp(date: Date = new Date()): string {
|
|
51
|
+
return date.toLocaleTimeString('en-US', { hour12: false });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Log options interface for contextual logging
|
|
56
|
+
*/
|
|
57
|
+
export interface LogOptions {
|
|
58
|
+
context?: string;
|
|
59
|
+
emoji?: string;
|
|
60
|
+
noTimestamp?: boolean;
|
|
61
|
+
color?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Internal log function with color support
|
|
66
|
+
*/
|
|
67
|
+
function logWithColor(
|
|
68
|
+
color: string,
|
|
69
|
+
level: keyof typeof LogLevel,
|
|
70
|
+
message: string,
|
|
71
|
+
options: LogOptions = {}
|
|
72
|
+
): void {
|
|
73
|
+
if (LogLevel[level] > currentLogLevel) return;
|
|
74
|
+
|
|
75
|
+
const { context, emoji = '', noTimestamp = false } = options;
|
|
76
|
+
const timestamp = noTimestamp ? '' : `${COLORS.gray}[${formatTimestamp()}]${COLORS.reset}`;
|
|
77
|
+
const contextPart = context ? ` ${COLORS.magenta}[${context}]${COLORS.reset}` : '';
|
|
78
|
+
const emojiPart = emoji ? `${emoji} ` : '';
|
|
79
|
+
const levelPart = `${color}${level.toUpperCase().padEnd(5)}${COLORS.reset}`;
|
|
80
|
+
|
|
81
|
+
const lines = String(message).split('\n');
|
|
82
|
+
const prefix = `${timestamp}${contextPart} ${emojiPart}${levelPart}`;
|
|
83
|
+
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
console.log(`${prefix} ${line}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeOptions(options: LogOptions | string | undefined, defaultEmoji: string): LogOptions {
|
|
90
|
+
if (typeof options === 'string') {
|
|
91
|
+
return { emoji: options };
|
|
92
|
+
}
|
|
93
|
+
return { emoji: defaultEmoji, ...options };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Primary logging functions
|
|
97
|
+
export function error(message: string, options?: LogOptions | string): void {
|
|
98
|
+
logWithColor(COLORS.red, 'error', message, normalizeOptions(options, '❌'));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function warn(message: string, options?: LogOptions | string): void {
|
|
102
|
+
logWithColor(COLORS.yellow, 'warn', message, normalizeOptions(options, '⚠️'));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function info(message: string, options?: LogOptions | string): void {
|
|
106
|
+
logWithColor(COLORS.cyan, 'info', message, normalizeOptions(options, 'ℹ️'));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function success(message: string, options?: LogOptions | string): void {
|
|
110
|
+
logWithColor(COLORS.green, 'info', message, normalizeOptions(options, '✅'));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function debug(message: string, options?: LogOptions | string): void {
|
|
114
|
+
logWithColor(COLORS.gray, 'debug', message, normalizeOptions(options, '🔍'));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function progress(message: string, options?: LogOptions | string): void {
|
|
118
|
+
logWithColor(COLORS.blue, 'info', message, normalizeOptions(options, '🔄'));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a context-bound logger
|
|
123
|
+
*/
|
|
124
|
+
export function withContext(context: string) {
|
|
125
|
+
return {
|
|
126
|
+
error: (message: string, options?: Omit<LogOptions, 'context'>) =>
|
|
127
|
+
error(message, { ...options, context }),
|
|
128
|
+
warn: (message: string, options?: Omit<LogOptions, 'context'>) =>
|
|
129
|
+
warn(message, { ...options, context }),
|
|
130
|
+
info: (message: string, options?: Omit<LogOptions, 'context'>) =>
|
|
131
|
+
info(message, { ...options, context }),
|
|
132
|
+
success: (message: string, options?: Omit<LogOptions, 'context'>) =>
|
|
133
|
+
success(message, { ...options, context }),
|
|
134
|
+
debug: (message: string, options?: Omit<LogOptions, 'context'>) =>
|
|
135
|
+
debug(message, { ...options, context }),
|
|
136
|
+
progress: (message: string, options?: Omit<LogOptions, 'context'>) =>
|
|
137
|
+
progress(message, { ...options, context }),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Lane-specific output
|
|
143
|
+
*/
|
|
144
|
+
export function laneOutput(laneName: string, message: string, isError = false): void {
|
|
145
|
+
const timestamp = `${COLORS.gray}[${formatTimestamp()}]${COLORS.reset}`;
|
|
146
|
+
const laneLabel = `${COLORS.magenta}${laneName.padEnd(10)}${COLORS.reset}`;
|
|
147
|
+
const output = isError ? `${COLORS.red}${message}${COLORS.reset}` : message;
|
|
148
|
+
|
|
149
|
+
if (isError) {
|
|
150
|
+
process.stderr.write(`${timestamp} ${laneLabel} ${output}\n`);
|
|
151
|
+
} else {
|
|
152
|
+
process.stdout.write(`${timestamp} ${laneLabel} ${output}\n`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Section header
|
|
158
|
+
*/
|
|
159
|
+
export function section(message: string): void {
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log(`${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
162
|
+
console.log(`${COLORS.cyan} ${message}${COLORS.reset}`);
|
|
163
|
+
console.log(`${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Simple log without formatting
|
|
169
|
+
*/
|
|
170
|
+
export function log(message: string | any): void {
|
|
171
|
+
console.log(message);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Raw output (no formatting)
|
|
176
|
+
*/
|
|
177
|
+
export function raw(message: string): void {
|
|
178
|
+
process.stdout.write(message);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Status indicators
|
|
183
|
+
*/
|
|
184
|
+
export const STATUS = {
|
|
185
|
+
running: `${COLORS.blue}🔄${COLORS.reset}`,
|
|
186
|
+
done: `${COLORS.green}✅${COLORS.reset}`,
|
|
187
|
+
failed: `${COLORS.red}❌${COLORS.reset}`,
|
|
188
|
+
warning: `${COLORS.yellow}⚠️${COLORS.reset}`,
|
|
189
|
+
pending: `${COLORS.gray}⏳${COLORS.reset}`,
|
|
190
|
+
paused: `${COLORS.yellow}⏸️${COLORS.reset}`,
|
|
191
|
+
waiting: `${COLORS.gray}⏳${COLORS.reset}`,
|
|
192
|
+
} as const;
|
|
193
|
+
|