@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
package/dist/cli/monitor.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* CursorFlow interactive monitor command
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Lane dashboard with accurate process status
|
|
7
|
+
* - Unified log view for all lanes
|
|
8
|
+
* - Readable log format support
|
|
9
|
+
* - Multiple flows dashboard
|
|
10
|
+
* - Consistent layout across all views
|
|
4
11
|
*/
|
|
5
12
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
13
|
if (k2 === undefined) k2 = k;
|
|
@@ -41,16 +48,49 @@ const readline = __importStar(require("readline"));
|
|
|
41
48
|
const state_1 = require("../utils/state");
|
|
42
49
|
const config_1 = require("../utils/config");
|
|
43
50
|
const path_1 = require("../utils/path");
|
|
51
|
+
const process_1 = require("../services/process");
|
|
52
|
+
const buffer_1 = require("../services/logging/buffer");
|
|
53
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
54
|
+
// UI Constants
|
|
55
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
56
|
+
const UI = {
|
|
57
|
+
COLORS: {
|
|
58
|
+
reset: '\x1b[0m',
|
|
59
|
+
bold: '\x1b[1m',
|
|
60
|
+
dim: '\x1b[2m',
|
|
61
|
+
cyan: '\x1b[36m',
|
|
62
|
+
green: '\x1b[32m',
|
|
63
|
+
yellow: '\x1b[33m',
|
|
64
|
+
red: '\x1b[31m',
|
|
65
|
+
magenta: '\x1b[35m',
|
|
66
|
+
gray: '\x1b[90m',
|
|
67
|
+
white: '\x1b[37m',
|
|
68
|
+
bgGray: '\x1b[48;5;236m',
|
|
69
|
+
bgCyan: '\x1b[46m',
|
|
70
|
+
},
|
|
71
|
+
CHARS: {
|
|
72
|
+
hLine: '━',
|
|
73
|
+
vLine: '│',
|
|
74
|
+
corner: {
|
|
75
|
+
tl: '┌', tr: '┐', bl: '└', br: '┘'
|
|
76
|
+
},
|
|
77
|
+
arrow: {
|
|
78
|
+
right: '▶', left: '◀', up: '▲', down: '▼'
|
|
79
|
+
},
|
|
80
|
+
bullet: '•',
|
|
81
|
+
check: '✓',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
44
84
|
function printHelp() {
|
|
45
|
-
console.log(`
|
|
46
|
-
Usage: cursorflow monitor [run-dir] [options]
|
|
47
|
-
|
|
48
|
-
Interactive lane dashboard to track progress and dependencies.
|
|
49
|
-
|
|
50
|
-
Options:
|
|
51
|
-
[run-dir] Run directory to monitor (default: latest)
|
|
52
|
-
--interval <seconds> Refresh interval (default: 2)
|
|
53
|
-
--help, -h Show help
|
|
85
|
+
console.log(`
|
|
86
|
+
Usage: cursorflow monitor [run-dir] [options]
|
|
87
|
+
|
|
88
|
+
Interactive lane dashboard to track progress and dependencies.
|
|
89
|
+
|
|
90
|
+
Options:
|
|
91
|
+
[run-dir] Run directory to monitor (default: latest)
|
|
92
|
+
--interval <seconds> Refresh interval (default: 2)
|
|
93
|
+
--help, -h Show help
|
|
54
94
|
`);
|
|
55
95
|
}
|
|
56
96
|
var View;
|
|
@@ -62,6 +102,8 @@ var View;
|
|
|
62
102
|
View[View["TERMINAL"] = 4] = "TERMINAL";
|
|
63
103
|
View[View["INTERVENE"] = 5] = "INTERVENE";
|
|
64
104
|
View[View["TIMEOUT"] = 6] = "TIMEOUT";
|
|
105
|
+
View[View["UNIFIED_LOG"] = 7] = "UNIFIED_LOG";
|
|
106
|
+
View[View["FLOWS_DASHBOARD"] = 8] = "FLOWS_DASHBOARD";
|
|
65
107
|
})(View || (View = {}));
|
|
66
108
|
class InteractiveMonitor {
|
|
67
109
|
runDir;
|
|
@@ -75,19 +117,92 @@ class InteractiveMonitor {
|
|
|
75
117
|
timer = null;
|
|
76
118
|
scrollOffset = 0;
|
|
77
119
|
terminalScrollOffset = 0;
|
|
120
|
+
followMode = true;
|
|
121
|
+
unseenLineCount = 0;
|
|
78
122
|
lastTerminalTotalLines = 0;
|
|
79
123
|
interventionInput = '';
|
|
80
124
|
timeoutInput = '';
|
|
81
125
|
notification = null;
|
|
82
|
-
|
|
126
|
+
// Process status tracking
|
|
127
|
+
laneProcessStatuses = new Map();
|
|
128
|
+
// Unified log buffer for all lanes
|
|
129
|
+
unifiedLogBuffer = null;
|
|
130
|
+
unifiedLogScrollOffset = 0;
|
|
131
|
+
unifiedLogFollowMode = true;
|
|
132
|
+
// Multiple flows support
|
|
133
|
+
allFlows = [];
|
|
134
|
+
selectedFlowIndex = 0;
|
|
135
|
+
logsDir = '';
|
|
136
|
+
// NEW: UX improvements
|
|
137
|
+
readableFormat = true; // Toggle readable log format
|
|
138
|
+
laneFilter = null; // Filter by lane name
|
|
139
|
+
confirmAction = null;
|
|
140
|
+
// Screen dimensions
|
|
141
|
+
get screenWidth() {
|
|
142
|
+
return process.stdout.columns || 120;
|
|
143
|
+
}
|
|
144
|
+
get screenHeight() {
|
|
145
|
+
return process.stdout.rows || 24;
|
|
146
|
+
}
|
|
147
|
+
constructor(runDir, interval, logsDir) {
|
|
83
148
|
this.runDir = runDir;
|
|
84
149
|
this.interval = interval;
|
|
150
|
+
// Set logs directory for multiple flows discovery
|
|
151
|
+
if (logsDir) {
|
|
152
|
+
this.logsDir = logsDir;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
const config = (0, config_1.loadConfig)();
|
|
156
|
+
this.logsDir = (0, path_1.safeJoin)(config.logsDir, 'runs');
|
|
157
|
+
}
|
|
158
|
+
// Initialize unified log buffer
|
|
159
|
+
this.unifiedLogBuffer = new buffer_1.LogBufferService(runDir);
|
|
85
160
|
}
|
|
86
161
|
async start() {
|
|
87
162
|
this.setupTerminal();
|
|
163
|
+
// Start unified log streaming
|
|
164
|
+
if (this.unifiedLogBuffer) {
|
|
165
|
+
this.unifiedLogBuffer.startStreaming();
|
|
166
|
+
this.unifiedLogBuffer.on('update', () => {
|
|
167
|
+
if (this.view === View.UNIFIED_LOG && this.unifiedLogFollowMode) {
|
|
168
|
+
this.render();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// Discover all flows
|
|
173
|
+
this.discoverFlows();
|
|
88
174
|
this.refresh();
|
|
89
175
|
this.timer = setInterval(() => this.refresh(), this.interval * 1000);
|
|
90
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Discover all run directories (flows) for multi-flow view
|
|
179
|
+
*/
|
|
180
|
+
discoverFlows() {
|
|
181
|
+
try {
|
|
182
|
+
if (!fs.existsSync(this.logsDir))
|
|
183
|
+
return;
|
|
184
|
+
const runs = fs.readdirSync(this.logsDir)
|
|
185
|
+
.filter(d => d.startsWith('run-'))
|
|
186
|
+
.map(d => {
|
|
187
|
+
const runDir = (0, path_1.safeJoin)(this.logsDir, d);
|
|
188
|
+
const summary = (0, process_1.getFlowSummary)(runDir);
|
|
189
|
+
return {
|
|
190
|
+
runDir,
|
|
191
|
+
runId: d,
|
|
192
|
+
isAlive: summary.isAlive,
|
|
193
|
+
summary,
|
|
194
|
+
};
|
|
195
|
+
})
|
|
196
|
+
.sort((a, b) => {
|
|
197
|
+
// Sort by run ID (timestamp-based) descending
|
|
198
|
+
return b.runId.localeCompare(a.runId);
|
|
199
|
+
});
|
|
200
|
+
this.allFlows = runs;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Ignore errors
|
|
204
|
+
}
|
|
205
|
+
}
|
|
91
206
|
setupTerminal() {
|
|
92
207
|
if (process.stdin.isTTY) {
|
|
93
208
|
process.stdin.setRawMode(true);
|
|
@@ -122,6 +237,12 @@ class InteractiveMonitor {
|
|
|
122
237
|
else if (this.view === View.MESSAGE_DETAIL) {
|
|
123
238
|
this.handleMessageDetailKey(keyName);
|
|
124
239
|
}
|
|
240
|
+
else if (this.view === View.UNIFIED_LOG) {
|
|
241
|
+
this.handleUnifiedLogKey(keyName);
|
|
242
|
+
}
|
|
243
|
+
else if (this.view === View.FLOWS_DASHBOARD) {
|
|
244
|
+
this.handleFlowsDashboardKey(keyName);
|
|
245
|
+
}
|
|
125
246
|
});
|
|
126
247
|
// Hide cursor
|
|
127
248
|
process.stdout.write('\x1B[?25l');
|
|
@@ -129,6 +250,10 @@ class InteractiveMonitor {
|
|
|
129
250
|
stop() {
|
|
130
251
|
if (this.timer)
|
|
131
252
|
clearInterval(this.timer);
|
|
253
|
+
// Stop unified log streaming
|
|
254
|
+
if (this.unifiedLogBuffer) {
|
|
255
|
+
this.unifiedLogBuffer.stopStreaming();
|
|
256
|
+
}
|
|
132
257
|
// Show cursor and clear screen
|
|
133
258
|
process.stdout.write('\x1B[?25h');
|
|
134
259
|
process.stdout.write('\x1Bc');
|
|
@@ -162,6 +287,19 @@ class InteractiveMonitor {
|
|
|
162
287
|
this.view = View.FLOW;
|
|
163
288
|
this.render();
|
|
164
289
|
break;
|
|
290
|
+
case 'u':
|
|
291
|
+
// Unified log view
|
|
292
|
+
this.view = View.UNIFIED_LOG;
|
|
293
|
+
this.unifiedLogScrollOffset = 0;
|
|
294
|
+
this.unifiedLogFollowMode = true;
|
|
295
|
+
this.render();
|
|
296
|
+
break;
|
|
297
|
+
case 'm':
|
|
298
|
+
// Multiple flows dashboard
|
|
299
|
+
this.discoverFlows();
|
|
300
|
+
this.view = View.FLOWS_DASHBOARD;
|
|
301
|
+
this.render();
|
|
302
|
+
break;
|
|
165
303
|
case 'q':
|
|
166
304
|
this.stop();
|
|
167
305
|
break;
|
|
@@ -249,11 +387,29 @@ class InteractiveMonitor {
|
|
|
249
387
|
handleTerminalKey(key) {
|
|
250
388
|
switch (key) {
|
|
251
389
|
case 'up':
|
|
390
|
+
this.followMode = false;
|
|
252
391
|
this.terminalScrollOffset++;
|
|
253
392
|
this.render();
|
|
254
393
|
break;
|
|
255
394
|
case 'down':
|
|
256
395
|
this.terminalScrollOffset = Math.max(0, this.terminalScrollOffset - 1);
|
|
396
|
+
if (this.terminalScrollOffset === 0) {
|
|
397
|
+
this.followMode = true;
|
|
398
|
+
this.unseenLineCount = 0;
|
|
399
|
+
}
|
|
400
|
+
this.render();
|
|
401
|
+
break;
|
|
402
|
+
case 'f':
|
|
403
|
+
this.followMode = true;
|
|
404
|
+
this.terminalScrollOffset = 0;
|
|
405
|
+
this.unseenLineCount = 0;
|
|
406
|
+
this.render();
|
|
407
|
+
break;
|
|
408
|
+
case 'r':
|
|
409
|
+
// Toggle readable log format
|
|
410
|
+
this.readableFormat = !this.readableFormat;
|
|
411
|
+
this.terminalScrollOffset = 0;
|
|
412
|
+
this.lastTerminalTotalLines = 0;
|
|
257
413
|
this.render();
|
|
258
414
|
break;
|
|
259
415
|
case 't':
|
|
@@ -339,6 +495,197 @@ class InteractiveMonitor {
|
|
|
339
495
|
break;
|
|
340
496
|
}
|
|
341
497
|
}
|
|
498
|
+
handleUnifiedLogKey(key) {
|
|
499
|
+
const pageSize = Math.max(10, this.screenHeight - 12);
|
|
500
|
+
switch (key) {
|
|
501
|
+
case 'up':
|
|
502
|
+
this.unifiedLogFollowMode = false;
|
|
503
|
+
this.unifiedLogScrollOffset++;
|
|
504
|
+
this.render();
|
|
505
|
+
break;
|
|
506
|
+
case 'down':
|
|
507
|
+
this.unifiedLogScrollOffset = Math.max(0, this.unifiedLogScrollOffset - 1);
|
|
508
|
+
if (this.unifiedLogScrollOffset === 0) {
|
|
509
|
+
this.unifiedLogFollowMode = true;
|
|
510
|
+
}
|
|
511
|
+
this.render();
|
|
512
|
+
break;
|
|
513
|
+
case 'pageup':
|
|
514
|
+
this.unifiedLogFollowMode = false;
|
|
515
|
+
this.unifiedLogScrollOffset += pageSize;
|
|
516
|
+
this.render();
|
|
517
|
+
break;
|
|
518
|
+
case 'pagedown':
|
|
519
|
+
this.unifiedLogScrollOffset = Math.max(0, this.unifiedLogScrollOffset - pageSize);
|
|
520
|
+
if (this.unifiedLogScrollOffset === 0) {
|
|
521
|
+
this.unifiedLogFollowMode = true;
|
|
522
|
+
}
|
|
523
|
+
this.render();
|
|
524
|
+
break;
|
|
525
|
+
case 'f':
|
|
526
|
+
this.unifiedLogFollowMode = true;
|
|
527
|
+
this.unifiedLogScrollOffset = 0;
|
|
528
|
+
this.render();
|
|
529
|
+
break;
|
|
530
|
+
case 'r':
|
|
531
|
+
// Toggle readable format
|
|
532
|
+
this.readableFormat = !this.readableFormat;
|
|
533
|
+
this.render();
|
|
534
|
+
break;
|
|
535
|
+
case 'l':
|
|
536
|
+
// Cycle through lane filter
|
|
537
|
+
this.cycleLaneFilter();
|
|
538
|
+
this.unifiedLogScrollOffset = 0;
|
|
539
|
+
this.render();
|
|
540
|
+
break;
|
|
541
|
+
case 'escape':
|
|
542
|
+
case 'backspace':
|
|
543
|
+
case 'u':
|
|
544
|
+
this.view = View.LIST;
|
|
545
|
+
this.render();
|
|
546
|
+
break;
|
|
547
|
+
case 'q':
|
|
548
|
+
this.stop();
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Cycle through available lanes for filtering
|
|
554
|
+
*/
|
|
555
|
+
cycleLaneFilter() {
|
|
556
|
+
const lanes = this.unifiedLogBuffer?.getLanes() || [];
|
|
557
|
+
if (lanes.length === 0) {
|
|
558
|
+
this.laneFilter = null;
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (this.laneFilter === null) {
|
|
562
|
+
// Show first lane
|
|
563
|
+
this.laneFilter = lanes[0];
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
const currentIndex = lanes.indexOf(this.laneFilter);
|
|
567
|
+
if (currentIndex === -1 || currentIndex === lanes.length - 1) {
|
|
568
|
+
// Reset to all lanes
|
|
569
|
+
this.laneFilter = null;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
// Next lane
|
|
573
|
+
this.laneFilter = lanes[currentIndex + 1];
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
handleFlowsDashboardKey(key) {
|
|
578
|
+
// Handle confirmation dialog first
|
|
579
|
+
if (this.confirmAction) {
|
|
580
|
+
if (key === 'y') {
|
|
581
|
+
this.executeConfirmedAction();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
else if (key === 'n' || key === 'escape') {
|
|
585
|
+
this.confirmAction = null;
|
|
586
|
+
this.render();
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
// Other keys cancel confirmation
|
|
590
|
+
this.confirmAction = null;
|
|
591
|
+
this.render();
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
switch (key) {
|
|
595
|
+
case 'up':
|
|
596
|
+
this.selectedFlowIndex = Math.max(0, this.selectedFlowIndex - 1);
|
|
597
|
+
this.render();
|
|
598
|
+
break;
|
|
599
|
+
case 'down':
|
|
600
|
+
this.selectedFlowIndex = Math.min(this.allFlows.length - 1, this.selectedFlowIndex + 1);
|
|
601
|
+
this.render();
|
|
602
|
+
break;
|
|
603
|
+
case 'right':
|
|
604
|
+
case 'return':
|
|
605
|
+
case 'enter':
|
|
606
|
+
// Switch to selected flow
|
|
607
|
+
if (this.allFlows[this.selectedFlowIndex]) {
|
|
608
|
+
const flow = this.allFlows[this.selectedFlowIndex];
|
|
609
|
+
this.runDir = flow.runDir;
|
|
610
|
+
// Restart log buffer for new run
|
|
611
|
+
if (this.unifiedLogBuffer) {
|
|
612
|
+
this.unifiedLogBuffer.stopStreaming();
|
|
613
|
+
}
|
|
614
|
+
this.unifiedLogBuffer = new buffer_1.LogBufferService(this.runDir);
|
|
615
|
+
this.unifiedLogBuffer.startStreaming();
|
|
616
|
+
this.lanes = [];
|
|
617
|
+
this.laneProcessStatuses.clear();
|
|
618
|
+
this.view = View.LIST;
|
|
619
|
+
this.showNotification(`Switched to flow: ${flow.runId}`, 'info');
|
|
620
|
+
this.refresh();
|
|
621
|
+
}
|
|
622
|
+
break;
|
|
623
|
+
case 'd':
|
|
624
|
+
// Delete flow (with confirmation)
|
|
625
|
+
if (this.allFlows[this.selectedFlowIndex]) {
|
|
626
|
+
const flow = this.allFlows[this.selectedFlowIndex];
|
|
627
|
+
if (flow.isAlive) {
|
|
628
|
+
this.showNotification('Cannot delete a running flow. Stop it first.', 'error');
|
|
629
|
+
}
|
|
630
|
+
else if (flow.runDir === this.runDir) {
|
|
631
|
+
this.showNotification('Cannot delete the currently viewed flow.', 'error');
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
this.confirmAction = {
|
|
635
|
+
type: 'delete-flow',
|
|
636
|
+
target: flow.runId,
|
|
637
|
+
time: Date.now(),
|
|
638
|
+
};
|
|
639
|
+
this.render();
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
break;
|
|
643
|
+
case 'r':
|
|
644
|
+
// Refresh flows
|
|
645
|
+
this.discoverFlows();
|
|
646
|
+
this.showNotification('Flows refreshed', 'info');
|
|
647
|
+
this.render();
|
|
648
|
+
break;
|
|
649
|
+
case 'escape':
|
|
650
|
+
case 'backspace':
|
|
651
|
+
case 'm':
|
|
652
|
+
this.view = View.LIST;
|
|
653
|
+
this.render();
|
|
654
|
+
break;
|
|
655
|
+
case 'q':
|
|
656
|
+
this.stop();
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Execute a confirmed action (delete flow, kill process, etc.)
|
|
662
|
+
*/
|
|
663
|
+
executeConfirmedAction() {
|
|
664
|
+
if (!this.confirmAction)
|
|
665
|
+
return;
|
|
666
|
+
const { type, target } = this.confirmAction;
|
|
667
|
+
this.confirmAction = null;
|
|
668
|
+
if (type === 'delete-flow') {
|
|
669
|
+
const flow = this.allFlows.find(f => f.runId === target);
|
|
670
|
+
if (flow) {
|
|
671
|
+
try {
|
|
672
|
+
// Delete the flow directory
|
|
673
|
+
fs.rmSync(flow.runDir, { recursive: true, force: true });
|
|
674
|
+
this.showNotification(`Deleted flow: ${target}`, 'success');
|
|
675
|
+
// Refresh the list
|
|
676
|
+
this.discoverFlows();
|
|
677
|
+
// Adjust selection if needed
|
|
678
|
+
if (this.selectedFlowIndex >= this.allFlows.length) {
|
|
679
|
+
this.selectedFlowIndex = Math.max(0, this.allFlows.length - 1);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
catch (err) {
|
|
683
|
+
this.showNotification(`Failed to delete flow: ${err}`, 'error');
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
this.render();
|
|
688
|
+
}
|
|
342
689
|
sendIntervention(message) {
|
|
343
690
|
if (!this.selectedLaneName)
|
|
344
691
|
return;
|
|
@@ -352,7 +699,7 @@ class InteractiveMonitor {
|
|
|
352
699
|
const convoPath = (0, path_1.safeJoin)(lane.path, 'conversation.jsonl');
|
|
353
700
|
const entry = {
|
|
354
701
|
timestamp: new Date().toISOString(),
|
|
355
|
-
role: '
|
|
702
|
+
role: 'intervention',
|
|
356
703
|
task: 'INTERVENTION',
|
|
357
704
|
fullText: `[HUMAN INTERVENTION]: ${message}`,
|
|
358
705
|
textLength: message.length + 20,
|
|
@@ -400,11 +747,29 @@ class InteractiveMonitor {
|
|
|
400
747
|
}
|
|
401
748
|
refresh() {
|
|
402
749
|
this.lanes = this.listLanesWithDeps(this.runDir);
|
|
403
|
-
|
|
750
|
+
// Update process statuses for accurate display
|
|
751
|
+
this.updateProcessStatuses();
|
|
752
|
+
if (this.view !== View.LIST && this.view !== View.UNIFIED_LOG && this.view !== View.FLOWS_DASHBOARD) {
|
|
404
753
|
this.refreshLogs();
|
|
405
754
|
}
|
|
755
|
+
// Refresh flows list periodically
|
|
756
|
+
if (this.view === View.FLOWS_DASHBOARD) {
|
|
757
|
+
this.discoverFlows();
|
|
758
|
+
}
|
|
406
759
|
this.render();
|
|
407
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* Update process statuses for all lanes
|
|
763
|
+
*/
|
|
764
|
+
updateProcessStatuses() {
|
|
765
|
+
const lanesDir = (0, path_1.safeJoin)(this.runDir, 'lanes');
|
|
766
|
+
if (!fs.existsSync(lanesDir))
|
|
767
|
+
return;
|
|
768
|
+
for (const lane of this.lanes) {
|
|
769
|
+
const status = (0, process_1.getLaneProcessStatus)(lane.path, lane.name);
|
|
770
|
+
this.laneProcessStatuses.set(lane.name, status);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
408
773
|
killLane() {
|
|
409
774
|
if (!this.selectedLaneName)
|
|
410
775
|
return;
|
|
@@ -429,6 +794,55 @@ class InteractiveMonitor {
|
|
|
429
794
|
this.notification = { message, type, time: Date.now() };
|
|
430
795
|
this.render();
|
|
431
796
|
}
|
|
797
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
798
|
+
// UI Layout Helpers - Consistent header/footer across all views
|
|
799
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
800
|
+
renderHeader(title, breadcrumb = []) {
|
|
801
|
+
const width = Math.min(this.screenWidth, 120);
|
|
802
|
+
const line = UI.CHARS.hLine.repeat(width);
|
|
803
|
+
// Flow status
|
|
804
|
+
const flowSummary = (0, process_1.getFlowSummary)(this.runDir);
|
|
805
|
+
const flowStatusIcon = flowSummary.isAlive ? '🟢' : (flowSummary.completed === flowSummary.total && flowSummary.total > 0 ? '✅' : '🔴');
|
|
806
|
+
// Breadcrumb
|
|
807
|
+
const crumbs = ['CursorFlow', ...breadcrumb].join(` ${UI.COLORS.gray}›${UI.COLORS.reset} `);
|
|
808
|
+
// Time
|
|
809
|
+
const timeStr = new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
810
|
+
process.stdout.write(`${UI.COLORS.cyan}${line}${UI.COLORS.reset}\n`);
|
|
811
|
+
process.stdout.write(`${UI.COLORS.bold}${crumbs}${UI.COLORS.reset} ${flowStatusIcon} `);
|
|
812
|
+
process.stdout.write(`${UI.COLORS.dim}${timeStr}${UI.COLORS.reset}\n`);
|
|
813
|
+
process.stdout.write(`${UI.COLORS.cyan}${line}${UI.COLORS.reset}\n`);
|
|
814
|
+
}
|
|
815
|
+
renderFooter(actions) {
|
|
816
|
+
const width = Math.min(this.screenWidth, 120);
|
|
817
|
+
const line = UI.CHARS.hLine.repeat(width);
|
|
818
|
+
// Notification area
|
|
819
|
+
if (this.notification && Date.now() - this.notification.time < 3000) {
|
|
820
|
+
const nColor = this.notification.type === 'error' ? UI.COLORS.red
|
|
821
|
+
: this.notification.type === 'success' ? UI.COLORS.green
|
|
822
|
+
: UI.COLORS.cyan;
|
|
823
|
+
process.stdout.write(`\n${nColor}🔔 ${this.notification.message}${UI.COLORS.reset}\n`);
|
|
824
|
+
}
|
|
825
|
+
// Confirmation dialog area
|
|
826
|
+
if (this.confirmAction && Date.now() - this.confirmAction.time < 10000) {
|
|
827
|
+
const actionName = this.confirmAction.type === 'delete-flow' ? 'DELETE FLOW' : 'KILL PROCESS';
|
|
828
|
+
process.stdout.write(`\n${UI.COLORS.yellow}⚠️ Confirm ${actionName}: ${this.confirmAction.target}? [Y] Yes / [N] No${UI.COLORS.reset}\n`);
|
|
829
|
+
}
|
|
830
|
+
process.stdout.write(`\n${UI.COLORS.cyan}${line}${UI.COLORS.reset}\n`);
|
|
831
|
+
const formattedActions = actions.map(a => {
|
|
832
|
+
const parts = a.split('] ');
|
|
833
|
+
if (parts.length === 2) {
|
|
834
|
+
// Use regex with global flag to replace all occurrences
|
|
835
|
+
return `${UI.COLORS.yellow}[${parts[0].replace(/\[/g, '')}]${UI.COLORS.reset} ${parts[1]}`;
|
|
836
|
+
}
|
|
837
|
+
return a;
|
|
838
|
+
});
|
|
839
|
+
process.stdout.write(` ${formattedActions.join(' ')}\n`);
|
|
840
|
+
}
|
|
841
|
+
renderSectionTitle(title, extra) {
|
|
842
|
+
const extraStr = extra ? ` ${UI.COLORS.dim}${extra}${UI.COLORS.reset}` : '';
|
|
843
|
+
process.stdout.write(`\n${UI.COLORS.bold}${title}${UI.COLORS.reset}${extraStr}\n`);
|
|
844
|
+
process.stdout.write(`${UI.COLORS.gray}${'─'.repeat(40)}${UI.COLORS.reset}\n`);
|
|
845
|
+
}
|
|
432
846
|
render() {
|
|
433
847
|
// Clear screen
|
|
434
848
|
process.stdout.write('\x1Bc');
|
|
@@ -436,9 +850,9 @@ class InteractiveMonitor {
|
|
|
436
850
|
if (this.notification && Date.now() - this.notification.time > 3000) {
|
|
437
851
|
this.notification = null;
|
|
438
852
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
853
|
+
// Clear old confirmation
|
|
854
|
+
if (this.confirmAction && Date.now() - this.confirmAction.time > 10000) {
|
|
855
|
+
this.confirmAction = null;
|
|
442
856
|
}
|
|
443
857
|
switch (this.view) {
|
|
444
858
|
case View.LIST:
|
|
@@ -462,67 +876,113 @@ class InteractiveMonitor {
|
|
|
462
876
|
case View.TIMEOUT:
|
|
463
877
|
this.renderTimeout();
|
|
464
878
|
break;
|
|
879
|
+
case View.UNIFIED_LOG:
|
|
880
|
+
this.renderUnifiedLog();
|
|
881
|
+
break;
|
|
882
|
+
case View.FLOWS_DASHBOARD:
|
|
883
|
+
this.renderFlowsDashboard();
|
|
884
|
+
break;
|
|
465
885
|
}
|
|
466
886
|
}
|
|
467
887
|
renderList() {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
888
|
+
const flowSummary = (0, process_1.getFlowSummary)(this.runDir);
|
|
889
|
+
const runId = path.basename(this.runDir);
|
|
890
|
+
this.renderHeader('Lane Dashboard', [runId]);
|
|
891
|
+
// Summary line
|
|
892
|
+
const summaryParts = [
|
|
893
|
+
`${flowSummary.running} ${UI.COLORS.cyan}running${UI.COLORS.reset}`,
|
|
894
|
+
`${flowSummary.completed} ${UI.COLORS.green}done${UI.COLORS.reset}`,
|
|
895
|
+
`${flowSummary.failed} ${UI.COLORS.red}failed${UI.COLORS.reset}`,
|
|
896
|
+
`${flowSummary.dead} ${UI.COLORS.yellow}stale${UI.COLORS.reset}`,
|
|
897
|
+
];
|
|
898
|
+
process.stdout.write(` ${UI.COLORS.dim}Lanes:${UI.COLORS.reset} ${summaryParts.join(' │ ')}\n`);
|
|
472
899
|
if (this.lanes.length === 0) {
|
|
473
|
-
|
|
900
|
+
process.stdout.write(`\n ${UI.COLORS.dim}No lanes found${UI.COLORS.reset}\n`);
|
|
901
|
+
this.renderFooter(['[Q] Quit', '[M] All Flows']);
|
|
474
902
|
return;
|
|
475
903
|
}
|
|
476
904
|
const laneStatuses = {};
|
|
477
905
|
this.lanes.forEach(l => laneStatuses[l.name] = this.getLaneStatus(l.path, l.name));
|
|
478
|
-
const maxNameLen = Math.max(...this.lanes.map(l => l.name.length),
|
|
479
|
-
|
|
480
|
-
|
|
906
|
+
const maxNameLen = Math.max(...this.lanes.map(l => l.name.length), 12);
|
|
907
|
+
process.stdout.write(`\n ${'Lane'.padEnd(maxNameLen)} ${'Status'.padEnd(12)} ${'PID'.padEnd(7)} ${'Time'.padEnd(8)} ${'Tasks'.padEnd(6)} Next\n`);
|
|
908
|
+
process.stdout.write(` ${'─'.repeat(maxNameLen)} ${'─'.repeat(12)} ${'─'.repeat(7)} ${'─'.repeat(8)} ${'─'.repeat(6)} ${'─'.repeat(25)}\n`);
|
|
481
909
|
this.lanes.forEach((lane, i) => {
|
|
482
910
|
const isSelected = i === this.selectedLaneIndex;
|
|
483
911
|
const status = laneStatuses[lane.name];
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
let
|
|
912
|
+
const processStatus = this.laneProcessStatuses.get(lane.name);
|
|
913
|
+
// Determine the accurate status based on process detection
|
|
914
|
+
let displayStatus = status.status;
|
|
915
|
+
let statusColor = UI.COLORS.gray;
|
|
916
|
+
let statusIcon = this.getStatusIcon(status.status);
|
|
917
|
+
if (processStatus) {
|
|
918
|
+
if (processStatus.isStale) {
|
|
919
|
+
displayStatus = 'STALE';
|
|
920
|
+
statusIcon = '💀';
|
|
921
|
+
statusColor = UI.COLORS.yellow;
|
|
922
|
+
}
|
|
923
|
+
else if (processStatus.actualStatus === 'dead' && status.status === 'running') {
|
|
924
|
+
displayStatus = 'DEAD';
|
|
925
|
+
statusIcon = '☠️';
|
|
926
|
+
statusColor = UI.COLORS.red;
|
|
927
|
+
}
|
|
928
|
+
else if (processStatus.actualStatus === 'running') {
|
|
929
|
+
statusColor = UI.COLORS.cyan;
|
|
930
|
+
}
|
|
931
|
+
else if (status.status === 'completed') {
|
|
932
|
+
statusColor = UI.COLORS.green;
|
|
933
|
+
}
|
|
934
|
+
else if (status.status === 'failed') {
|
|
935
|
+
statusColor = UI.COLORS.red;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
const statusText = `${statusIcon} ${displayStatus}`.padEnd(12);
|
|
939
|
+
// Process indicator
|
|
940
|
+
let pidText = '-'.padEnd(7);
|
|
941
|
+
if (processStatus?.pid) {
|
|
942
|
+
const pidIcon = processStatus.processRunning ? '●' : '○';
|
|
943
|
+
const pidColor = processStatus.processRunning ? UI.COLORS.green : UI.COLORS.red;
|
|
944
|
+
pidText = `${pidColor}${pidIcon}${UI.COLORS.reset}${processStatus.pid}`.padEnd(7 + 9); // +9 for color codes
|
|
945
|
+
}
|
|
946
|
+
// Duration
|
|
947
|
+
const duration = processStatus?.duration || status.duration;
|
|
948
|
+
const timeText = this.formatDuration(duration).padEnd(8);
|
|
949
|
+
// Tasks
|
|
950
|
+
let tasksText = '-'.padEnd(6);
|
|
489
951
|
if (typeof status.totalTasks === 'number') {
|
|
490
|
-
|
|
952
|
+
tasksText = `${status.currentTask}/${status.totalTasks}`.padEnd(6);
|
|
491
953
|
}
|
|
492
|
-
|
|
493
|
-
// Determine "Next Action"
|
|
954
|
+
// Next action
|
|
494
955
|
let nextAction = '-';
|
|
495
956
|
if (status.status === 'completed') {
|
|
496
|
-
const dependents = this.lanes.filter(l => laneStatuses[l.name]
|
|
497
|
-
|
|
498
|
-
nextAction = `Unlock: ${dependents.map(d => d.name).join(', ')}`;
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
nextAction = '🏁 Done';
|
|
502
|
-
}
|
|
957
|
+
const dependents = this.lanes.filter(l => laneStatuses[l.name]?.dependsOn?.includes(lane.name));
|
|
958
|
+
nextAction = dependents.length > 0 ? `→ ${dependents.map(d => d.name).join(', ')}` : '✓ Done';
|
|
503
959
|
}
|
|
504
960
|
else if (status.status === 'waiting') {
|
|
505
|
-
if (status.waitingFor
|
|
506
|
-
nextAction =
|
|
961
|
+
if (status.waitingFor?.length > 0) {
|
|
962
|
+
nextAction = `⏳ ${status.waitingFor.join(', ')}`;
|
|
507
963
|
}
|
|
508
964
|
else {
|
|
509
|
-
const missingDeps = status.dependsOn.filter((d) => laneStatuses[d]
|
|
510
|
-
nextAction =
|
|
965
|
+
const missingDeps = status.dependsOn.filter((d) => laneStatuses[d]?.status !== 'completed');
|
|
966
|
+
nextAction = missingDeps.length > 0 ? `⏳ ${missingDeps.join(', ')}` : '⏳ waiting';
|
|
511
967
|
}
|
|
512
968
|
}
|
|
513
|
-
else if (
|
|
514
|
-
nextAction = '🚀
|
|
515
|
-
}
|
|
516
|
-
const prefix = isSelected ? ' ▶ ' : ' ';
|
|
517
|
-
const line = `${prefix}${lane.name.padEnd(maxNameLen)} ${statusText} ${progressText} ${timeText} ${tasksText} ${nextAction}`;
|
|
518
|
-
if (isSelected) {
|
|
519
|
-
process.stdout.write(`\x1b[36m${line}\x1b[0m\n`);
|
|
969
|
+
else if (processStatus?.actualStatus === 'running') {
|
|
970
|
+
nextAction = '🚀 working...';
|
|
520
971
|
}
|
|
521
|
-
else {
|
|
522
|
-
|
|
972
|
+
else if (processStatus?.isStale) {
|
|
973
|
+
nextAction = '⚠️ died unexpectedly';
|
|
523
974
|
}
|
|
975
|
+
// Truncate next action
|
|
976
|
+
if (nextAction.length > 25)
|
|
977
|
+
nextAction = nextAction.substring(0, 22) + '...';
|
|
978
|
+
const prefix = isSelected ? ` ${UI.COLORS.cyan}▶${UI.COLORS.reset} ` : ' ';
|
|
979
|
+
const rowBg = isSelected ? UI.COLORS.bgGray : '';
|
|
980
|
+
const rowEnd = isSelected ? UI.COLORS.reset : '';
|
|
981
|
+
process.stdout.write(`${rowBg}${prefix}${lane.name.padEnd(maxNameLen)} ${statusColor}${statusText}${UI.COLORS.reset} ${pidText} ${timeText} ${tasksText} ${nextAction}${rowEnd}\n`);
|
|
524
982
|
});
|
|
525
|
-
|
|
983
|
+
this.renderFooter([
|
|
984
|
+
'[↑↓] Select', '[→/Enter] Details', '[F] Flow', '[U] Unified Logs', '[M] All Flows', '[Q] Quit'
|
|
985
|
+
]);
|
|
526
986
|
}
|
|
527
987
|
renderLaneDetail() {
|
|
528
988
|
const lane = this.lanes.find(l => l.name === this.selectedLaneName);
|
|
@@ -532,70 +992,92 @@ class InteractiveMonitor {
|
|
|
532
992
|
return;
|
|
533
993
|
}
|
|
534
994
|
const status = this.getLaneStatus(lane.path, lane.name);
|
|
995
|
+
const processStatus = this.laneProcessStatuses.get(lane.name);
|
|
996
|
+
this.renderHeader('Lane Detail', [path.basename(this.runDir), lane.name]);
|
|
997
|
+
// Status grid
|
|
998
|
+
const statusColor = status.status === 'completed' ? UI.COLORS.green
|
|
999
|
+
: status.status === 'failed' ? UI.COLORS.red
|
|
1000
|
+
: status.status === 'running' ? UI.COLORS.cyan : UI.COLORS.gray;
|
|
1001
|
+
const actualStatus = processStatus?.actualStatus || status.status;
|
|
1002
|
+
const isStale = processStatus?.isStale || false;
|
|
1003
|
+
process.stdout.write(`\n`);
|
|
1004
|
+
process.stdout.write(` ${UI.COLORS.dim}Status${UI.COLORS.reset} ${statusColor}${this.getStatusIcon(actualStatus)} ${actualStatus.toUpperCase()}${UI.COLORS.reset}`);
|
|
1005
|
+
if (isStale)
|
|
1006
|
+
process.stdout.write(` ${UI.COLORS.yellow}(stale)${UI.COLORS.reset}`);
|
|
1007
|
+
process.stdout.write(`\n`);
|
|
1008
|
+
const pidDisplay = processStatus?.pid
|
|
1009
|
+
? `${processStatus.processRunning ? UI.COLORS.green : UI.COLORS.red}${processStatus.pid}${UI.COLORS.reset}`
|
|
1010
|
+
: '-';
|
|
1011
|
+
process.stdout.write(` ${UI.COLORS.dim}PID${UI.COLORS.reset} ${pidDisplay}\n`);
|
|
1012
|
+
process.stdout.write(` ${UI.COLORS.dim}Progress${UI.COLORS.reset} ${status.currentTask}/${status.totalTasks} tasks (${status.progress})\n`);
|
|
1013
|
+
process.stdout.write(` ${UI.COLORS.dim}Duration${UI.COLORS.reset} ${this.formatDuration(processStatus?.duration || status.duration)}\n`);
|
|
1014
|
+
process.stdout.write(` ${UI.COLORS.dim}Branch${UI.COLORS.reset} ${status.pipelineBranch}\n`);
|
|
1015
|
+
if (status.dependsOn && status.dependsOn.length > 0) {
|
|
1016
|
+
process.stdout.write(` ${UI.COLORS.dim}Depends${UI.COLORS.reset} ${status.dependsOn.join(', ')}\n`);
|
|
1017
|
+
}
|
|
1018
|
+
if (status.waitingFor && status.waitingFor.length > 0) {
|
|
1019
|
+
process.stdout.write(` ${UI.COLORS.yellow}Waiting${UI.COLORS.reset} ${status.waitingFor.join(', ')}\n`);
|
|
1020
|
+
}
|
|
1021
|
+
if (status.error) {
|
|
1022
|
+
process.stdout.write(` ${UI.COLORS.red}Error${UI.COLORS.reset} ${status.error}\n`);
|
|
1023
|
+
}
|
|
1024
|
+
// Live terminal preview
|
|
1025
|
+
this.renderSectionTitle('Live Terminal', 'last 10 lines');
|
|
535
1026
|
const logPath = (0, path_1.safeJoin)(lane.path, 'terminal.log');
|
|
536
|
-
let liveLog = '(No live terminal output)';
|
|
537
1027
|
if (fs.existsSync(logPath)) {
|
|
538
1028
|
const content = fs.readFileSync(logPath, 'utf8');
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1029
|
+
const lines = content.split('\n').slice(-10);
|
|
1030
|
+
for (const line of lines) {
|
|
1031
|
+
const formatted = this.formatTerminalLine(line);
|
|
1032
|
+
process.stdout.write(` ${UI.COLORS.dim}${formatted.substring(0, this.screenWidth - 4)}${UI.COLORS.reset}\n`);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
process.stdout.write(` ${UI.COLORS.dim}(No output yet)${UI.COLORS.reset}\n`);
|
|
1037
|
+
}
|
|
1038
|
+
// Conversation preview
|
|
1039
|
+
this.renderSectionTitle('Conversation', `${this.currentLogs.length} messages`);
|
|
1040
|
+
const maxVisible = 8;
|
|
1041
|
+
if (this.selectedMessageIndex < this.scrollOffset) {
|
|
1042
|
+
this.scrollOffset = this.selectedMessageIndex;
|
|
1043
|
+
}
|
|
1044
|
+
else if (this.selectedMessageIndex >= this.scrollOffset + maxVisible) {
|
|
1045
|
+
this.scrollOffset = this.selectedMessageIndex - maxVisible + 1;
|
|
554
1046
|
}
|
|
555
|
-
if (status.error) {
|
|
556
|
-
process.stdout.write(`\x1b[31m Error: ${status.error}\x1b[0m\n`);
|
|
557
|
-
}
|
|
558
|
-
console.log('\n🖥️ Live Terminal Output (Last 15 lines):');
|
|
559
|
-
console.log('─'.repeat(80));
|
|
560
|
-
console.log(`\x1b[90m${liveLog}\x1b[0m`);
|
|
561
|
-
console.log('\n💬 Conversation History (Select to see full details):');
|
|
562
|
-
console.log('─'.repeat(80));
|
|
563
|
-
process.stdout.write(' [↑/↓] Browse | [→/Enter] Full Msg | [I] Intervene | [K] Kill | [T] Live Terminal | [Esc/←] Back\n\n');
|
|
564
1047
|
if (this.currentLogs.length === 0) {
|
|
565
|
-
|
|
1048
|
+
process.stdout.write(` ${UI.COLORS.dim}(No messages yet)${UI.COLORS.reset}\n`);
|
|
566
1049
|
}
|
|
567
1050
|
else {
|
|
568
|
-
// Simple windowed view for long histories
|
|
569
|
-
const maxVisible = 15; // Number of messages to show
|
|
570
|
-
if (this.selectedMessageIndex < this.scrollOffset) {
|
|
571
|
-
this.scrollOffset = this.selectedMessageIndex;
|
|
572
|
-
}
|
|
573
|
-
else if (this.selectedMessageIndex >= this.scrollOffset + maxVisible) {
|
|
574
|
-
this.scrollOffset = this.selectedMessageIndex - maxVisible + 1;
|
|
575
|
-
}
|
|
576
1051
|
const visibleLogs = this.currentLogs.slice(this.scrollOffset, this.scrollOffset + maxVisible);
|
|
577
1052
|
visibleLogs.forEach((log, i) => {
|
|
578
1053
|
const actualIndex = i + this.scrollOffset;
|
|
579
1054
|
const isSelected = actualIndex === this.selectedMessageIndex;
|
|
580
|
-
const roleColor =
|
|
1055
|
+
const roleColor = this.getRoleColor(log.role);
|
|
581
1056
|
const role = log.role.toUpperCase().padEnd(10);
|
|
582
|
-
const
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
process.stdout.write(`${header}\n`);
|
|
589
|
-
}
|
|
590
|
-
const lines = log.fullText.split('\n').filter(l => l.trim());
|
|
591
|
-
const preview = lines[0]?.substring(0, 70) || '...';
|
|
592
|
-
process.stdout.write(` ${preview}${log.fullText.length > 70 ? '...' : ''}\n\n`);
|
|
1057
|
+
const ts = new Date(log.timestamp).toLocaleTimeString('en-US', { hour12: false });
|
|
1058
|
+
const prefix = isSelected ? `${UI.COLORS.cyan}▶${UI.COLORS.reset}` : ' ';
|
|
1059
|
+
const bg = isSelected ? UI.COLORS.bgGray : '';
|
|
1060
|
+
const reset = isSelected ? UI.COLORS.reset : '';
|
|
1061
|
+
const preview = log.fullText.replace(/\n/g, ' ').substring(0, 60);
|
|
1062
|
+
process.stdout.write(`${bg}${prefix} ${roleColor}${role}${UI.COLORS.reset} ${UI.COLORS.dim}${ts}${UI.COLORS.reset} ${preview}...${reset}\n`);
|
|
593
1063
|
});
|
|
594
1064
|
if (this.currentLogs.length > maxVisible) {
|
|
595
|
-
|
|
1065
|
+
process.stdout.write(` ${UI.COLORS.dim}(${this.currentLogs.length - maxVisible} more messages)${UI.COLORS.reset}\n`);
|
|
596
1066
|
}
|
|
597
1067
|
}
|
|
598
|
-
|
|
1068
|
+
this.renderFooter([
|
|
1069
|
+
'[↑↓] Scroll', '[→/Enter] Full Msg', '[T] Terminal', '[I] Intervene', '[K] Kill', '[←/Esc] Back'
|
|
1070
|
+
]);
|
|
1071
|
+
}
|
|
1072
|
+
getRoleColor(role) {
|
|
1073
|
+
const colors = {
|
|
1074
|
+
user: UI.COLORS.yellow,
|
|
1075
|
+
assistant: UI.COLORS.green,
|
|
1076
|
+
reviewer: UI.COLORS.magenta,
|
|
1077
|
+
intervention: UI.COLORS.red,
|
|
1078
|
+
system: UI.COLORS.cyan,
|
|
1079
|
+
};
|
|
1080
|
+
return colors[role] || UI.COLORS.gray;
|
|
599
1081
|
}
|
|
600
1082
|
renderMessageDetail() {
|
|
601
1083
|
const log = this.currentLogs[this.selectedMessageIndex];
|
|
@@ -604,51 +1086,143 @@ class InteractiveMonitor {
|
|
|
604
1086
|
this.render();
|
|
605
1087
|
return;
|
|
606
1088
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
process.stdout.write(
|
|
1089
|
+
this.renderHeader('Message Detail', [path.basename(this.runDir), this.selectedLaneName || '', log.role.toUpperCase()]);
|
|
1090
|
+
const roleColor = this.getRoleColor(log.role);
|
|
1091
|
+
const ts = new Date(log.timestamp).toLocaleString();
|
|
1092
|
+
process.stdout.write(`\n`);
|
|
1093
|
+
process.stdout.write(` ${UI.COLORS.dim}Role${UI.COLORS.reset} ${roleColor}${log.role.toUpperCase()}${UI.COLORS.reset}\n`);
|
|
1094
|
+
process.stdout.write(` ${UI.COLORS.dim}Time${UI.COLORS.reset} ${ts}\n`);
|
|
613
1095
|
if (log.model)
|
|
614
|
-
process.stdout.write(`
|
|
1096
|
+
process.stdout.write(` ${UI.COLORS.dim}Model${UI.COLORS.reset} ${log.model}\n`);
|
|
615
1097
|
if (log.task)
|
|
616
|
-
process.stdout.write(`
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1098
|
+
process.stdout.write(` ${UI.COLORS.dim}Task${UI.COLORS.reset} ${log.task}\n`);
|
|
1099
|
+
this.renderSectionTitle('Content');
|
|
1100
|
+
// Display message content with wrapping
|
|
1101
|
+
const maxWidth = this.screenWidth - 4;
|
|
1102
|
+
const lines = log.fullText.split('\n');
|
|
1103
|
+
const maxLines = this.screenHeight - 16;
|
|
1104
|
+
let lineCount = 0;
|
|
1105
|
+
for (const line of lines) {
|
|
1106
|
+
if (lineCount >= maxLines) {
|
|
1107
|
+
process.stdout.write(` ${UI.COLORS.dim}... (truncated, ${lines.length - lineCount} more lines)${UI.COLORS.reset}\n`);
|
|
1108
|
+
break;
|
|
1109
|
+
}
|
|
1110
|
+
// Word wrap long lines
|
|
1111
|
+
if (line.length > maxWidth) {
|
|
1112
|
+
const wrapped = this.wrapText(line, maxWidth);
|
|
1113
|
+
for (const wl of wrapped) {
|
|
1114
|
+
if (lineCount >= maxLines)
|
|
1115
|
+
break;
|
|
1116
|
+
process.stdout.write(` ${wl}\n`);
|
|
1117
|
+
lineCount++;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
process.stdout.write(` ${line}\n`);
|
|
1122
|
+
lineCount++;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
this.renderFooter(['[←/Esc] Back']);
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Wrap text to specified width
|
|
1129
|
+
*/
|
|
1130
|
+
wrapText(text, maxWidth) {
|
|
1131
|
+
const words = text.split(' ');
|
|
1132
|
+
const lines = [];
|
|
1133
|
+
let currentLine = '';
|
|
1134
|
+
for (const word of words) {
|
|
1135
|
+
if (currentLine.length + word.length + 1 <= maxWidth) {
|
|
1136
|
+
currentLine += (currentLine ? ' ' : '') + word;
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
if (currentLine)
|
|
1140
|
+
lines.push(currentLine);
|
|
1141
|
+
currentLine = word;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (currentLine)
|
|
1145
|
+
lines.push(currentLine);
|
|
1146
|
+
return lines;
|
|
620
1147
|
}
|
|
621
1148
|
renderFlow() {
|
|
622
|
-
|
|
623
|
-
console.log(`⛓️ Task Dependency Flow`);
|
|
624
|
-
console.log(`🕒 Updated: ${new Date().toLocaleTimeString()} | [→/Enter/Esc] Back to List`);
|
|
625
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
1149
|
+
this.renderHeader('Dependency Flow', [path.basename(this.runDir), 'Flow']);
|
|
626
1150
|
const laneMap = new Map();
|
|
627
1151
|
this.lanes.forEach(lane => {
|
|
628
1152
|
laneMap.set(lane.name, this.getLaneStatus(lane.path, lane.name));
|
|
629
1153
|
});
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
1154
|
+
process.stdout.write('\n');
|
|
1155
|
+
// Group lanes by dependency level
|
|
1156
|
+
const levels = this.calculateDependencyLevels();
|
|
1157
|
+
const maxLevelWidth = Math.max(...levels.map(l => l.length));
|
|
1158
|
+
for (let level = 0; level < levels.length; level++) {
|
|
1159
|
+
const lanesAtLevel = levels[level];
|
|
1160
|
+
// Level header
|
|
1161
|
+
process.stdout.write(` ${UI.COLORS.dim}Level ${level}${UI.COLORS.reset}\n`);
|
|
1162
|
+
for (const laneName of lanesAtLevel) {
|
|
1163
|
+
const status = laneMap.get(laneName);
|
|
1164
|
+
const statusIcon = this.getStatusIcon(status?.status || 'pending');
|
|
1165
|
+
let statusColor = UI.COLORS.gray;
|
|
1166
|
+
if (status?.status === 'completed')
|
|
1167
|
+
statusColor = UI.COLORS.green;
|
|
1168
|
+
else if (status?.status === 'running')
|
|
1169
|
+
statusColor = UI.COLORS.cyan;
|
|
1170
|
+
else if (status?.status === 'failed')
|
|
1171
|
+
statusColor = UI.COLORS.red;
|
|
1172
|
+
// Render the node
|
|
1173
|
+
const nodeText = `${statusIcon} ${laneName}`;
|
|
1174
|
+
process.stdout.write(` ${statusColor}${nodeText.padEnd(20)}${UI.COLORS.reset}`);
|
|
1175
|
+
// Render dependencies
|
|
1176
|
+
if (status?.dependsOn?.length > 0) {
|
|
1177
|
+
process.stdout.write(` ${UI.COLORS.dim}←${UI.COLORS.reset} ${UI.COLORS.yellow}${status.dependsOn.join(', ')}${UI.COLORS.reset}`);
|
|
1178
|
+
}
|
|
1179
|
+
process.stdout.write('\n');
|
|
1180
|
+
}
|
|
1181
|
+
if (level < levels.length - 1) {
|
|
1182
|
+
process.stdout.write(` ${UI.COLORS.dim}│${UI.COLORS.reset}\n`);
|
|
1183
|
+
process.stdout.write(` ${UI.COLORS.dim}▼${UI.COLORS.reset}\n`);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
process.stdout.write(`\n ${UI.COLORS.dim}Lanes wait for dependencies to complete before starting${UI.COLORS.reset}\n`);
|
|
1187
|
+
this.renderFooter(['[←/Esc] Back']);
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Calculate dependency levels for visualization
|
|
1191
|
+
*/
|
|
1192
|
+
calculateDependencyLevels() {
|
|
1193
|
+
const levels = [];
|
|
1194
|
+
const assigned = new Set();
|
|
1195
|
+
// First, find lanes with no dependencies
|
|
1196
|
+
const noDeps = this.lanes.filter(l => !l.dependsOn || l.dependsOn.length === 0);
|
|
1197
|
+
if (noDeps.length > 0) {
|
|
1198
|
+
levels.push(noDeps.map(l => l.name));
|
|
1199
|
+
noDeps.forEach(l => assigned.add(l.name));
|
|
1200
|
+
}
|
|
1201
|
+
// Then assign remaining lanes by dependency completion
|
|
1202
|
+
let maxIterations = 10;
|
|
1203
|
+
while (assigned.size < this.lanes.length && maxIterations-- > 0) {
|
|
1204
|
+
const nextLevel = [];
|
|
1205
|
+
for (const lane of this.lanes) {
|
|
1206
|
+
if (assigned.has(lane.name))
|
|
1207
|
+
continue;
|
|
1208
|
+
// Check if all dependencies are assigned
|
|
1209
|
+
const allDepsAssigned = lane.dependsOn.every(d => assigned.has(d));
|
|
1210
|
+
if (allDepsAssigned) {
|
|
1211
|
+
nextLevel.push(lane.name);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (nextLevel.length === 0) {
|
|
1215
|
+
// Remaining lanes have circular deps or missing deps
|
|
1216
|
+
const remaining = this.lanes.filter(l => !assigned.has(l.name)).map(l => l.name);
|
|
1217
|
+
if (remaining.length > 0) {
|
|
1218
|
+
levels.push(remaining);
|
|
1219
|
+
}
|
|
1220
|
+
break;
|
|
1221
|
+
}
|
|
1222
|
+
levels.push(nextLevel);
|
|
1223
|
+
nextLevel.forEach(n => assigned.add(n));
|
|
1224
|
+
}
|
|
1225
|
+
return levels;
|
|
652
1226
|
}
|
|
653
1227
|
renderTerminal() {
|
|
654
1228
|
const lane = this.lanes.find(l => l.name === this.selectedLaneName);
|
|
@@ -657,18 +1231,35 @@ class InteractiveMonitor {
|
|
|
657
1231
|
this.render();
|
|
658
1232
|
return;
|
|
659
1233
|
}
|
|
660
|
-
|
|
1234
|
+
this.renderHeader('Live Terminal', [path.basename(this.runDir), lane.name, 'Terminal']);
|
|
1235
|
+
// Get logs based on format mode
|
|
661
1236
|
let logLines = [];
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1237
|
+
let totalLines = 0;
|
|
1238
|
+
if (this.readableFormat) {
|
|
1239
|
+
// Use JSONL for readable format
|
|
1240
|
+
const jsonlPath = (0, path_1.safeJoin)(lane.path, 'terminal.jsonl');
|
|
1241
|
+
logLines = this.getReadableLogLines(jsonlPath, lane.name);
|
|
1242
|
+
totalLines = logLines.length;
|
|
665
1243
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1244
|
+
else {
|
|
1245
|
+
// Use raw log
|
|
1246
|
+
const logPath = (0, path_1.safeJoin)(lane.path, 'terminal.log');
|
|
1247
|
+
if (fs.existsSync(logPath)) {
|
|
1248
|
+
const content = fs.readFileSync(logPath, 'utf8');
|
|
1249
|
+
logLines = content.split('\n');
|
|
1250
|
+
totalLines = logLines.length;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
const maxVisible = this.screenHeight - 10;
|
|
1254
|
+
// Follow mode logic
|
|
1255
|
+
if (this.followMode) {
|
|
1256
|
+
this.terminalScrollOffset = 0;
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
if (this.lastTerminalTotalLines > 0 && totalLines > this.lastTerminalTotalLines) {
|
|
1260
|
+
this.unseenLineCount += (totalLines - this.lastTerminalTotalLines);
|
|
1261
|
+
this.terminalScrollOffset += (totalLines - this.lastTerminalTotalLines);
|
|
1262
|
+
}
|
|
672
1263
|
}
|
|
673
1264
|
this.lastTerminalTotalLines = totalLines;
|
|
674
1265
|
// Clamp scroll offset
|
|
@@ -676,55 +1267,283 @@ class InteractiveMonitor {
|
|
|
676
1267
|
if (this.terminalScrollOffset > maxScroll) {
|
|
677
1268
|
this.terminalScrollOffset = maxScroll;
|
|
678
1269
|
}
|
|
1270
|
+
// Mode and status indicators
|
|
1271
|
+
const formatMode = this.readableFormat
|
|
1272
|
+
? `${UI.COLORS.green}[R] Readable ✓${UI.COLORS.reset}`
|
|
1273
|
+
: `${UI.COLORS.dim}[R] Raw${UI.COLORS.reset}`;
|
|
1274
|
+
const followStatus = this.followMode
|
|
1275
|
+
? `${UI.COLORS.green}[F] Follow ✓${UI.COLORS.reset}`
|
|
1276
|
+
: `${UI.COLORS.yellow}[F] Follow OFF${this.unseenLineCount > 0 ? ` (↓${this.unseenLineCount})` : ''}${UI.COLORS.reset}`;
|
|
1277
|
+
process.stdout.write(` ${formatMode} ${followStatus} ${UI.COLORS.dim}Lines: ${totalLines}${UI.COLORS.reset}\n\n`);
|
|
679
1278
|
// Slice based on scroll (0 means bottom, >0 means scrolled up)
|
|
680
1279
|
const end = totalLines - this.terminalScrollOffset;
|
|
681
1280
|
const start = Math.max(0, end - maxVisible);
|
|
682
1281
|
const visibleLines = logLines.slice(start, end);
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1282
|
+
for (const line of visibleLines) {
|
|
1283
|
+
const formatted = this.readableFormat ? line : this.formatTerminalLine(line);
|
|
1284
|
+
// Truncate to screen width
|
|
1285
|
+
const displayLine = formatted.length > this.screenWidth - 2
|
|
1286
|
+
? formatted.substring(0, this.screenWidth - 5) + '...'
|
|
1287
|
+
: formatted;
|
|
1288
|
+
process.stdout.write(` ${displayLine}\n`);
|
|
1289
|
+
}
|
|
1290
|
+
if (visibleLines.length === 0) {
|
|
1291
|
+
process.stdout.write(` ${UI.COLORS.dim}(No output yet)${UI.COLORS.reset}\n`);
|
|
1292
|
+
}
|
|
1293
|
+
this.renderFooter([
|
|
1294
|
+
'[↑↓] Scroll', '[F] Follow', '[R] Toggle Readable', '[I] Intervene', '[←/Esc] Back'
|
|
1295
|
+
]);
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Format a raw terminal line with syntax highlighting
|
|
1299
|
+
*/
|
|
1300
|
+
formatTerminalLine(line) {
|
|
1301
|
+
// Highlight patterns
|
|
1302
|
+
if (line.includes('[HUMAN INTERVENTION]') || line.includes('Injecting intervention:')) {
|
|
1303
|
+
return `${UI.COLORS.yellow}${UI.COLORS.bold}${line}${UI.COLORS.reset}`;
|
|
1304
|
+
}
|
|
1305
|
+
if (line.includes('Executing cursor-agent')) {
|
|
1306
|
+
return `${UI.COLORS.cyan}${UI.COLORS.bold}${line}${UI.COLORS.reset}`;
|
|
1307
|
+
}
|
|
1308
|
+
if (line.includes('=== Task:') || line.includes('Starting task:')) {
|
|
1309
|
+
return `${UI.COLORS.green}${UI.COLORS.bold}${line}${UI.COLORS.reset}`;
|
|
1310
|
+
}
|
|
1311
|
+
if (line.toLowerCase().includes('error') || line.toLowerCase().includes('failed')) {
|
|
1312
|
+
return `${UI.COLORS.red}${line}${UI.COLORS.reset}`;
|
|
1313
|
+
}
|
|
1314
|
+
if (line.toLowerCase().includes('success') || line.toLowerCase().includes('completed')) {
|
|
1315
|
+
return `${UI.COLORS.green}${line}${UI.COLORS.reset}`;
|
|
1316
|
+
}
|
|
1317
|
+
return line;
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Get readable log lines from JSONL file
|
|
1321
|
+
*/
|
|
1322
|
+
getReadableLogLines(jsonlPath, laneName) {
|
|
1323
|
+
if (!fs.existsSync(jsonlPath)) {
|
|
1324
|
+
// Fallback: try to read raw log
|
|
1325
|
+
const rawPath = jsonlPath.replace('.jsonl', '.log');
|
|
1326
|
+
if (fs.existsSync(rawPath)) {
|
|
1327
|
+
return fs.readFileSync(rawPath, 'utf8').split('\n').map(l => this.formatTerminalLine(l));
|
|
1328
|
+
}
|
|
1329
|
+
return [];
|
|
1330
|
+
}
|
|
1331
|
+
try {
|
|
1332
|
+
const content = fs.readFileSync(jsonlPath, 'utf8');
|
|
1333
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
1334
|
+
return lines.map(line => {
|
|
1335
|
+
try {
|
|
1336
|
+
const entry = JSON.parse(line);
|
|
1337
|
+
const ts = new Date(entry.timestamp || Date.now()).toLocaleTimeString('en-US', { hour12: false });
|
|
1338
|
+
const type = (entry.type || 'info').toLowerCase();
|
|
1339
|
+
const content = entry.content || entry.message || '';
|
|
1340
|
+
// Format based on type
|
|
1341
|
+
const typeInfo = this.getLogTypeInfo(type);
|
|
1342
|
+
const preview = content.replace(/\n/g, ' ').substring(0, 100);
|
|
1343
|
+
return `${UI.COLORS.dim}[${ts}]${UI.COLORS.reset} ${typeInfo.color}[${typeInfo.label}]${UI.COLORS.reset} ${preview}`;
|
|
1344
|
+
}
|
|
1345
|
+
catch {
|
|
1346
|
+
return this.formatTerminalLine(line);
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
catch {
|
|
1351
|
+
return [];
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Get log type display info
|
|
1356
|
+
*/
|
|
1357
|
+
getLogTypeInfo(type) {
|
|
1358
|
+
const typeMap = {
|
|
1359
|
+
user: { label: 'USER ', color: UI.COLORS.cyan },
|
|
1360
|
+
assistant: { label: 'ASST ', color: UI.COLORS.green },
|
|
1361
|
+
tool: { label: 'TOOL ', color: UI.COLORS.yellow },
|
|
1362
|
+
tool_result: { label: 'RESULT', color: UI.COLORS.gray },
|
|
1363
|
+
result: { label: 'DONE ', color: UI.COLORS.green },
|
|
1364
|
+
system: { label: 'SYSTEM', color: UI.COLORS.gray },
|
|
1365
|
+
thinking: { label: 'THINK ', color: UI.COLORS.dim },
|
|
1366
|
+
error: { label: 'ERROR ', color: UI.COLORS.red },
|
|
1367
|
+
stderr: { label: 'STDERR', color: UI.COLORS.red },
|
|
1368
|
+
stdout: { label: 'STDOUT', color: UI.COLORS.white },
|
|
1369
|
+
};
|
|
1370
|
+
return typeMap[type] || { label: type.toUpperCase().padEnd(6).substring(0, 6), color: UI.COLORS.gray };
|
|
708
1371
|
}
|
|
709
1372
|
renderIntervene() {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1373
|
+
this.renderHeader('Human Intervention', [path.basename(this.runDir), this.selectedLaneName || '', 'Intervene']);
|
|
1374
|
+
process.stdout.write(`\n`);
|
|
1375
|
+
process.stdout.write(` ${UI.COLORS.yellow}Send a message directly to the agent.${UI.COLORS.reset}\n`);
|
|
1376
|
+
process.stdout.write(` ${UI.COLORS.dim}This will interrupt the current flow and inject your instruction.${UI.COLORS.reset}\n\n`);
|
|
1377
|
+
// Input box
|
|
1378
|
+
const width = Math.min(this.screenWidth - 8, 80);
|
|
1379
|
+
process.stdout.write(` ${UI.COLORS.cyan}┌${'─'.repeat(width)}┐${UI.COLORS.reset}\n`);
|
|
1380
|
+
// Wrap input text
|
|
1381
|
+
const inputLines = this.wrapText(this.interventionInput || ' ', width - 4);
|
|
1382
|
+
for (const line of inputLines) {
|
|
1383
|
+
process.stdout.write(` ${UI.COLORS.cyan}│${UI.COLORS.reset} ${line.padEnd(width - 2)} ${UI.COLORS.cyan}│${UI.COLORS.reset}\n`);
|
|
1384
|
+
}
|
|
1385
|
+
if (inputLines.length === 0 || inputLines[inputLines.length - 1] === ' ') {
|
|
1386
|
+
process.stdout.write(` ${UI.COLORS.cyan}│${UI.COLORS.reset} ${UI.COLORS.white}█${UI.COLORS.reset}${' '.repeat(width - 3)} ${UI.COLORS.cyan}│${UI.COLORS.reset}\n`);
|
|
1387
|
+
}
|
|
1388
|
+
process.stdout.write(` ${UI.COLORS.cyan}└${'─'.repeat(width)}┘${UI.COLORS.reset}\n`);
|
|
1389
|
+
this.renderFooter(['[Enter] Send', '[Esc] Cancel']);
|
|
718
1390
|
}
|
|
719
1391
|
renderTimeout() {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
1392
|
+
this.renderHeader('Update Timeout', [path.basename(this.runDir), this.selectedLaneName || '', 'Timeout']);
|
|
1393
|
+
process.stdout.write(`\n`);
|
|
1394
|
+
process.stdout.write(` ${UI.COLORS.yellow}Update the task timeout for this lane.${UI.COLORS.reset}\n`);
|
|
1395
|
+
process.stdout.write(` ${UI.COLORS.dim}Enter timeout in milliseconds (e.g., 600000 = 10 minutes)${UI.COLORS.reset}\n\n`);
|
|
1396
|
+
// Common presets
|
|
1397
|
+
process.stdout.write(` ${UI.COLORS.dim}Presets: 300000 (5m) | 600000 (10m) | 1800000 (30m) | 3600000 (1h)${UI.COLORS.reset}\n\n`);
|
|
1398
|
+
// Input box
|
|
1399
|
+
const width = 40;
|
|
1400
|
+
process.stdout.write(` ${UI.COLORS.cyan}┌${'─'.repeat(width)}┐${UI.COLORS.reset}\n`);
|
|
1401
|
+
process.stdout.write(` ${UI.COLORS.cyan}│${UI.COLORS.reset} ${(this.timeoutInput || '').padEnd(width - 2)}${UI.COLORS.white}█${UI.COLORS.reset} ${UI.COLORS.cyan}│${UI.COLORS.reset}\n`);
|
|
1402
|
+
process.stdout.write(` ${UI.COLORS.cyan}└${'─'.repeat(width)}┘${UI.COLORS.reset}\n`);
|
|
1403
|
+
// Show human-readable interpretation
|
|
1404
|
+
if (this.timeoutInput) {
|
|
1405
|
+
const ms = parseInt(this.timeoutInput);
|
|
1406
|
+
if (!isNaN(ms) && ms > 0) {
|
|
1407
|
+
const formatted = this.formatDuration(ms);
|
|
1408
|
+
process.stdout.write(`\n ${UI.COLORS.green}= ${formatted}${UI.COLORS.reset}\n`);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
this.renderFooter(['[Enter] Apply', '[Esc] Cancel']);
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Render unified log view - all lanes combined
|
|
1415
|
+
*/
|
|
1416
|
+
renderUnifiedLog() {
|
|
1417
|
+
this.renderHeader('Unified Logs', [path.basename(this.runDir), 'All Lanes']);
|
|
1418
|
+
const bufferState = this.unifiedLogBuffer?.getState();
|
|
1419
|
+
const totalEntries = bufferState?.totalEntries || 0;
|
|
1420
|
+
const availableLanes = bufferState?.lanes || [];
|
|
1421
|
+
// Status bar
|
|
1422
|
+
const formatMode = this.readableFormat
|
|
1423
|
+
? `${UI.COLORS.green}[R] Readable ✓${UI.COLORS.reset}`
|
|
1424
|
+
: `${UI.COLORS.dim}[R] Compact${UI.COLORS.reset}`;
|
|
1425
|
+
const followStatus = this.unifiedLogFollowMode
|
|
1426
|
+
? `${UI.COLORS.green}[F] Follow ✓${UI.COLORS.reset}`
|
|
1427
|
+
: `${UI.COLORS.yellow}[F] Follow OFF${UI.COLORS.reset}`;
|
|
1428
|
+
const filterStatus = this.laneFilter
|
|
1429
|
+
? `${UI.COLORS.cyan}[L] ${this.laneFilter}${UI.COLORS.reset}`
|
|
1430
|
+
: `${UI.COLORS.dim}[L] All Lanes${UI.COLORS.reset}`;
|
|
1431
|
+
process.stdout.write(` ${formatMode} ${followStatus} ${filterStatus} ${UI.COLORS.dim}Total: ${totalEntries}${UI.COLORS.reset}\n`);
|
|
1432
|
+
// Lane list for filtering hint
|
|
1433
|
+
if (availableLanes.length > 1) {
|
|
1434
|
+
process.stdout.write(` ${UI.COLORS.dim}Lanes: ${availableLanes.join(', ')}${UI.COLORS.reset}\n`);
|
|
1435
|
+
}
|
|
1436
|
+
process.stdout.write('\n');
|
|
1437
|
+
if (!this.unifiedLogBuffer) {
|
|
1438
|
+
process.stdout.write(` ${UI.COLORS.dim}(No log buffer available)${UI.COLORS.reset}\n`);
|
|
1439
|
+
this.renderFooter(['[U/Esc] Back', '[Q] Quit']);
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
const pageSize = this.screenHeight - 12;
|
|
1443
|
+
const filter = this.laneFilter ? { lane: this.laneFilter } : undefined;
|
|
1444
|
+
const entries = this.unifiedLogBuffer.getEntries({
|
|
1445
|
+
offset: this.unifiedLogScrollOffset,
|
|
1446
|
+
limit: pageSize,
|
|
1447
|
+
filter,
|
|
1448
|
+
fromEnd: true,
|
|
1449
|
+
});
|
|
1450
|
+
if (entries.length === 0) {
|
|
1451
|
+
process.stdout.write(` ${UI.COLORS.dim}(No log entries yet)${UI.COLORS.reset}\n`);
|
|
1452
|
+
}
|
|
1453
|
+
else {
|
|
1454
|
+
for (const entry of entries) {
|
|
1455
|
+
const formatted = this.formatUnifiedLogEntry(entry);
|
|
1456
|
+
const displayLine = formatted.length > this.screenWidth - 2
|
|
1457
|
+
? formatted.substring(0, this.screenWidth - 5) + '...'
|
|
1458
|
+
: formatted;
|
|
1459
|
+
process.stdout.write(` ${displayLine}\n`);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
this.renderFooter([
|
|
1463
|
+
'[↑↓/PgUp/PgDn] Scroll', '[F] Follow', '[R] Readable', '[L] Filter Lane', '[U/Esc] Back'
|
|
1464
|
+
]);
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Format a unified log entry
|
|
1468
|
+
*/
|
|
1469
|
+
formatUnifiedLogEntry(entry) {
|
|
1470
|
+
const ts = entry.timestamp.toLocaleTimeString('en-US', { hour12: false });
|
|
1471
|
+
const lane = entry.laneName.padEnd(12);
|
|
1472
|
+
const typeInfo = this.getLogTypeInfo(entry.type || 'info');
|
|
1473
|
+
if (this.readableFormat) {
|
|
1474
|
+
// Readable format: show more context
|
|
1475
|
+
const content = entry.message.replace(/\n/g, ' ');
|
|
1476
|
+
return `${UI.COLORS.dim}[${ts}]${UI.COLORS.reset} ${entry.laneColor}[${lane}]${UI.COLORS.reset} ${typeInfo.color}[${typeInfo.label}]${UI.COLORS.reset} ${content}`;
|
|
1477
|
+
}
|
|
1478
|
+
else {
|
|
1479
|
+
// Compact format
|
|
1480
|
+
const preview = entry.message.replace(/\n/g, ' ').substring(0, 60);
|
|
1481
|
+
return `${UI.COLORS.dim}${ts}${UI.COLORS.reset} ${entry.laneColor}${entry.laneName.substring(0, 8).padEnd(8)}${UI.COLORS.reset} ${typeInfo.color}${typeInfo.label.trim().substring(0, 4)}${UI.COLORS.reset} ${preview}`;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Render multiple flows dashboard
|
|
1486
|
+
*/
|
|
1487
|
+
renderFlowsDashboard() {
|
|
1488
|
+
this.renderHeader('All Flows', ['Flows Dashboard']);
|
|
1489
|
+
process.stdout.write(` ${UI.COLORS.dim}Total: ${this.allFlows.length} flows${UI.COLORS.reset}\n\n`);
|
|
1490
|
+
if (this.allFlows.length === 0) {
|
|
1491
|
+
process.stdout.write(` ${UI.COLORS.dim}No flow runs found.${UI.COLORS.reset}\n\n`);
|
|
1492
|
+
process.stdout.write(` Run ${UI.COLORS.cyan}cursorflow run${UI.COLORS.reset} to start a new flow.\n`);
|
|
1493
|
+
this.renderFooter(['[M/Esc] Back', '[Q] Quit']);
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
// Header
|
|
1497
|
+
process.stdout.write(` ${'Status'.padEnd(8)} ${'Run ID'.padEnd(32)} ${'Lanes'.padEnd(12)} Progress\n`);
|
|
1498
|
+
process.stdout.write(` ${'─'.repeat(8)} ${'─'.repeat(32)} ${'─'.repeat(12)} ${'─'.repeat(20)}\n`);
|
|
1499
|
+
const maxVisible = this.screenHeight - 14;
|
|
1500
|
+
const startIdx = Math.max(0, this.selectedFlowIndex - Math.floor(maxVisible / 2));
|
|
1501
|
+
const endIdx = Math.min(this.allFlows.length, startIdx + maxVisible);
|
|
1502
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
1503
|
+
const flow = this.allFlows[i];
|
|
1504
|
+
const isSelected = i === this.selectedFlowIndex;
|
|
1505
|
+
const isCurrent = flow.runDir === this.runDir;
|
|
1506
|
+
// Status icon based on flow state
|
|
1507
|
+
let statusIcon = '⚪';
|
|
1508
|
+
if (flow.isAlive) {
|
|
1509
|
+
statusIcon = '🟢';
|
|
1510
|
+
}
|
|
1511
|
+
else if (flow.summary.completed === flow.summary.total && flow.summary.total > 0) {
|
|
1512
|
+
statusIcon = '✅';
|
|
1513
|
+
}
|
|
1514
|
+
else if (flow.summary.failed > 0 || flow.summary.dead > 0) {
|
|
1515
|
+
statusIcon = '🔴';
|
|
1516
|
+
}
|
|
1517
|
+
// Lanes summary
|
|
1518
|
+
const lanesSummary = [
|
|
1519
|
+
flow.summary.running > 0 ? `${UI.COLORS.cyan}${flow.summary.running}R${UI.COLORS.reset}` : '',
|
|
1520
|
+
flow.summary.completed > 0 ? `${UI.COLORS.green}${flow.summary.completed}C${UI.COLORS.reset}` : '',
|
|
1521
|
+
flow.summary.failed > 0 ? `${UI.COLORS.red}${flow.summary.failed}F${UI.COLORS.reset}` : '',
|
|
1522
|
+
flow.summary.dead > 0 ? `${UI.COLORS.yellow}${flow.summary.dead}D${UI.COLORS.reset}` : '',
|
|
1523
|
+
].filter(Boolean).join('/') || '0';
|
|
1524
|
+
// Progress bar
|
|
1525
|
+
const total = flow.summary.total || 1;
|
|
1526
|
+
const completed = flow.summary.completed;
|
|
1527
|
+
const ratio = completed / total;
|
|
1528
|
+
const barWidth = 12;
|
|
1529
|
+
const filled = Math.round(ratio * barWidth);
|
|
1530
|
+
const progressBar = `${UI.COLORS.green}${'█'.repeat(filled)}${UI.COLORS.reset}${UI.COLORS.gray}${'░'.repeat(barWidth - filled)}${UI.COLORS.reset}`;
|
|
1531
|
+
const pct = `${Math.round(ratio * 100)}%`;
|
|
1532
|
+
// Display
|
|
1533
|
+
const prefix = isSelected ? ` ${UI.COLORS.cyan}▶${UI.COLORS.reset} ` : ' ';
|
|
1534
|
+
const currentTag = isCurrent ? ` ${UI.COLORS.cyan}●${UI.COLORS.reset}` : '';
|
|
1535
|
+
const bg = isSelected ? UI.COLORS.bgGray : '';
|
|
1536
|
+
const resetBg = isSelected ? UI.COLORS.reset : '';
|
|
1537
|
+
// Truncate run ID if needed
|
|
1538
|
+
const runIdDisplay = flow.runId.length > 30 ? flow.runId.substring(0, 27) + '...' : flow.runId.padEnd(30);
|
|
1539
|
+
process.stdout.write(`${bg}${prefix}${statusIcon} ${runIdDisplay} ${lanesSummary.padEnd(12 + 30)} ${progressBar} ${pct}${currentTag}${resetBg}\n`);
|
|
1540
|
+
}
|
|
1541
|
+
if (this.allFlows.length > maxVisible) {
|
|
1542
|
+
process.stdout.write(`\n ${UI.COLORS.dim}(${this.allFlows.length - maxVisible} more flows, scroll to see)${UI.COLORS.reset}\n`);
|
|
1543
|
+
}
|
|
1544
|
+
this.renderFooter([
|
|
1545
|
+
'[↑↓] Select', '[→/Enter] Switch', '[D] Delete', '[R] Refresh', '[M/Esc] Back', '[Q] Quit'
|
|
1546
|
+
]);
|
|
728
1547
|
}
|
|
729
1548
|
listLanesWithDeps(runDir) {
|
|
730
1549
|
const lanesDir = (0, path_1.safeJoin)(runDir, 'lanes');
|
|
@@ -833,7 +1652,6 @@ async function monitor(args) {
|
|
|
833
1652
|
printHelp();
|
|
834
1653
|
return;
|
|
835
1654
|
}
|
|
836
|
-
const watchIdx = args.indexOf('--watch');
|
|
837
1655
|
const intervalIdx = args.indexOf('--interval');
|
|
838
1656
|
const interval = intervalIdx >= 0 ? parseInt(args[intervalIdx + 1] || '2') || 2 : 2;
|
|
839
1657
|
const runDirArg = args.find(arg => !arg.startsWith('--') && args.indexOf(arg) !== intervalIdx + 1);
|