@litmers/cursorflow-orchestrator 0.1.18 → 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 +25 -0
- package/README.md +25 -7
- 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 +178 -6
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +12 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +8 -7
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +126 -77
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1021 -202
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +39 -21
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +268 -163
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +11 -5
- 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 +8 -8
- 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 +16 -2
- package/dist/core/orchestrator.js +439 -105
- 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 +374 -164
- 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 +18 -8
- 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 +17 -11
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +108 -20
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +484 -11
- 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 +25 -0
- package/dist/utils/log-formatter.js +237 -0
- package/dist/utils/log-formatter.js.map +1 -0
- 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/path.d.ts +19 -0
- package/dist/utils/path.js +77 -0
- package/dist/utils/path.js.map +1 -0
- 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 +62 -3
- package/dist/utils/state.js +317 -11
- 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/template.d.ts +14 -0
- package/dist/utils/template.js +122 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/types.d.ts +2 -271
- 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 +187 -6
- package/src/cli/index.ts +12 -1
- package/src/cli/init.ts +8 -7
- package/src/cli/logs.ts +124 -77
- package/src/cli/monitor.ts +1815 -898
- package/src/cli/prepare.ts +41 -21
- package/src/cli/resume.ts +753 -626
- package/src/cli/run.ts +12 -5
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +8 -7
- 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 -704
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +444 -180
- 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 +18 -8
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +18 -11
- package/src/utils/enhanced-logger.ts +122 -60
- package/src/utils/git.ts +517 -11
- 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 +245 -0
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/path.ts +45 -0
- 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 +385 -11
- package/src/utils/task-service.ts +370 -0
- package/src/utils/template.ts +92 -0
- package/src/utils/types.ts +2 -314
- package/templates/basic.json +21 -0
package/src/cli/logs.ts
CHANGED
|
@@ -6,12 +6,15 @@ import * as fs from 'fs';
|
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as logger from '../utils/logger';
|
|
8
8
|
import { loadConfig } from '../utils/config';
|
|
9
|
+
import { safeJoin } from '../utils/path';
|
|
9
10
|
import {
|
|
10
11
|
readJsonLog,
|
|
11
12
|
exportLogs,
|
|
12
13
|
stripAnsi,
|
|
13
14
|
JsonLogEntry
|
|
14
15
|
} from '../utils/enhanced-logger';
|
|
16
|
+
import { formatPotentialJsonMessage } from '../utils/log-formatter';
|
|
17
|
+
import { startLogViewer } from '../ui/log-viewer';
|
|
15
18
|
|
|
16
19
|
interface LogsOptions {
|
|
17
20
|
runDir?: string;
|
|
@@ -21,6 +24,7 @@ interface LogsOptions {
|
|
|
21
24
|
output?: string;
|
|
22
25
|
tail?: number;
|
|
23
26
|
follow: boolean;
|
|
27
|
+
interactive: boolean;
|
|
24
28
|
filter?: string;
|
|
25
29
|
level?: string;
|
|
26
30
|
clean: boolean;
|
|
@@ -44,12 +48,14 @@ View and export lane logs.
|
|
|
44
48
|
|
|
45
49
|
Options:
|
|
46
50
|
[run-dir] Run directory (default: latest)
|
|
51
|
+
--run <id> Specific run directory
|
|
47
52
|
--lane <name> Filter to specific lane
|
|
48
53
|
--all, -a View all lanes merged (sorted by timestamp)
|
|
49
54
|
--format <fmt> Output format: text, json, markdown, html (default: text)
|
|
50
55
|
--output <path> Write output to file instead of stdout
|
|
51
56
|
--tail <n> Show last n lines/entries (default: all)
|
|
52
57
|
--follow, -f Follow log output in real-time
|
|
58
|
+
--interactive, -i Open interactive log viewer
|
|
53
59
|
--filter <pattern> Filter entries by regex pattern
|
|
54
60
|
--level <level> Filter by log level: stdout, stderr, info, error, debug
|
|
55
61
|
--readable, -r Show readable log (parsed AI output) (default)
|
|
@@ -66,23 +72,26 @@ Examples:
|
|
|
66
72
|
cursorflow logs --all --format json # Export all lanes as JSON
|
|
67
73
|
cursorflow logs --all --filter "error" # Filter all lanes for errors
|
|
68
74
|
cursorflow logs --format json --output out.json # Export as JSON
|
|
75
|
+
cursorflow logs -i # Interactive log viewer
|
|
76
|
+
cursorflow logs -i --run <id> # Specific run in interactive mode
|
|
69
77
|
`);
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
function parseArgs(args: string[]): LogsOptions {
|
|
73
81
|
const laneIdx = args.indexOf('--lane');
|
|
82
|
+
const runIdx = args.indexOf('--run');
|
|
74
83
|
const formatIdx = args.indexOf('--format');
|
|
75
84
|
const outputIdx = args.indexOf('--output');
|
|
76
85
|
const tailIdx = args.indexOf('--tail');
|
|
77
86
|
const filterIdx = args.indexOf('--filter');
|
|
78
87
|
const levelIdx = args.indexOf('--level');
|
|
79
88
|
|
|
80
|
-
// Find run directory (first non-option argument)
|
|
81
|
-
|
|
89
|
+
// Find run directory (first non-option argument or --run value)
|
|
90
|
+
let runDir = runIdx >= 0 ? args[runIdx + 1] : args.find((arg, i) => {
|
|
82
91
|
if (arg.startsWith('--') || arg.startsWith('-')) return false;
|
|
83
92
|
// Skip values for options
|
|
84
93
|
const prevArg = args[i - 1];
|
|
85
|
-
if (prevArg && ['--lane', '--format', '--output', '--tail', '--filter', '--level'].includes(prevArg)) {
|
|
94
|
+
if (prevArg && ['--lane', '--run', '--format', '--output', '--tail', '--filter', '--level'].includes(prevArg)) {
|
|
86
95
|
return false;
|
|
87
96
|
}
|
|
88
97
|
return true;
|
|
@@ -100,6 +109,7 @@ function parseArgs(args: string[]): LogsOptions {
|
|
|
100
109
|
output: outputIdx >= 0 ? args[outputIdx + 1] : undefined,
|
|
101
110
|
tail: tailIdx >= 0 ? parseInt(args[tailIdx + 1] || '50') : undefined,
|
|
102
111
|
follow: args.includes('--follow') || args.includes('-f'),
|
|
112
|
+
interactive: args.includes('--interactive') || args.includes('-i'),
|
|
103
113
|
filter: filterIdx >= 0 ? args[filterIdx + 1] : undefined,
|
|
104
114
|
level: levelIdx >= 0 ? args[levelIdx + 1] : undefined,
|
|
105
115
|
raw,
|
|
@@ -114,15 +124,15 @@ function parseArgs(args: string[]): LogsOptions {
|
|
|
114
124
|
* Find the latest run directory
|
|
115
125
|
*/
|
|
116
126
|
function findLatestRunDir(logsDir: string): string | null {
|
|
117
|
-
const runsDir =
|
|
127
|
+
const runsDir = safeJoin(logsDir, 'runs');
|
|
118
128
|
if (!fs.existsSync(runsDir)) return null;
|
|
119
129
|
|
|
120
130
|
const runs = fs.readdirSync(runsDir)
|
|
121
131
|
.filter(d => d.startsWith('run-'))
|
|
122
132
|
.map(d => ({
|
|
123
133
|
name: d,
|
|
124
|
-
path:
|
|
125
|
-
mtime: fs.statSync(
|
|
134
|
+
path: safeJoin(runsDir, d),
|
|
135
|
+
mtime: fs.statSync(safeJoin(runsDir, d)).mtime.getTime()
|
|
126
136
|
}))
|
|
127
137
|
.sort((a, b) => b.mtime - a.mtime);
|
|
128
138
|
|
|
@@ -133,11 +143,11 @@ function findLatestRunDir(logsDir: string): string | null {
|
|
|
133
143
|
* List lanes in a run directory
|
|
134
144
|
*/
|
|
135
145
|
function listLanes(runDir: string): string[] {
|
|
136
|
-
const lanesDir =
|
|
146
|
+
const lanesDir = safeJoin(runDir, 'lanes');
|
|
137
147
|
if (!fs.existsSync(lanesDir)) return [];
|
|
138
148
|
|
|
139
149
|
return fs.readdirSync(lanesDir)
|
|
140
|
-
.filter(d => fs.statSync(
|
|
150
|
+
.filter(d => fs.statSync(safeJoin(lanesDir, d)).isDirectory());
|
|
141
151
|
}
|
|
142
152
|
|
|
143
153
|
/**
|
|
@@ -148,9 +158,9 @@ function displayTextLogs(
|
|
|
148
158
|
options: LogsOptions
|
|
149
159
|
): void {
|
|
150
160
|
let logFile: string;
|
|
151
|
-
const readableLog =
|
|
152
|
-
const rawLog =
|
|
153
|
-
const cleanLog =
|
|
161
|
+
const readableLog = safeJoin(laneDir, 'terminal-readable.log');
|
|
162
|
+
const rawLog = safeJoin(laneDir, 'terminal-raw.log');
|
|
163
|
+
const cleanLog = safeJoin(laneDir, 'terminal.log');
|
|
154
164
|
|
|
155
165
|
if (options.raw) {
|
|
156
166
|
logFile = rawLog;
|
|
@@ -171,10 +181,10 @@ function displayTextLogs(
|
|
|
171
181
|
let content = fs.readFileSync(logFile, 'utf8');
|
|
172
182
|
let lines = content.split('\n');
|
|
173
183
|
|
|
174
|
-
// Apply filter (
|
|
184
|
+
// Apply filter (case-insensitive string match to avoid ReDoS)
|
|
175
185
|
if (options.filter) {
|
|
176
|
-
const
|
|
177
|
-
lines = lines.filter(line =>
|
|
186
|
+
const filterLower = options.filter.toLowerCase();
|
|
187
|
+
lines = lines.filter(line => line.toLowerCase().includes(filterLower));
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
// Apply tail
|
|
@@ -197,7 +207,7 @@ function displayJsonLogs(
|
|
|
197
207
|
laneDir: string,
|
|
198
208
|
options: LogsOptions
|
|
199
209
|
): void {
|
|
200
|
-
const logFile =
|
|
210
|
+
const logFile = safeJoin(laneDir, 'terminal.jsonl');
|
|
201
211
|
|
|
202
212
|
if (!fs.existsSync(logFile)) {
|
|
203
213
|
console.log('No JSON log file found.');
|
|
@@ -211,10 +221,13 @@ function displayJsonLogs(
|
|
|
211
221
|
entries = entries.filter(e => e.level === options.level);
|
|
212
222
|
}
|
|
213
223
|
|
|
214
|
-
// Apply
|
|
224
|
+
// Apply filter (case-insensitive string match to avoid ReDoS)
|
|
215
225
|
if (options.filter) {
|
|
216
|
-
const
|
|
217
|
-
entries = entries.filter(e =>
|
|
226
|
+
const filterLower = options.filter.toLowerCase();
|
|
227
|
+
entries = entries.filter(e =>
|
|
228
|
+
(e.message || '').toLowerCase().includes(filterLower) ||
|
|
229
|
+
(e.task && e.task.toLowerCase().includes(filterLower))
|
|
230
|
+
);
|
|
218
231
|
}
|
|
219
232
|
|
|
220
233
|
// Apply tail
|
|
@@ -227,9 +240,12 @@ function displayJsonLogs(
|
|
|
227
240
|
} else {
|
|
228
241
|
// Display as formatted text
|
|
229
242
|
for (const entry of entries) {
|
|
230
|
-
const
|
|
243
|
+
const level = entry.level || 'info';
|
|
244
|
+
const message = entry.message || '';
|
|
245
|
+
const levelColor = getLevelColor(level);
|
|
231
246
|
const ts = new Date(entry.timestamp).toLocaleTimeString();
|
|
232
|
-
|
|
247
|
+
const formattedMsg = formatPotentialJsonMessage(message);
|
|
248
|
+
console.log(`${levelColor}[${ts}] [${level.toUpperCase().padEnd(6)}]${logger.COLORS.reset} ${formattedMsg}`);
|
|
233
249
|
}
|
|
234
250
|
}
|
|
235
251
|
}
|
|
@@ -290,8 +306,8 @@ function readAllLaneLogs(runDir: string): MergedLogEntry[] {
|
|
|
290
306
|
const allEntries: MergedLogEntry[] = [];
|
|
291
307
|
|
|
292
308
|
lanes.forEach((laneName, index) => {
|
|
293
|
-
const laneDir =
|
|
294
|
-
const jsonLogPath =
|
|
309
|
+
const laneDir = safeJoin(runDir, 'lanes', laneName);
|
|
310
|
+
const jsonLogPath = safeJoin(laneDir, 'terminal.jsonl');
|
|
295
311
|
|
|
296
312
|
if (fs.existsSync(jsonLogPath)) {
|
|
297
313
|
const entries = readJsonLog(jsonLogPath);
|
|
@@ -333,13 +349,13 @@ function displayMergedLogs(runDir: string, options: LogsOptions): void {
|
|
|
333
349
|
entries = entries.filter(e => e.level === options.level);
|
|
334
350
|
}
|
|
335
351
|
|
|
336
|
-
// Apply
|
|
352
|
+
// Apply filter (case-insensitive string match to avoid ReDoS)
|
|
337
353
|
if (options.filter) {
|
|
338
|
-
const
|
|
354
|
+
const filterLower = options.filter.toLowerCase();
|
|
339
355
|
entries = entries.filter(e =>
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
356
|
+
(e.message || '').toLowerCase().includes(filterLower) ||
|
|
357
|
+
(e.task && e.task.toLowerCase().includes(filterLower)) ||
|
|
358
|
+
e.laneName.toLowerCase().includes(filterLower)
|
|
343
359
|
);
|
|
344
360
|
}
|
|
345
361
|
|
|
@@ -372,22 +388,26 @@ function displayMergedLogs(runDir: string, options: LogsOptions): void {
|
|
|
372
388
|
// Display entries
|
|
373
389
|
for (const entry of entries) {
|
|
374
390
|
const ts = new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false });
|
|
375
|
-
const
|
|
391
|
+
const level = entry.level || 'info';
|
|
392
|
+
const levelColor = getLevelColor(level);
|
|
376
393
|
const laneColor = entry.laneColor;
|
|
377
394
|
const lanePad = entry.laneName.substring(0, 12).padEnd(12);
|
|
378
|
-
const levelPad =
|
|
395
|
+
const levelPad = level.toUpperCase().padEnd(6);
|
|
396
|
+
|
|
397
|
+
const message = entry.message || '';
|
|
379
398
|
|
|
380
399
|
// Skip session entries for cleaner output unless they're important
|
|
381
|
-
if (
|
|
400
|
+
if (level === 'session' && message === 'Session started') {
|
|
382
401
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Started ──${logger.COLORS.reset}`);
|
|
383
402
|
continue;
|
|
384
403
|
}
|
|
385
|
-
if (
|
|
404
|
+
if (level === 'session' && message === 'Session ended') {
|
|
386
405
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Ended ──${logger.COLORS.reset}`);
|
|
387
406
|
continue;
|
|
388
407
|
}
|
|
389
408
|
|
|
390
|
-
|
|
409
|
+
const formattedMsg = formatPotentialJsonMessage(message);
|
|
410
|
+
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${laneColor}[${lanePad}]${logger.COLORS.reset} ${levelColor}[${levelPad}]${logger.COLORS.reset} ${formattedMsg}`);
|
|
391
411
|
}
|
|
392
412
|
|
|
393
413
|
console.log(`\n${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}`);
|
|
@@ -426,17 +446,17 @@ function followAllLogs(runDir: string, options: LogsOptions): void {
|
|
|
426
446
|
const newEntries: MergedLogEntry[] = [];
|
|
427
447
|
|
|
428
448
|
for (const lane of lanes) {
|
|
429
|
-
const laneDir =
|
|
430
|
-
const jsonLogPath =
|
|
449
|
+
const laneDir = safeJoin(runDir, 'lanes', lane);
|
|
450
|
+
const jsonLogPath = safeJoin(laneDir, 'terminal.jsonl');
|
|
431
451
|
|
|
452
|
+
let fd: number | null = null;
|
|
432
453
|
try {
|
|
433
|
-
// Use
|
|
434
|
-
|
|
454
|
+
// Use fstat on open fd to avoid TOCTOU race condition
|
|
455
|
+
fd = fs.openSync(jsonLogPath, 'r');
|
|
456
|
+
const stats = fs.fstatSync(fd);
|
|
435
457
|
if (stats.size > lastPositions[lane]!) {
|
|
436
|
-
const fd = fs.openSync(jsonLogPath, 'r');
|
|
437
458
|
const buffer = Buffer.alloc(stats.size - lastPositions[lane]!);
|
|
438
459
|
fs.readSync(fd, buffer, 0, buffer.length, lastPositions[lane]!);
|
|
439
|
-
fs.closeSync(fd);
|
|
440
460
|
|
|
441
461
|
const content = buffer.toString();
|
|
442
462
|
const lines = content.split('\n').filter(l => l.trim());
|
|
@@ -458,6 +478,10 @@ function followAllLogs(runDir: string, options: LogsOptions): void {
|
|
|
458
478
|
}
|
|
459
479
|
} catch {
|
|
460
480
|
// Ignore errors
|
|
481
|
+
} finally {
|
|
482
|
+
if (fd !== null) {
|
|
483
|
+
try { fs.closeSync(fd); } catch { /* ignore */ }
|
|
484
|
+
}
|
|
461
485
|
}
|
|
462
486
|
}
|
|
463
487
|
|
|
@@ -470,33 +494,39 @@ function followAllLogs(runDir: string, options: LogsOptions): void {
|
|
|
470
494
|
|
|
471
495
|
// Apply filters and display
|
|
472
496
|
for (let entry of newEntries) {
|
|
497
|
+
const level = entry.level || 'info';
|
|
498
|
+
const message = entry.message || '';
|
|
499
|
+
|
|
473
500
|
// Apply level filter
|
|
474
|
-
if (options.level &&
|
|
501
|
+
if (options.level && level !== options.level) continue;
|
|
475
502
|
|
|
476
|
-
// Apply
|
|
503
|
+
// Apply filter (case-insensitive string match to avoid ReDoS)
|
|
477
504
|
if (options.filter) {
|
|
478
|
-
const
|
|
479
|
-
if (!
|
|
505
|
+
const filterLower = options.filter.toLowerCase();
|
|
506
|
+
if (!message.toLowerCase().includes(filterLower) &&
|
|
507
|
+
!(entry.task && entry.task.toLowerCase().includes(filterLower)) &&
|
|
508
|
+
!entry.laneName.toLowerCase().includes(filterLower)) {
|
|
480
509
|
continue;
|
|
481
510
|
}
|
|
482
511
|
}
|
|
483
512
|
|
|
484
513
|
const ts = new Date(entry.timestamp).toLocaleTimeString('en-US', { hour12: false });
|
|
485
|
-
const levelColor = getLevelColor(
|
|
514
|
+
const levelColor = getLevelColor(level);
|
|
486
515
|
const lanePad = entry.laneName.substring(0, 12).padEnd(12);
|
|
487
|
-
const levelPad =
|
|
516
|
+
const levelPad = level.toUpperCase().padEnd(6);
|
|
488
517
|
|
|
489
518
|
// Skip verbose session entries
|
|
490
|
-
if (
|
|
491
|
-
if (
|
|
519
|
+
if (level === 'session') {
|
|
520
|
+
if (message === 'Session started') {
|
|
492
521
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${entry.laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Started ──${logger.COLORS.reset}`);
|
|
493
|
-
} else if (
|
|
522
|
+
} else if (message === 'Session ended') {
|
|
494
523
|
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${entry.laneColor}[${lanePad}]${logger.COLORS.reset} ${logger.COLORS.cyan}── Session Ended ──${logger.COLORS.reset}`);
|
|
495
524
|
}
|
|
496
525
|
continue;
|
|
497
526
|
}
|
|
498
527
|
|
|
499
|
-
|
|
528
|
+
const formattedMsg = formatPotentialJsonMessage(message);
|
|
529
|
+
console.log(`${logger.COLORS.gray}[${ts}]${logger.COLORS.reset} ${entry.laneColor}[${lanePad}]${logger.COLORS.reset} ${levelColor}[${levelPad}]${logger.COLORS.reset} ${formattedMsg}`);
|
|
500
530
|
}
|
|
501
531
|
}, 100);
|
|
502
532
|
|
|
@@ -532,7 +562,9 @@ function exportMergedLogs(runDir: string, format: string, outputPath?: string):
|
|
|
532
562
|
// Text format
|
|
533
563
|
for (const entry of entries) {
|
|
534
564
|
const ts = new Date(entry.timestamp).toISOString();
|
|
535
|
-
|
|
565
|
+
const level = entry.level || 'info';
|
|
566
|
+
const message = entry.message || '';
|
|
567
|
+
output += `[${ts}] [${entry.laneName}] [${level.toUpperCase()}] ${message}\n`;
|
|
536
568
|
}
|
|
537
569
|
}
|
|
538
570
|
|
|
@@ -560,8 +592,14 @@ function exportMergedToMarkdown(entries: MergedLogEntry[], runDir: string): stri
|
|
|
560
592
|
|
|
561
593
|
for (const entry of entries) {
|
|
562
594
|
const ts = new Date(entry.timestamp).toLocaleTimeString();
|
|
563
|
-
const
|
|
564
|
-
|
|
595
|
+
const level = entry.level || 'info';
|
|
596
|
+
// Escape markdown table special characters: pipe, backslash, and newlines
|
|
597
|
+
const message = (entry.message || '')
|
|
598
|
+
.replace(/\\/g, '\\\\')
|
|
599
|
+
.replace(/\|/g, '\\|')
|
|
600
|
+
.replace(/\n/g, ' ')
|
|
601
|
+
.substring(0, 80);
|
|
602
|
+
md += `| ${ts} | ${entry.laneName} | ${level} | ${message} |\n`;
|
|
565
603
|
}
|
|
566
604
|
|
|
567
605
|
return md;
|
|
@@ -615,12 +653,14 @@ function exportMergedToHtml(entries: MergedLogEntry[], runDir: string): string {
|
|
|
615
653
|
const ts = new Date(entry.timestamp).toLocaleTimeString();
|
|
616
654
|
const laneIndex = lanes.indexOf(entry.laneName);
|
|
617
655
|
const color = colors[laneIndex % colors.length];
|
|
656
|
+
const level = entry.level || 'info';
|
|
657
|
+
const message = entry.message || '';
|
|
618
658
|
|
|
619
|
-
html += ` <div class="entry ${
|
|
659
|
+
html += ` <div class="entry ${level}">
|
|
620
660
|
<span class="time">${ts}</span>
|
|
621
661
|
<span class="lane" style="color: ${color}">${entry.laneName}</span>
|
|
622
|
-
<span class="level">[${
|
|
623
|
-
<span class="message">${escapeHtml(
|
|
662
|
+
<span class="level">[${level.toUpperCase()}]</span>
|
|
663
|
+
<span class="message">${escapeHtml(message)}</span>
|
|
624
664
|
</div>\n`;
|
|
625
665
|
}
|
|
626
666
|
|
|
@@ -644,9 +684,9 @@ function escapeHtml(text: string): string {
|
|
|
644
684
|
*/
|
|
645
685
|
function followLogs(laneDir: string, options: LogsOptions): void {
|
|
646
686
|
let logFile: string;
|
|
647
|
-
const readableLog =
|
|
648
|
-
const rawLog =
|
|
649
|
-
const cleanLog =
|
|
687
|
+
const readableLog = safeJoin(laneDir, 'terminal-readable.log');
|
|
688
|
+
const rawLog = safeJoin(laneDir, 'terminal-raw.log');
|
|
689
|
+
const cleanLog = safeJoin(laneDir, 'terminal.log');
|
|
650
690
|
|
|
651
691
|
if (options.raw) {
|
|
652
692
|
logFile = rawLog;
|
|
@@ -675,23 +715,22 @@ function followLogs(laneDir: string, options: LogsOptions): void {
|
|
|
675
715
|
console.log(`${logger.COLORS.cyan}Following ${logFile}... (Ctrl+C to stop)${logger.COLORS.reset}\n`);
|
|
676
716
|
|
|
677
717
|
const checkInterval = setInterval(() => {
|
|
718
|
+
// Use fstat on open fd to avoid TOCTOU race condition
|
|
719
|
+
let fd: number | null = null;
|
|
678
720
|
try {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
const stats = fs.statSync(logFile);
|
|
721
|
+
fd = fs.openSync(logFile, 'r');
|
|
722
|
+
const stats = fs.fstatSync(fd);
|
|
682
723
|
if (stats.size > lastSize) {
|
|
683
|
-
const fd = fs.openSync(logFile, 'r');
|
|
684
724
|
const buffer = Buffer.alloc(stats.size - lastSize);
|
|
685
725
|
fs.readSync(fd, buffer, 0, buffer.length, lastSize);
|
|
686
|
-
fs.closeSync(fd);
|
|
687
726
|
|
|
688
727
|
let content = buffer.toString();
|
|
689
728
|
|
|
690
|
-
// Apply filter (
|
|
729
|
+
// Apply filter (case-insensitive string match to avoid ReDoS)
|
|
691
730
|
if (options.filter) {
|
|
692
|
-
const
|
|
731
|
+
const filterLower = options.filter.toLowerCase();
|
|
693
732
|
const lines = content.split('\n');
|
|
694
|
-
content = lines.filter(line =>
|
|
733
|
+
content = lines.filter(line => line.toLowerCase().includes(filterLower)).join('\n');
|
|
695
734
|
}
|
|
696
735
|
|
|
697
736
|
// Clean ANSI if needed (unless raw mode)
|
|
@@ -705,8 +744,12 @@ function followLogs(laneDir: string, options: LogsOptions): void {
|
|
|
705
744
|
|
|
706
745
|
lastSize = stats.size;
|
|
707
746
|
}
|
|
708
|
-
} catch
|
|
747
|
+
} catch {
|
|
709
748
|
// Ignore errors (file might be rotating)
|
|
749
|
+
} finally {
|
|
750
|
+
if (fd !== null) {
|
|
751
|
+
try { fs.closeSync(fd); } catch { /* ignore */ }
|
|
752
|
+
}
|
|
710
753
|
}
|
|
711
754
|
}, 100);
|
|
712
755
|
|
|
@@ -734,11 +777,11 @@ function displaySummary(runDir: string): void {
|
|
|
734
777
|
}
|
|
735
778
|
|
|
736
779
|
for (const lane of lanes) {
|
|
737
|
-
const laneDir =
|
|
738
|
-
const cleanLog =
|
|
739
|
-
const rawLog =
|
|
740
|
-
const jsonLog =
|
|
741
|
-
const readableLog =
|
|
780
|
+
const laneDir = safeJoin(runDir, 'lanes', lane);
|
|
781
|
+
const cleanLog = safeJoin(laneDir, 'terminal.log');
|
|
782
|
+
const rawLog = safeJoin(laneDir, 'terminal-raw.log');
|
|
783
|
+
const jsonLog = safeJoin(laneDir, 'terminal.jsonl');
|
|
784
|
+
const readableLog = safeJoin(laneDir, 'terminal-readable.log');
|
|
742
785
|
|
|
743
786
|
console.log(` ${logger.COLORS.green}📁 ${lane}${logger.COLORS.reset}`);
|
|
744
787
|
|
|
@@ -794,13 +837,17 @@ async function logs(args: string[]): Promise<void> {
|
|
|
794
837
|
let runDir = options.runDir;
|
|
795
838
|
if (!runDir || runDir === 'latest') {
|
|
796
839
|
runDir = findLatestRunDir(config.logsDir) || undefined;
|
|
797
|
-
if (!runDir) {
|
|
798
|
-
throw new Error('No run directories found');
|
|
799
|
-
}
|
|
800
840
|
}
|
|
801
841
|
|
|
802
|
-
if (!fs.existsSync(runDir)) {
|
|
803
|
-
|
|
842
|
+
if (!runDir || !fs.existsSync(runDir)) {
|
|
843
|
+
console.error('No run found');
|
|
844
|
+
process.exit(1);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Handle interactive mode
|
|
848
|
+
if (options.interactive) {
|
|
849
|
+
await startLogViewer(runDir);
|
|
850
|
+
return;
|
|
804
851
|
}
|
|
805
852
|
|
|
806
853
|
// Handle --all option (view all lanes merged)
|
|
@@ -839,7 +886,7 @@ async function logs(args: string[]): Promise<void> {
|
|
|
839
886
|
}
|
|
840
887
|
|
|
841
888
|
// Find lane directory
|
|
842
|
-
const laneDir =
|
|
889
|
+
const laneDir = safeJoin(runDir, 'lanes', options.lane);
|
|
843
890
|
if (!fs.existsSync(laneDir)) {
|
|
844
891
|
const lanes = listLanes(runDir);
|
|
845
892
|
throw new Error(`Lane not found: ${options.lane}\nAvailable lanes: ${lanes.join(', ')}`);
|