agileflow 2.90.2 → 2.90.4
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 +5 -0
- package/package.json +1 -1
- package/scripts/tui/Dashboard.js +277 -0
- package/scripts/tui/index.js +38 -3
- package/scripts/tui/panels/OutputPanel.js +0 -2
- package/scripts/tui/panels/SessionPanel.js +2 -2
- package/scripts/tui/simple-tui.js +140 -81
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AgileFlow TUI Dashboard
|
|
5
|
+
*
|
|
6
|
+
* Main layout component using Ink's Flexbox for responsive design.
|
|
7
|
+
* Adapts to terminal size automatically.
|
|
8
|
+
*
|
|
9
|
+
* Layout:
|
|
10
|
+
* ┌────────────────────────────────────────────────────────────────┐
|
|
11
|
+
* │ AgileFlow TUI v2.x │
|
|
12
|
+
* ├──────────────────┬─────────────────────────────────────────────┤
|
|
13
|
+
* │ SESSIONS │ AGENT OUTPUT │
|
|
14
|
+
* │ ──────── │ ──────────── │
|
|
15
|
+
* │ ▶ Session 1 │ [10:30] [api] Running tests... │
|
|
16
|
+
* │ Branch: main │ [10:31] [api] ✓ 47 tests passed │
|
|
17
|
+
* │ Story: US-0115 │ [10:32] [ci] Building project... │
|
|
18
|
+
* │ │ │
|
|
19
|
+
* │ Session 2 │ │
|
|
20
|
+
* │ Branch: feat │ │
|
|
21
|
+
* ├──────────────────┴─────────────────────────────────────────────┤
|
|
22
|
+
* │ Loop: EP-0020 US-0115 | ████████░░░░ 65% | Q S P R T 1-9 │
|
|
23
|
+
* └────────────────────────────────────────────────────────────────┘
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const React = require('react');
|
|
27
|
+
const { Box, Text, useInput, useApp, useStdout } = require('ink');
|
|
28
|
+
const { SessionPanel } = require('./panels/SessionPanel');
|
|
29
|
+
const { OutputPanel } = require('./panels/OutputPanel');
|
|
30
|
+
const { TracePanel } = require('./panels/TracePanel');
|
|
31
|
+
const { getLoopStatus } = require('./lib/loopControl');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Version display
|
|
35
|
+
*/
|
|
36
|
+
const VERSION = '2.90.2';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Status bar showing loop progress and key bindings
|
|
40
|
+
*/
|
|
41
|
+
function StatusBar({ loopStatus, showTrace }) {
|
|
42
|
+
const progress = loopStatus?.progress || 0;
|
|
43
|
+
const epic = loopStatus?.epic || '';
|
|
44
|
+
const story = loopStatus?.story || '';
|
|
45
|
+
const state = loopStatus?.state || 'idle';
|
|
46
|
+
|
|
47
|
+
// Build progress bar
|
|
48
|
+
const barWidth = 15;
|
|
49
|
+
const filled = Math.round((progress / 100) * barWidth);
|
|
50
|
+
const empty = barWidth - filled;
|
|
51
|
+
const progressBar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
52
|
+
|
|
53
|
+
// Progress color
|
|
54
|
+
let progressColor = 'red';
|
|
55
|
+
if (progress >= 80) progressColor = 'green';
|
|
56
|
+
else if (progress >= 50) progressColor = 'yellow';
|
|
57
|
+
else if (progress >= 20) progressColor = 'cyan';
|
|
58
|
+
|
|
59
|
+
// State indicator
|
|
60
|
+
const stateColors = {
|
|
61
|
+
running: 'green',
|
|
62
|
+
paused: 'yellow',
|
|
63
|
+
idle: 'gray',
|
|
64
|
+
error: 'red',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return React.createElement(
|
|
68
|
+
Box,
|
|
69
|
+
{
|
|
70
|
+
borderStyle: 'single',
|
|
71
|
+
borderColor: 'gray',
|
|
72
|
+
paddingX: 1,
|
|
73
|
+
justifyContent: 'space-between',
|
|
74
|
+
flexShrink: 0,
|
|
75
|
+
},
|
|
76
|
+
// Left: Loop info
|
|
77
|
+
React.createElement(
|
|
78
|
+
Box,
|
|
79
|
+
{ flexDirection: 'row' },
|
|
80
|
+
React.createElement(Text, { color: stateColors[state] || 'gray' }, '● '),
|
|
81
|
+
epic && React.createElement(Text, { bold: true }, `${epic} `),
|
|
82
|
+
story && React.createElement(Text, { color: 'yellow' }, story),
|
|
83
|
+
!epic && !story && React.createElement(Text, { dimColor: true }, 'No active loop')
|
|
84
|
+
),
|
|
85
|
+
// Center: Progress bar
|
|
86
|
+
React.createElement(
|
|
87
|
+
Box,
|
|
88
|
+
{ flexDirection: 'row' },
|
|
89
|
+
React.createElement(Text, { color: progressColor }, progressBar),
|
|
90
|
+
React.createElement(Text, null, ` ${progress}%`)
|
|
91
|
+
),
|
|
92
|
+
// Right: Key bindings
|
|
93
|
+
React.createElement(
|
|
94
|
+
Box,
|
|
95
|
+
{ flexDirection: 'row' },
|
|
96
|
+
React.createElement(Text, { dimColor: true }, '['),
|
|
97
|
+
React.createElement(Text, { color: 'cyan' }, 'Q'),
|
|
98
|
+
React.createElement(Text, { dimColor: true }, ']uit ['),
|
|
99
|
+
React.createElement(Text, { color: 'green' }, 'S'),
|
|
100
|
+
React.createElement(Text, { dimColor: true }, ']tart ['),
|
|
101
|
+
React.createElement(Text, { color: 'yellow' }, 'P'),
|
|
102
|
+
React.createElement(Text, { dimColor: true }, ']ause ['),
|
|
103
|
+
React.createElement(Text, { color: 'cyan' }, 'R'),
|
|
104
|
+
React.createElement(Text, { dimColor: true }, ']esume ['),
|
|
105
|
+
React.createElement(Text, { color: showTrace ? 'green' : 'gray' }, 'T'),
|
|
106
|
+
React.createElement(Text, { dimColor: true }, ']race'),
|
|
107
|
+
React.createElement(Text, { dimColor: true }, ' | '),
|
|
108
|
+
React.createElement(Text, { color: 'blue' }, `v${VERSION}`)
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Main Dashboard component
|
|
115
|
+
*/
|
|
116
|
+
function Dashboard({ onAction = null }) {
|
|
117
|
+
const { exit } = useApp();
|
|
118
|
+
const { stdout } = useStdout();
|
|
119
|
+
|
|
120
|
+
// Terminal dimensions
|
|
121
|
+
const [dimensions, setDimensions] = React.useState({
|
|
122
|
+
width: stdout?.columns || 80,
|
|
123
|
+
height: stdout?.rows || 24,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// UI state
|
|
127
|
+
const [showTrace, setShowTrace] = React.useState(false);
|
|
128
|
+
const [loopStatus, setLoopStatus] = React.useState(null);
|
|
129
|
+
const [selectedSession, setSelectedSession] = React.useState(1);
|
|
130
|
+
|
|
131
|
+
// Handle terminal resize
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
const handleResize = () => {
|
|
134
|
+
setDimensions({
|
|
135
|
+
width: stdout?.columns || 80,
|
|
136
|
+
height: stdout?.rows || 24,
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
stdout?.on('resize', handleResize);
|
|
141
|
+
return () => stdout?.off('resize', handleResize);
|
|
142
|
+
}, [stdout]);
|
|
143
|
+
|
|
144
|
+
// Load loop status periodically
|
|
145
|
+
React.useEffect(() => {
|
|
146
|
+
const loadStatus = () => {
|
|
147
|
+
try {
|
|
148
|
+
const status = getLoopStatus();
|
|
149
|
+
setLoopStatus(status);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
// Silently ignore errors
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
loadStatus();
|
|
156
|
+
const interval = setInterval(loadStatus, 2000);
|
|
157
|
+
return () => clearInterval(interval);
|
|
158
|
+
}, []);
|
|
159
|
+
|
|
160
|
+
// Handle keyboard input
|
|
161
|
+
useInput((input, key) => {
|
|
162
|
+
const lowerInput = input.toLowerCase();
|
|
163
|
+
|
|
164
|
+
// Quit
|
|
165
|
+
if (lowerInput === 'q' || (key.ctrl && lowerInput === 'c')) {
|
|
166
|
+
exit();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Toggle trace
|
|
171
|
+
if (lowerInput === 't') {
|
|
172
|
+
setShowTrace(prev => !prev);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Session selection (1-9)
|
|
177
|
+
if (input >= '1' && input <= '9') {
|
|
178
|
+
setSelectedSession(parseInt(input, 10));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Forward actions to parent
|
|
183
|
+
if (onAction) {
|
|
184
|
+
if (lowerInput === 's') onAction({ action: 'start' });
|
|
185
|
+
if (lowerInput === 'p') onAction({ action: 'pause' });
|
|
186
|
+
if (lowerInput === 'r') onAction({ action: 'resume' });
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Calculate panel widths based on terminal size
|
|
191
|
+
const isNarrow = dimensions.width < 100;
|
|
192
|
+
const sessionPanelWidth = isNarrow ? 25 : 30;
|
|
193
|
+
|
|
194
|
+
// Main content height (subtract header + status bar)
|
|
195
|
+
const contentHeight = Math.max(10, dimensions.height - 6);
|
|
196
|
+
|
|
197
|
+
return React.createElement(
|
|
198
|
+
Box,
|
|
199
|
+
{
|
|
200
|
+
flexDirection: 'column',
|
|
201
|
+
width: dimensions.width,
|
|
202
|
+
height: dimensions.height,
|
|
203
|
+
},
|
|
204
|
+
// Header
|
|
205
|
+
React.createElement(
|
|
206
|
+
Box,
|
|
207
|
+
{
|
|
208
|
+
borderStyle: 'double',
|
|
209
|
+
borderColor: 'cyan',
|
|
210
|
+
justifyContent: 'center',
|
|
211
|
+
paddingX: 1,
|
|
212
|
+
flexShrink: 0,
|
|
213
|
+
},
|
|
214
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'AgileFlow TUI'),
|
|
215
|
+
React.createElement(Text, { dimColor: true }, ` v${VERSION}`)
|
|
216
|
+
),
|
|
217
|
+
|
|
218
|
+
// Main content area (sessions + output + optional trace)
|
|
219
|
+
React.createElement(
|
|
220
|
+
Box,
|
|
221
|
+
{
|
|
222
|
+
flexDirection: 'row',
|
|
223
|
+
flexGrow: 1,
|
|
224
|
+
height: contentHeight,
|
|
225
|
+
},
|
|
226
|
+
// Session panel (left sidebar)
|
|
227
|
+
React.createElement(
|
|
228
|
+
Box,
|
|
229
|
+
{
|
|
230
|
+
width: sessionPanelWidth,
|
|
231
|
+
flexShrink: 0,
|
|
232
|
+
borderStyle: 'single',
|
|
233
|
+
borderColor: 'gray',
|
|
234
|
+
flexDirection: 'column',
|
|
235
|
+
overflow: 'hidden',
|
|
236
|
+
},
|
|
237
|
+
React.createElement(SessionPanel, { refreshInterval: 3000 })
|
|
238
|
+
),
|
|
239
|
+
|
|
240
|
+
// Output panel (center, grows to fill)
|
|
241
|
+
React.createElement(
|
|
242
|
+
Box,
|
|
243
|
+
{
|
|
244
|
+
flexGrow: 1,
|
|
245
|
+
flexDirection: 'column',
|
|
246
|
+
borderStyle: 'single',
|
|
247
|
+
borderColor: 'green',
|
|
248
|
+
overflow: 'hidden',
|
|
249
|
+
},
|
|
250
|
+
React.createElement(OutputPanel, {
|
|
251
|
+
maxMessages: 50,
|
|
252
|
+
showTimestamp: !isNarrow,
|
|
253
|
+
})
|
|
254
|
+
),
|
|
255
|
+
|
|
256
|
+
// Trace panel (right, optional)
|
|
257
|
+
showTrace &&
|
|
258
|
+
React.createElement(
|
|
259
|
+
Box,
|
|
260
|
+
{
|
|
261
|
+
width: isNarrow ? 25 : 35,
|
|
262
|
+
flexShrink: 0,
|
|
263
|
+
borderStyle: 'single',
|
|
264
|
+
borderColor: 'magenta',
|
|
265
|
+
flexDirection: 'column',
|
|
266
|
+
overflow: 'hidden',
|
|
267
|
+
},
|
|
268
|
+
React.createElement(TracePanel, null)
|
|
269
|
+
)
|
|
270
|
+
),
|
|
271
|
+
|
|
272
|
+
// Status bar (bottom)
|
|
273
|
+
React.createElement(StatusBar, { loopStatus, showTrace })
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
module.exports = { Dashboard, StatusBar };
|
package/scripts/tui/index.js
CHANGED
|
@@ -20,12 +20,47 @@
|
|
|
20
20
|
* 1-9 - Switch session focus
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
|
|
23
|
+
// Ink-based TUI is disabled due to React version conflicts in monorepo
|
|
24
|
+
// The simple-tui provides responsive layouts and works reliably
|
|
25
|
+
const useInk = false;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Main entry point
|
|
29
|
+
*/
|
|
30
|
+
async function main() {
|
|
31
|
+
if (useInk) {
|
|
32
|
+
// Use the React/Ink-based Dashboard for modern terminals
|
|
33
|
+
const React = require('react');
|
|
34
|
+
const { render } = require('ink');
|
|
35
|
+
const { Dashboard } = require('./Dashboard');
|
|
36
|
+
|
|
37
|
+
// Handle actions from the dashboard
|
|
38
|
+
const handleAction = action => {
|
|
39
|
+
// TODO: Implement loop control actions
|
|
40
|
+
// console.log('Action:', action);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Render the dashboard
|
|
44
|
+
const { waitUntilExit } = render(
|
|
45
|
+
React.createElement(Dashboard, { onAction: handleAction })
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Wait for exit
|
|
49
|
+
await waitUntilExit();
|
|
50
|
+
} else {
|
|
51
|
+
// Fallback to simple TUI (pure Node.js, no React)
|
|
52
|
+
console.log('React/Ink not available, using simple TUI...');
|
|
53
|
+
const { main: simpleTuiMain } = require('./simple-tui');
|
|
54
|
+
simpleTuiMain();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
25
57
|
|
|
26
58
|
// Run if executed directly
|
|
27
59
|
if (require.main === module) {
|
|
28
|
-
main()
|
|
60
|
+
main().catch(err => {
|
|
61
|
+
console.error('TUI Error:', err.message);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
29
64
|
}
|
|
30
65
|
|
|
31
66
|
module.exports = { main };
|
|
@@ -134,7 +134,7 @@ function SessionPanel({ refreshInterval = 5000 }) {
|
|
|
134
134
|
if (sessions.length === 0) {
|
|
135
135
|
return React.createElement(
|
|
136
136
|
Box,
|
|
137
|
-
{ flexDirection: 'column',
|
|
137
|
+
{ flexDirection: 'column', padding: 1 },
|
|
138
138
|
React.createElement(Text, { bold: true, color: 'cyan' }, 'SESSIONS'),
|
|
139
139
|
React.createElement(Text, { dimColor: true, italic: true }, 'No active sessions')
|
|
140
140
|
);
|
|
@@ -142,7 +142,7 @@ function SessionPanel({ refreshInterval = 5000 }) {
|
|
|
142
142
|
|
|
143
143
|
return React.createElement(
|
|
144
144
|
Box,
|
|
145
|
-
{ flexDirection: 'column',
|
|
145
|
+
{ flexDirection: 'column', padding: 1 },
|
|
146
146
|
// Header
|
|
147
147
|
React.createElement(
|
|
148
148
|
Box,
|
|
@@ -293,116 +293,175 @@ class SimpleTUI {
|
|
|
293
293
|
// Clear screen
|
|
294
294
|
output.push(ANSI.clear + ANSI.home);
|
|
295
295
|
|
|
296
|
-
//
|
|
297
|
-
const
|
|
296
|
+
// Determine layout mode based on terminal width
|
|
297
|
+
const isWide = width >= 100;
|
|
298
|
+
const isNarrow = width < 60;
|
|
299
|
+
|
|
300
|
+
// Header - compact on narrow terminals
|
|
301
|
+
const title = isNarrow ? ' TUI ' : ' AgileFlow TUI ';
|
|
298
302
|
const headerPadding = Math.floor((width - title.length) / 2);
|
|
299
303
|
output.push(
|
|
300
304
|
`${ANSI.bgCyan}${ANSI.black}${'═'.repeat(headerPadding)}${ANSI.bold}${title}${ANSI.reset}${ANSI.bgCyan}${ANSI.black}${'═'.repeat(width - headerPadding - title.length)}${ANSI.reset}`
|
|
301
305
|
);
|
|
302
|
-
output.push('');
|
|
303
|
-
|
|
304
|
-
// Calculate panel widths
|
|
305
|
-
const leftWidth = Math.floor(width * 0.4);
|
|
306
|
-
const rightWidth = width - leftWidth - 1;
|
|
307
|
-
const panelHeight = height - 6; // Leave room for header and footer
|
|
308
306
|
|
|
309
307
|
// Get data
|
|
310
308
|
const sessions = getSessions();
|
|
311
309
|
const loopStatus = getLoopStatus();
|
|
312
|
-
const agentEvents = getAgentEvents(8);
|
|
310
|
+
const agentEvents = getAgentEvents(isNarrow ? 4 : 8);
|
|
311
|
+
|
|
312
|
+
if (isWide) {
|
|
313
|
+
// Side-by-side layout for wide terminals
|
|
314
|
+
this.renderSideBySide(output, width, height, sessions, loopStatus, agentEvents);
|
|
315
|
+
} else {
|
|
316
|
+
// Stacked layout for normal/narrow terminals
|
|
317
|
+
this.renderStacked(output, width, height, sessions, loopStatus, agentEvents, isNarrow);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Output everything
|
|
321
|
+
process.stdout.write(output.join('\n'));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
renderSideBySide(output, width, height, sessions, loopStatus, agentEvents) {
|
|
325
|
+
// Calculate panel widths: 30% sessions, 70% output
|
|
326
|
+
const leftWidth = Math.max(25, Math.floor(width * 0.3));
|
|
327
|
+
const rightWidth = width - leftWidth - 3;
|
|
328
|
+
const panelHeight = Math.max(6, height - 5);
|
|
329
|
+
|
|
330
|
+
// Build both panels line by line
|
|
331
|
+
const leftLines = this.buildSessionPanel(leftWidth, panelHeight, sessions);
|
|
332
|
+
const rightLines = this.buildOutputPanel(rightWidth, panelHeight, agentEvents);
|
|
333
|
+
|
|
334
|
+
// Combine side by side
|
|
335
|
+
for (let i = 0; i < panelHeight; i++) {
|
|
336
|
+
const left = leftLines[i] || pad('', leftWidth);
|
|
337
|
+
const right = rightLines[i] || pad('', rightWidth);
|
|
338
|
+
output.push(`${left} ${right}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Status bar and footer
|
|
342
|
+
this.renderStatusBar(output, width, loopStatus);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
renderStacked(output, width, height, sessions, loopStatus, agentEvents, isNarrow) {
|
|
346
|
+
// Calculate heights
|
|
347
|
+
const sessionHeight = isNarrow ? 4 : Math.min(6, sessions.length * 3 + 2);
|
|
348
|
+
const outputHeight = Math.max(4, height - sessionHeight - 5);
|
|
349
|
+
|
|
350
|
+
// Build panels
|
|
351
|
+
const sessionLines = this.buildSessionPanel(width - 2, sessionHeight, sessions);
|
|
352
|
+
const outputLines = this.buildOutputPanel(width - 2, outputHeight, agentEvents);
|
|
353
|
+
|
|
354
|
+
// Add session panel
|
|
355
|
+
for (const line of sessionLines) {
|
|
356
|
+
output.push(line);
|
|
357
|
+
}
|
|
358
|
+
output.push('');
|
|
359
|
+
|
|
360
|
+
// Add output panel
|
|
361
|
+
for (const line of outputLines) {
|
|
362
|
+
output.push(line);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Status bar and footer
|
|
366
|
+
this.renderStatusBar(output, width, loopStatus);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
buildSessionPanel(panelWidth, panelHeight, sessions) {
|
|
370
|
+
const lines = [];
|
|
371
|
+
const innerWidth = panelWidth - 2;
|
|
313
372
|
|
|
314
|
-
//
|
|
315
|
-
|
|
373
|
+
// Header
|
|
374
|
+
const header = `─ SESSIONS ─`;
|
|
375
|
+
lines.push(`${ANSI.cyan}┌${header}${'─'.repeat(Math.max(0, innerWidth - header.length))}┐${ANSI.reset}`);
|
|
316
376
|
|
|
317
377
|
if (sessions.length === 0) {
|
|
318
|
-
|
|
319
|
-
`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}No active sessions${ANSI.reset}${' '.repeat(leftWidth - 21)}${ANSI.cyan}│${ANSI.reset}`
|
|
320
|
-
);
|
|
378
|
+
lines.push(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${pad('No active sessions', innerWidth)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
|
|
321
379
|
} else {
|
|
322
|
-
|
|
323
|
-
|
|
380
|
+
const maxSessions = Math.floor((panelHeight - 2) / 2);
|
|
381
|
+
for (const session of sessions.slice(0, maxSessions)) {
|
|
382
|
+
const indicator = session.current ? `${ANSI.green}▶` : ' ';
|
|
324
383
|
const name = `Session ${session.id}${session.is_main ? ' [main]' : ''}`;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
`${ANSI.cyan}│${ANSI.reset} ${indicator} ${ANSI.bold}${pad(name, leftWidth - 6)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
|
|
330
|
-
);
|
|
331
|
-
output.push(
|
|
332
|
-
`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}Branch:${ANSI.reset} ${ANSI.cyan}${pad(branch, leftWidth - 12)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
|
|
333
|
-
);
|
|
334
|
-
output.push(
|
|
335
|
-
`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}Story:${ANSI.reset} ${ANSI.yellow}${pad(story, leftWidth - 12)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`
|
|
336
|
-
);
|
|
384
|
+
lines.push(`${ANSI.cyan}│${ANSI.reset}${indicator} ${ANSI.bold}${pad(name, innerWidth - 2)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
|
|
385
|
+
|
|
386
|
+
const info = `${session.branch || '?'}${session.story ? ' / ' + session.story : ''}`;
|
|
387
|
+
lines.push(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${pad(info, innerWidth - 1)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
|
|
337
388
|
}
|
|
338
389
|
}
|
|
339
390
|
|
|
340
|
-
// Fill remaining
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
for (let i = 0; i < remainingRows; i++) {
|
|
344
|
-
output.push(
|
|
345
|
-
`${ANSI.cyan}│${ANSI.reset}${' '.repeat(leftWidth - 2)}${ANSI.cyan}│${ANSI.reset}`
|
|
346
|
-
);
|
|
391
|
+
// Fill remaining rows
|
|
392
|
+
while (lines.length < panelHeight - 1) {
|
|
393
|
+
lines.push(`${ANSI.cyan}│${ANSI.reset}${' '.repeat(innerWidth)}${ANSI.cyan}│${ANSI.reset}`);
|
|
347
394
|
}
|
|
348
395
|
|
|
349
|
-
|
|
396
|
+
// Footer
|
|
397
|
+
lines.push(`${ANSI.cyan}└${'─'.repeat(innerWidth)}┘${ANSI.reset}`);
|
|
350
398
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
output.push('');
|
|
354
|
-
output.push(
|
|
355
|
-
`${ANSI.green}${ANSI.bold}┌─ AGENT OUTPUT ─${'─'.repeat(width - 19)}┐${ANSI.reset}`
|
|
356
|
-
);
|
|
399
|
+
return lines;
|
|
400
|
+
}
|
|
357
401
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
);
|
|
362
|
-
} else {
|
|
363
|
-
// Show recent events
|
|
364
|
-
const allMessages = [
|
|
365
|
-
...agentEvents.map(e => ({
|
|
366
|
-
timestamp: e.timestamp ? new Date(e.timestamp).toLocaleTimeString() : '',
|
|
367
|
-
agent: e.agent || 'unknown',
|
|
368
|
-
message:
|
|
369
|
-
e.message ||
|
|
370
|
-
(e.event === 'iteration' ? `Iteration ${e.iter}` : e.event || JSON.stringify(e)),
|
|
371
|
-
})),
|
|
372
|
-
...this.messages,
|
|
373
|
-
].slice(-8);
|
|
402
|
+
buildOutputPanel(panelWidth, panelHeight, agentEvents) {
|
|
403
|
+
const lines = [];
|
|
404
|
+
const innerWidth = panelWidth - 2;
|
|
374
405
|
|
|
406
|
+
// Header
|
|
407
|
+
const header = `─ OUTPUT ─`;
|
|
408
|
+
lines.push(`${ANSI.green}┌${header}${'─'.repeat(Math.max(0, innerWidth - header.length))}┐${ANSI.reset}`);
|
|
409
|
+
|
|
410
|
+
// Combine events and messages
|
|
411
|
+
const allMessages = [
|
|
412
|
+
...agentEvents.map(e => ({
|
|
413
|
+
time: e.timestamp ? new Date(e.timestamp).toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit' }) : '',
|
|
414
|
+
agent: (e.agent || 'unknown').replace('agileflow-', ''),
|
|
415
|
+
msg: e.message || (e.event === 'iteration' ? `Iter ${e.iter}` : e.event || ''),
|
|
416
|
+
})),
|
|
417
|
+
...this.messages.map(m => ({
|
|
418
|
+
time: m.timestamp || '',
|
|
419
|
+
agent: (m.agent || 'unknown').replace('agileflow-', ''),
|
|
420
|
+
msg: m.message || '',
|
|
421
|
+
})),
|
|
422
|
+
].slice(-(panelHeight - 2));
|
|
423
|
+
|
|
424
|
+
if (allMessages.length === 0) {
|
|
425
|
+
lines.push(`${ANSI.green}│${ANSI.reset} ${ANSI.dim}${pad('Waiting for activity...', innerWidth)}${ANSI.reset}${ANSI.green}│${ANSI.reset}`);
|
|
426
|
+
} else {
|
|
375
427
|
for (const msg of allMessages) {
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
);
|
|
428
|
+
const prefix = `${msg.time} [${msg.agent}] `;
|
|
429
|
+
const maxMsgLen = Math.max(10, innerWidth - prefix.length - 1);
|
|
430
|
+
const truncatedMsg = msg.msg.length > maxMsgLen ? msg.msg.slice(0, maxMsgLen - 2) + '..' : msg.msg;
|
|
431
|
+
const line = `${ANSI.dim}${msg.time}${ANSI.reset} [${ANSI.cyan}${msg.agent}${ANSI.reset}] ${truncatedMsg}`;
|
|
432
|
+
const cleanLen = prefix.length + truncatedMsg.length;
|
|
433
|
+
lines.push(`${ANSI.green}│${ANSI.reset} ${line}${' '.repeat(Math.max(0, innerWidth - cleanLen - 1))}${ANSI.green}│${ANSI.reset}`);
|
|
382
434
|
}
|
|
383
435
|
}
|
|
384
436
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (loopStatus.active) {
|
|
389
|
-
output.push('');
|
|
390
|
-
const statusIcon = loopStatus.paused
|
|
391
|
-
? `${ANSI.yellow}||${ANSI.reset}`
|
|
392
|
-
: `${ANSI.green}>${ANSI.reset}`;
|
|
393
|
-
output.push(
|
|
394
|
-
`${statusIcon} ${ANSI.bold}Loop:${ANSI.reset} ${loopStatus.epic || 'unknown'} | Story: ${loopStatus.currentStory || 'none'} | ${progressBar(loopStatus.iteration, loopStatus.maxIterations, 15)}`
|
|
395
|
-
);
|
|
437
|
+
// Fill remaining rows
|
|
438
|
+
while (lines.length < panelHeight - 1) {
|
|
439
|
+
lines.push(`${ANSI.green}│${ANSI.reset}${' '.repeat(innerWidth)}${ANSI.green}│${ANSI.reset}`);
|
|
396
440
|
}
|
|
397
441
|
|
|
398
|
-
// Footer
|
|
442
|
+
// Footer
|
|
443
|
+
lines.push(`${ANSI.green}└${'─'.repeat(innerWidth)}┘${ANSI.reset}`);
|
|
444
|
+
|
|
445
|
+
return lines;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
renderStatusBar(output, width, loopStatus) {
|
|
399
449
|
output.push('');
|
|
400
|
-
output.push(
|
|
401
|
-
`${ANSI.dim}[Q]uit [S]tart [P]ause [R]esume [T]race [1-9]Sessions ${ANSI.reset}${ANSI.cyan}AgileFlow v2.90.0${ANSI.reset}`
|
|
402
|
-
);
|
|
403
450
|
|
|
404
|
-
//
|
|
405
|
-
|
|
451
|
+
// Loop status
|
|
452
|
+
if (loopStatus.active) {
|
|
453
|
+
const statusIcon = loopStatus.paused ? `${ANSI.yellow}⏸${ANSI.reset}` : `${ANSI.green}▶${ANSI.reset}`;
|
|
454
|
+
const bar = progressBar(loopStatus.iteration, loopStatus.maxIterations, Math.min(15, Math.floor(width / 6)));
|
|
455
|
+
const info = `${loopStatus.epic || ''}${loopStatus.currentStory ? ' / ' + loopStatus.currentStory : ''}`;
|
|
456
|
+
output.push(`${statusIcon} ${ANSI.bold}${info}${ANSI.reset} ${bar}`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Key bindings - compact on narrow terminals
|
|
460
|
+
const keys = width >= 70
|
|
461
|
+
? `${ANSI.dim}[Q]uit [S]tart [P]ause [R]esume [T]race [1-9]Sessions${ANSI.reset}`
|
|
462
|
+
: `${ANSI.dim}Q S P R T 1-9${ANSI.reset}`;
|
|
463
|
+
const version = `${ANSI.cyan}v2.90.3${ANSI.reset}`;
|
|
464
|
+
output.push(`${keys} ${version}`);
|
|
406
465
|
}
|
|
407
466
|
}
|
|
408
467
|
|