agileflow 2.90.1 → 2.90.3
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 +43 -3
- package/scripts/tui/panels/OutputPanel.js +0 -2
- package/scripts/tui/panels/SessionPanel.js +2 -2
- package/scripts/tui/simple-tui.js +144 -55
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,52 @@
|
|
|
20
20
|
* 1-9 - Switch session focus
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
//
|
|
24
|
-
|
|
23
|
+
// Check if we can use Ink (React-based TUI)
|
|
24
|
+
let useInk = true;
|
|
25
|
+
try {
|
|
26
|
+
require('react');
|
|
27
|
+
require('ink');
|
|
28
|
+
} catch (e) {
|
|
29
|
+
useInk = false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Main entry point
|
|
34
|
+
*/
|
|
35
|
+
async function main() {
|
|
36
|
+
if (useInk) {
|
|
37
|
+
// Use the React/Ink-based Dashboard for modern terminals
|
|
38
|
+
const React = require('react');
|
|
39
|
+
const { render } = require('ink');
|
|
40
|
+
const { Dashboard } = require('./Dashboard');
|
|
41
|
+
|
|
42
|
+
// Handle actions from the dashboard
|
|
43
|
+
const handleAction = action => {
|
|
44
|
+
// TODO: Implement loop control actions
|
|
45
|
+
// console.log('Action:', action);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Render the dashboard
|
|
49
|
+
const { waitUntilExit } = render(
|
|
50
|
+
React.createElement(Dashboard, { onAction: handleAction })
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Wait for exit
|
|
54
|
+
await waitUntilExit();
|
|
55
|
+
} else {
|
|
56
|
+
// Fallback to simple TUI (pure Node.js, no React)
|
|
57
|
+
console.log('React/Ink not available, using simple TUI...');
|
|
58
|
+
const { main: simpleTuiMain } = require('./simple-tui');
|
|
59
|
+
simpleTuiMain();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
25
62
|
|
|
26
63
|
// Run if executed directly
|
|
27
64
|
if (require.main === module) {
|
|
28
|
-
main()
|
|
65
|
+
main().catch(err => {
|
|
66
|
+
console.error('TUI Error:', err.message);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
});
|
|
29
69
|
}
|
|
30
70
|
|
|
31
71
|
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,
|
|
@@ -164,7 +164,8 @@ function pad(str, width, align = 'left') {
|
|
|
164
164
|
const s = String(str).slice(0, width);
|
|
165
165
|
const padding = width - s.length;
|
|
166
166
|
if (align === 'right') return ' '.repeat(padding) + s;
|
|
167
|
-
if (align === 'center')
|
|
167
|
+
if (align === 'center')
|
|
168
|
+
return ' '.repeat(Math.floor(padding / 2)) + s + ' '.repeat(Math.ceil(padding / 2));
|
|
168
169
|
return s + ' '.repeat(padding);
|
|
169
170
|
}
|
|
170
171
|
|
|
@@ -292,87 +293,175 @@ class SimpleTUI {
|
|
|
292
293
|
// Clear screen
|
|
293
294
|
output.push(ANSI.clear + ANSI.home);
|
|
294
295
|
|
|
295
|
-
//
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
output.push(`${ANSI.bgCyan}${ANSI.black}${'═'.repeat(headerPadding)}${ANSI.bold}${title}${ANSI.reset}${ANSI.bgCyan}${ANSI.black}${'═'.repeat(width - headerPadding - title.length)}${ANSI.reset}`);
|
|
299
|
-
output.push('');
|
|
296
|
+
// Determine layout mode based on terminal width
|
|
297
|
+
const isWide = width >= 100;
|
|
298
|
+
const isNarrow = width < 60;
|
|
300
299
|
|
|
301
|
-
//
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
|
|
300
|
+
// Header - compact on narrow terminals
|
|
301
|
+
const title = isNarrow ? ' TUI ' : ' AgileFlow TUI ';
|
|
302
|
+
const headerPadding = Math.floor((width - title.length) / 2);
|
|
303
|
+
output.push(
|
|
304
|
+
`${ANSI.bgCyan}${ANSI.black}${'═'.repeat(headerPadding)}${ANSI.bold}${title}${ANSI.reset}${ANSI.bgCyan}${ANSI.black}${'═'.repeat(width - headerPadding - title.length)}${ANSI.reset}`
|
|
305
|
+
);
|
|
305
306
|
|
|
306
307
|
// Get data
|
|
307
308
|
const sessions = getSessions();
|
|
308
309
|
const loopStatus = getLoopStatus();
|
|
309
|
-
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
|
+
}
|
|
310
323
|
|
|
311
|
-
|
|
312
|
-
|
|
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;
|
|
372
|
+
|
|
373
|
+
// Header
|
|
374
|
+
const header = `─ SESSIONS ─`;
|
|
375
|
+
lines.push(`${ANSI.cyan}┌${header}${'─'.repeat(Math.max(0, innerWidth - header.length))}┐${ANSI.reset}`);
|
|
313
376
|
|
|
314
377
|
if (sessions.length === 0) {
|
|
315
|
-
|
|
378
|
+
lines.push(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${pad('No active sessions', innerWidth)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
|
|
316
379
|
} else {
|
|
317
|
-
|
|
318
|
-
|
|
380
|
+
const maxSessions = Math.floor((panelHeight - 2) / 2);
|
|
381
|
+
for (const session of sessions.slice(0, maxSessions)) {
|
|
382
|
+
const indicator = session.current ? `${ANSI.green}▶` : ' ';
|
|
319
383
|
const name = `Session ${session.id}${session.is_main ? ' [main]' : ''}`;
|
|
320
|
-
|
|
321
|
-
const story = session.story || 'none';
|
|
384
|
+
lines.push(`${ANSI.cyan}│${ANSI.reset}${indicator} ${ANSI.bold}${pad(name, innerWidth - 2)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
|
|
322
385
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
output.push(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}Story:${ANSI.reset} ${ANSI.yellow}${pad(story, leftWidth - 12)}${ANSI.reset}${ANSI.cyan}│${ANSI.reset}`);
|
|
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}`);
|
|
326
388
|
}
|
|
327
389
|
}
|
|
328
390
|
|
|
329
|
-
// Fill remaining
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
for (let i = 0; i < remainingRows; i++) {
|
|
333
|
-
output.push(`${ANSI.cyan}│${ANSI.reset}${' '.repeat(leftWidth - 2)}${ANSI.cyan}│${ANSI.reset}`);
|
|
391
|
+
// Fill remaining rows
|
|
392
|
+
while (lines.length < panelHeight - 1) {
|
|
393
|
+
lines.push(`${ANSI.cyan}│${ANSI.reset}${' '.repeat(innerWidth)}${ANSI.cyan}│${ANSI.reset}`);
|
|
334
394
|
}
|
|
335
395
|
|
|
336
|
-
|
|
396
|
+
// Footer
|
|
397
|
+
lines.push(`${ANSI.cyan}└${'─'.repeat(innerWidth)}┘${ANSI.reset}`);
|
|
337
398
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
output.push('');
|
|
341
|
-
output.push(`${ANSI.green}${ANSI.bold}┌─ AGENT OUTPUT ─${'─'.repeat(width - 19)}┐${ANSI.reset}`);
|
|
399
|
+
return lines;
|
|
400
|
+
}
|
|
342
401
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
// Show recent events
|
|
347
|
-
const allMessages = [...agentEvents.map(e => ({
|
|
348
|
-
timestamp: e.timestamp ? new Date(e.timestamp).toLocaleTimeString() : '',
|
|
349
|
-
agent: e.agent || 'unknown',
|
|
350
|
-
message: e.message || (e.event === 'iteration' ? `Iteration ${e.iter}` : e.event || JSON.stringify(e))
|
|
351
|
-
})), ...this.messages].slice(-8);
|
|
402
|
+
buildOutputPanel(panelWidth, panelHeight, agentEvents) {
|
|
403
|
+
const lines = [];
|
|
404
|
+
const innerWidth = panelWidth - 2;
|
|
352
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 {
|
|
353
427
|
for (const msg of allMessages) {
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
|
|
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}`);
|
|
358
434
|
}
|
|
359
435
|
}
|
|
360
436
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
if (loopStatus.active) {
|
|
365
|
-
output.push('');
|
|
366
|
-
const statusIcon = loopStatus.paused ? `${ANSI.yellow}||${ANSI.reset}` : `${ANSI.green}>${ANSI.reset}`;
|
|
367
|
-
output.push(`${statusIcon} ${ANSI.bold}Loop:${ANSI.reset} ${loopStatus.epic || 'unknown'} | Story: ${loopStatus.currentStory || 'none'} | ${progressBar(loopStatus.iteration, loopStatus.maxIterations, 15)}`);
|
|
437
|
+
// Fill remaining rows
|
|
438
|
+
while (lines.length < panelHeight - 1) {
|
|
439
|
+
lines.push(`${ANSI.green}│${ANSI.reset}${' '.repeat(innerWidth)}${ANSI.green}│${ANSI.reset}`);
|
|
368
440
|
}
|
|
369
441
|
|
|
370
|
-
// Footer
|
|
442
|
+
// Footer
|
|
443
|
+
lines.push(`${ANSI.green}└${'─'.repeat(innerWidth)}┘${ANSI.reset}`);
|
|
444
|
+
|
|
445
|
+
return lines;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
renderStatusBar(output, width, loopStatus) {
|
|
371
449
|
output.push('');
|
|
372
|
-
output.push(`${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}`);
|
|
373
450
|
|
|
374
|
-
//
|
|
375
|
-
|
|
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}`);
|
|
376
465
|
}
|
|
377
466
|
}
|
|
378
467
|
|