agileflow 2.90.0 → 2.90.1
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/README.md +3 -3
- package/lib/smart-json-file.js +33 -10
- package/lib/table-formatter.js +28 -13
- package/package.json +3 -4
- package/scripts/check-update.js +2 -1
- package/scripts/lib/sessionRegistry.js +4 -8
- package/scripts/tui/App.js +33 -58
- package/scripts/tui/index.js +2 -46
- package/scripts/tui/lib/crashRecovery.js +16 -14
- package/scripts/tui/lib/eventStream.js +8 -15
- package/scripts/tui/lib/keyboard.js +16 -7
- package/scripts/tui/lib/loopControl.js +9 -9
- package/scripts/tui/panels/OutputPanel.js +25 -61
- package/scripts/tui/panels/SessionPanel.js +4 -12
- package/scripts/tui/panels/TracePanel.js +27 -62
- package/scripts/tui/simple-tui.js +390 -0
- package/tools/cli/commands/config.js +0 -1
- package/tools/cli/commands/doctor.js +18 -9
- package/tools/cli/commands/status.js +14 -8
- package/tools/cli/commands/tui.js +59 -0
- package/tools/cli/commands/uninstall.js +4 -2
- package/tools/cli/lib/command-context.js +9 -1
- package/tools/cli/lib/npm-utils.js +2 -1
- package/src/core/commands/tui.md +0 -91
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/agileflow)
|
|
6
|
-
[](docs/04-architecture/commands.md)
|
|
7
7
|
[](docs/04-architecture/subagents.md)
|
|
8
8
|
[](docs/04-architecture/skills.md)
|
|
9
9
|
|
|
@@ -65,7 +65,7 @@ AgileFlow combines three proven methodologies:
|
|
|
65
65
|
|
|
66
66
|
| Component | Count | Description |
|
|
67
67
|
|-----------|-------|-------------|
|
|
68
|
-
| [Commands](docs/04-architecture/commands.md) |
|
|
68
|
+
| [Commands](docs/04-architecture/commands.md) | 72 | Slash commands for agile workflows |
|
|
69
69
|
| [Agents/Experts](docs/04-architecture/subagents.md) | 29 | Specialized agents with self-improving knowledge bases |
|
|
70
70
|
| [Skills](docs/04-architecture/skills.md) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
|
|
71
71
|
|
|
@@ -76,7 +76,7 @@ AgileFlow combines three proven methodologies:
|
|
|
76
76
|
Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
|
|
77
77
|
|
|
78
78
|
### Reference
|
|
79
|
-
- [Commands](docs/04-architecture/commands.md) - All
|
|
79
|
+
- [Commands](docs/04-architecture/commands.md) - All 72 slash commands
|
|
80
80
|
- [Agents/Experts](docs/04-architecture/subagents.md) - 29 specialized agents with self-improving knowledge
|
|
81
81
|
- [Skills](docs/04-architecture/skills.md) - Dynamic skill generator with MCP integration
|
|
82
82
|
|
package/lib/smart-json-file.js
CHANGED
|
@@ -69,28 +69,40 @@ function checkFilePermissions(mode) {
|
|
|
69
69
|
if (worldWrite) {
|
|
70
70
|
return {
|
|
71
71
|
ok: false,
|
|
72
|
-
warning:
|
|
72
|
+
warning:
|
|
73
|
+
'File is world-writable (mode: ' +
|
|
74
|
+
permissions.toString(8) +
|
|
75
|
+
'). Security risk - others can modify.',
|
|
73
76
|
};
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
if (worldRead) {
|
|
77
80
|
return {
|
|
78
81
|
ok: false,
|
|
79
|
-
warning:
|
|
82
|
+
warning:
|
|
83
|
+
'File is world-readable (mode: ' +
|
|
84
|
+
permissions.toString(8) +
|
|
85
|
+
'). May expose sensitive config.',
|
|
80
86
|
};
|
|
81
87
|
}
|
|
82
88
|
|
|
83
89
|
if (groupWrite) {
|
|
84
90
|
return {
|
|
85
91
|
ok: false,
|
|
86
|
-
warning:
|
|
92
|
+
warning:
|
|
93
|
+
'File is group-writable (mode: ' +
|
|
94
|
+
permissions.toString(8) +
|
|
95
|
+
'). Consider restricting to 0600.',
|
|
87
96
|
};
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
if (groupRead) {
|
|
91
100
|
return {
|
|
92
101
|
ok: false,
|
|
93
|
-
warning:
|
|
102
|
+
warning:
|
|
103
|
+
'File is group-readable (mode: ' +
|
|
104
|
+
permissions.toString(8) +
|
|
105
|
+
'). Consider restricting to 0600.',
|
|
94
106
|
};
|
|
95
107
|
}
|
|
96
108
|
|
|
@@ -113,10 +125,14 @@ function setSecurePermissions(filePath) {
|
|
|
113
125
|
debugLog('setSecurePermissions', { filePath, mode: SECURE_FILE_MODE.toString(8) });
|
|
114
126
|
return { ok: true };
|
|
115
127
|
} catch (err) {
|
|
116
|
-
const error = createTypedError(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
const error = createTypedError(
|
|
129
|
+
`Failed to set secure permissions on ${filePath}: ${err.message}`,
|
|
130
|
+
'EPERM',
|
|
131
|
+
{
|
|
132
|
+
cause: err,
|
|
133
|
+
context: { filePath, mode: SECURE_FILE_MODE },
|
|
134
|
+
}
|
|
135
|
+
);
|
|
120
136
|
return { ok: false, error };
|
|
121
137
|
}
|
|
122
138
|
}
|
|
@@ -536,7 +552,10 @@ class SmartJsonFile {
|
|
|
536
552
|
if (this.secureMode) {
|
|
537
553
|
const permResult = setSecurePermissions(this.filePath);
|
|
538
554
|
if (!permResult.ok) {
|
|
539
|
-
debugLog('writeSync', {
|
|
555
|
+
debugLog('writeSync', {
|
|
556
|
+
status: 'warning',
|
|
557
|
+
security: 'failed to set secure permissions',
|
|
558
|
+
});
|
|
540
559
|
}
|
|
541
560
|
}
|
|
542
561
|
|
|
@@ -616,7 +635,11 @@ function cleanupTempFiles(directory, options = {}) {
|
|
|
616
635
|
}
|
|
617
636
|
|
|
618
637
|
cleaned.push(filePath);
|
|
619
|
-
debugLog('cleanupTempFiles', {
|
|
638
|
+
debugLog('cleanupTempFiles', {
|
|
639
|
+
action: dryRun ? 'would delete' : 'deleted',
|
|
640
|
+
filePath,
|
|
641
|
+
ageHours: Math.round(age / 3600000),
|
|
642
|
+
});
|
|
620
643
|
} catch (err) {
|
|
621
644
|
errors.push(`${filePath}: ${err.message}`);
|
|
622
645
|
debugLog('cleanupTempFiles', { action: 'error', filePath, error: err.message });
|
package/lib/table-formatter.js
CHANGED
|
@@ -211,7 +211,9 @@ function formatTable(headers, rows, options = {}) {
|
|
|
211
211
|
lines.push(indent + c.bold + headerLine + c.reset);
|
|
212
212
|
|
|
213
213
|
for (const row of stringRows) {
|
|
214
|
-
const rowLine = row
|
|
214
|
+
const rowLine = row
|
|
215
|
+
.map((cell, i) => padString(cell, colWidths[i], align[i] || 'left'))
|
|
216
|
+
.join(' ');
|
|
215
217
|
lines.push(indent + rowLine);
|
|
216
218
|
}
|
|
217
219
|
} else {
|
|
@@ -228,9 +230,7 @@ function formatTable(headers, rows, options = {}) {
|
|
|
228
230
|
|
|
229
231
|
// Top border
|
|
230
232
|
const topBorder =
|
|
231
|
-
box.topLeft +
|
|
232
|
-
paddedWidths.map(w => box.horizontal.repeat(w)).join(box.topT) +
|
|
233
|
-
box.topRight;
|
|
233
|
+
box.topLeft + paddedWidths.map(w => box.horizontal.repeat(w)).join(box.topT) + box.topRight;
|
|
234
234
|
lines.push(indent + c.dim + topBorder + c.reset);
|
|
235
235
|
|
|
236
236
|
// Header row
|
|
@@ -238,13 +238,20 @@ function formatTable(headers, rows, options = {}) {
|
|
|
238
238
|
const padded = padString(h, colWidths[i], align[i] || 'left');
|
|
239
239
|
return ' ' + c.bold + padded + c.reset + ' ';
|
|
240
240
|
});
|
|
241
|
-
lines.push(
|
|
241
|
+
lines.push(
|
|
242
|
+
indent +
|
|
243
|
+
c.dim +
|
|
244
|
+
box.vertical +
|
|
245
|
+
c.reset +
|
|
246
|
+
headerCells.join(c.dim + box.vertical + c.reset) +
|
|
247
|
+
c.dim +
|
|
248
|
+
box.vertical +
|
|
249
|
+
c.reset
|
|
250
|
+
);
|
|
242
251
|
|
|
243
252
|
// Header separator
|
|
244
253
|
const headerSep =
|
|
245
|
-
box.leftT +
|
|
246
|
-
paddedWidths.map(w => box.horizontal.repeat(w)).join(box.cross) +
|
|
247
|
-
box.rightT;
|
|
254
|
+
box.leftT + paddedWidths.map(w => box.horizontal.repeat(w)).join(box.cross) + box.rightT;
|
|
248
255
|
lines.push(indent + c.dim + headerSep + c.reset);
|
|
249
256
|
|
|
250
257
|
// Data rows
|
|
@@ -253,7 +260,16 @@ function formatTable(headers, rows, options = {}) {
|
|
|
253
260
|
const padded = padString(cell, colWidths[i], align[i] || 'left');
|
|
254
261
|
return ' ' + padded + ' ';
|
|
255
262
|
});
|
|
256
|
-
lines.push(
|
|
263
|
+
lines.push(
|
|
264
|
+
indent +
|
|
265
|
+
c.dim +
|
|
266
|
+
box.vertical +
|
|
267
|
+
c.reset +
|
|
268
|
+
cells.join(c.dim + box.vertical + c.reset) +
|
|
269
|
+
c.dim +
|
|
270
|
+
box.vertical +
|
|
271
|
+
c.reset
|
|
272
|
+
);
|
|
257
273
|
}
|
|
258
274
|
|
|
259
275
|
// Bottom border
|
|
@@ -314,9 +330,7 @@ function formatKeyValue(data, options = {}) {
|
|
|
314
330
|
}
|
|
315
331
|
|
|
316
332
|
// Calculate key width for alignment
|
|
317
|
-
const maxKeyWidth = alignValues
|
|
318
|
-
? Math.max(...pairs.map(([k]) => visibleWidth(k)))
|
|
319
|
-
: 0;
|
|
333
|
+
const maxKeyWidth = alignValues ? Math.max(...pairs.map(([k]) => visibleWidth(k))) : 0;
|
|
320
334
|
|
|
321
335
|
const lines = pairs.map(([key, value]) => {
|
|
322
336
|
const paddedKey = alignValues ? padString(key, maxKeyWidth, 'left') : key;
|
|
@@ -453,7 +467,8 @@ function formatHeader(title, options = {}) {
|
|
|
453
467
|
if (!isTTY()) {
|
|
454
468
|
let header = `\n${title}`;
|
|
455
469
|
if (subtitle) header += ` - ${subtitle}`;
|
|
456
|
-
header +=
|
|
470
|
+
header +=
|
|
471
|
+
'\n' + '='.repeat(Math.min(visibleWidth(title) + (subtitle ? subtitle.length + 3 : 0), 40));
|
|
457
472
|
return header;
|
|
458
473
|
}
|
|
459
474
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agileflow",
|
|
3
|
-
"version": "2.90.
|
|
3
|
+
"version": "2.90.1",
|
|
4
4
|
"description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agile",
|
|
@@ -56,12 +56,11 @@
|
|
|
56
56
|
"chalk": "^4.1.2",
|
|
57
57
|
"commander": "^12.1.0",
|
|
58
58
|
"fs-extra": "^11.2.0",
|
|
59
|
-
"ink": "^
|
|
60
|
-
"ink-spinner": "^5.0.0",
|
|
59
|
+
"ink": "^3.2.0",
|
|
61
60
|
"inquirer": "^8.2.6",
|
|
62
61
|
"js-yaml": "^4.1.0",
|
|
63
62
|
"ora": "^5.4.1",
|
|
64
|
-
"react": "^
|
|
63
|
+
"react": "^17.0.2",
|
|
65
64
|
"semver": "^7.6.3"
|
|
66
65
|
},
|
|
67
66
|
"optionalDependencies": {
|
package/scripts/check-update.js
CHANGED
|
@@ -151,7 +151,8 @@ async function fetchLatestVersion() {
|
|
|
151
151
|
suggestion: 'Check network connection. If error persists, try: npm cache clean --force',
|
|
152
152
|
};
|
|
153
153
|
if (err.code === 'CERT_HAS_EXPIRED' || err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
|
|
154
|
-
errorInfo.suggestion =
|
|
154
|
+
errorInfo.suggestion =
|
|
155
|
+
'TLS certificate error - check system time or update CA certificates';
|
|
155
156
|
}
|
|
156
157
|
debugLog('Network error', errorInfo);
|
|
157
158
|
resolve(null);
|
|
@@ -637,14 +637,10 @@ class SessionRegistry extends EventEmitter {
|
|
|
637
637
|
|
|
638
638
|
if (checkMain.status === 0) return 'main';
|
|
639
639
|
|
|
640
|
-
const checkMaster = spawnSync(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
cwd: this.rootDir,
|
|
645
|
-
encoding: 'utf8',
|
|
646
|
-
}
|
|
647
|
-
);
|
|
640
|
+
const checkMaster = spawnSync('git', ['show-ref', '--verify', '--quiet', 'refs/heads/master'], {
|
|
641
|
+
cwd: this.rootDir,
|
|
642
|
+
encoding: 'utf8',
|
|
643
|
+
});
|
|
648
644
|
|
|
649
645
|
if (checkMaster.status === 0) return 'master';
|
|
650
646
|
|
package/scripts/tui/App.js
CHANGED
|
@@ -15,7 +15,7 @@ function App({
|
|
|
15
15
|
title = 'AgileFlow TUI',
|
|
16
16
|
showFooter = true,
|
|
17
17
|
onAction = null,
|
|
18
|
-
bindings = DEFAULT_BINDINGS
|
|
18
|
+
bindings = DEFAULT_BINDINGS,
|
|
19
19
|
}) {
|
|
20
20
|
const { exit } = useApp();
|
|
21
21
|
const [showHelp, setShowHelp] = React.useState(false);
|
|
@@ -35,7 +35,7 @@ function App({
|
|
|
35
35
|
keyboard.on('help', () => setShowHelp(prev => !prev));
|
|
36
36
|
|
|
37
37
|
// Forward all actions to parent
|
|
38
|
-
keyboard.on('action',
|
|
38
|
+
keyboard.on('action', action => {
|
|
39
39
|
setLastAction(action);
|
|
40
40
|
if (onAction) {
|
|
41
41
|
onAction(action);
|
|
@@ -60,7 +60,7 @@ function App({
|
|
|
60
60
|
{
|
|
61
61
|
flexDirection: 'column',
|
|
62
62
|
width: '100%',
|
|
63
|
-
minHeight: 20
|
|
63
|
+
minHeight: 20,
|
|
64
64
|
},
|
|
65
65
|
// Header
|
|
66
66
|
React.createElement(
|
|
@@ -69,18 +69,10 @@ function App({
|
|
|
69
69
|
borderStyle: 'round',
|
|
70
70
|
borderColor: 'cyan',
|
|
71
71
|
paddingX: 1,
|
|
72
|
-
justifyContent: 'center'
|
|
72
|
+
justifyContent: 'center',
|
|
73
73
|
},
|
|
74
|
-
React.createElement(
|
|
75
|
-
|
|
76
|
-
{ bold: true, color: 'cyan' },
|
|
77
|
-
title
|
|
78
|
-
),
|
|
79
|
-
lastAction && React.createElement(
|
|
80
|
-
Text,
|
|
81
|
-
{ dimColor: true },
|
|
82
|
-
` [${lastAction.action}]`
|
|
83
|
-
)
|
|
74
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, title),
|
|
75
|
+
lastAction && React.createElement(Text, { dimColor: true }, ` [${lastAction.action}]`)
|
|
84
76
|
),
|
|
85
77
|
// Main content area
|
|
86
78
|
React.createElement(
|
|
@@ -89,39 +81,34 @@ function App({
|
|
|
89
81
|
flexDirection: 'column',
|
|
90
82
|
flexGrow: 1,
|
|
91
83
|
paddingX: 1,
|
|
92
|
-
paddingY: 1
|
|
84
|
+
paddingY: 1,
|
|
93
85
|
},
|
|
94
|
-
showHelp
|
|
95
|
-
? React.createElement(HelpPanel, { bindings })
|
|
96
|
-
: children
|
|
86
|
+
showHelp ? React.createElement(HelpPanel, { bindings }) : children
|
|
97
87
|
),
|
|
98
88
|
// Footer with key bindings
|
|
99
|
-
showFooter &&
|
|
100
|
-
Box,
|
|
101
|
-
{
|
|
102
|
-
borderStyle: 'single',
|
|
103
|
-
borderColor: 'gray',
|
|
104
|
-
paddingX: 1,
|
|
105
|
-
justifyContent: 'space-between'
|
|
106
|
-
},
|
|
89
|
+
showFooter &&
|
|
107
90
|
React.createElement(
|
|
108
91
|
Box,
|
|
109
|
-
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
92
|
+
{
|
|
93
|
+
borderStyle: 'single',
|
|
94
|
+
borderColor: 'gray',
|
|
95
|
+
paddingX: 1,
|
|
96
|
+
justifyContent: 'space-between',
|
|
97
|
+
},
|
|
98
|
+
React.createElement(
|
|
99
|
+
Box,
|
|
100
|
+
{ flexDirection: 'row' },
|
|
101
|
+
footerBindings.map((binding, i) =>
|
|
102
|
+
React.createElement(
|
|
103
|
+
Text,
|
|
104
|
+
{ key: `binding-${i}`, dimColor: true },
|
|
105
|
+
i > 0 ? ' | ' : '',
|
|
106
|
+
binding
|
|
107
|
+
)
|
|
116
108
|
)
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
React.createElement(
|
|
120
|
-
Text,
|
|
121
|
-
{ dimColor: true },
|
|
122
|
-
'1-9:Sessions | AgileFlow v2.89.3'
|
|
109
|
+
),
|
|
110
|
+
React.createElement(Text, { dimColor: true }, '1-9:Sessions | AgileFlow v2.89.3')
|
|
123
111
|
)
|
|
124
|
-
)
|
|
125
112
|
);
|
|
126
113
|
}
|
|
127
114
|
|
|
@@ -131,29 +118,21 @@ function App({
|
|
|
131
118
|
function HelpPanel({ bindings = DEFAULT_BINDINGS }) {
|
|
132
119
|
const groups = {
|
|
133
120
|
'Loop Control': ['start', 'pause', 'resume'],
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
121
|
+
View: ['trace', 'help'],
|
|
122
|
+
Navigation: ['quit'],
|
|
123
|
+
Sessions: ['session1', 'session2', 'session3'],
|
|
137
124
|
};
|
|
138
125
|
|
|
139
126
|
return React.createElement(
|
|
140
127
|
Box,
|
|
141
128
|
{ flexDirection: 'column', padding: 1 },
|
|
142
|
-
React.createElement(
|
|
143
|
-
Text,
|
|
144
|
-
{ bold: true, color: 'cyan' },
|
|
145
|
-
'Key Bindings'
|
|
146
|
-
),
|
|
129
|
+
React.createElement(Text, { bold: true, color: 'cyan' }, 'Key Bindings'),
|
|
147
130
|
React.createElement(Box, { marginTop: 1 }),
|
|
148
131
|
Object.entries(groups).map(([groupName, actions]) =>
|
|
149
132
|
React.createElement(
|
|
150
133
|
Box,
|
|
151
134
|
{ key: groupName, flexDirection: 'column', marginBottom: 1 },
|
|
152
|
-
React.createElement(
|
|
153
|
-
Text,
|
|
154
|
-
{ bold: true },
|
|
155
|
-
groupName + ':'
|
|
156
|
-
),
|
|
135
|
+
React.createElement(Text, { bold: true }, groupName + ':'),
|
|
157
136
|
actions.map(action => {
|
|
158
137
|
const binding = bindings[action];
|
|
159
138
|
if (!binding) return null;
|
|
@@ -165,11 +144,7 @@ function HelpPanel({ bindings = DEFAULT_BINDINGS }) {
|
|
|
165
144
|
})
|
|
166
145
|
)
|
|
167
146
|
),
|
|
168
|
-
React.createElement(
|
|
169
|
-
Text,
|
|
170
|
-
{ dimColor: true, marginTop: 1 },
|
|
171
|
-
'Press ? to close help'
|
|
172
|
-
)
|
|
147
|
+
React.createElement(Text, { dimColor: true, marginTop: 1 }, 'Press ? to close help')
|
|
173
148
|
);
|
|
174
149
|
}
|
|
175
150
|
|
package/scripts/tui/index.js
CHANGED
|
@@ -20,52 +20,8 @@
|
|
|
20
20
|
* 1-9 - Switch session focus
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
const {
|
|
25
|
-
const { App } = require('./App');
|
|
26
|
-
const { SessionPanel } = require('./panels/SessionPanel');
|
|
27
|
-
const { OutputPanel } = require('./panels/OutputPanel');
|
|
28
|
-
|
|
29
|
-
// Main TUI Layout - split panel view
|
|
30
|
-
function MainLayout() {
|
|
31
|
-
return React.createElement(
|
|
32
|
-
Box,
|
|
33
|
-
{ flexDirection: 'row', width: '100%', minHeight: 15 },
|
|
34
|
-
// Left panel - Sessions (40% width)
|
|
35
|
-
React.createElement(
|
|
36
|
-
Box,
|
|
37
|
-
{ flexDirection: 'column', width: '40%', paddingRight: 1 },
|
|
38
|
-
React.createElement(SessionPanel, { refreshInterval: 5000 })
|
|
39
|
-
),
|
|
40
|
-
// Right panel - Agent Output (60% width)
|
|
41
|
-
React.createElement(
|
|
42
|
-
Box,
|
|
43
|
-
{ flexDirection: 'column', width: '60%' },
|
|
44
|
-
React.createElement(OutputPanel, {
|
|
45
|
-
maxMessages: 100,
|
|
46
|
-
showTimestamp: true,
|
|
47
|
-
title: 'AGENT OUTPUT'
|
|
48
|
-
})
|
|
49
|
-
)
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Main entry point
|
|
54
|
-
function main() {
|
|
55
|
-
const instance = render(
|
|
56
|
-
React.createElement(
|
|
57
|
-
App,
|
|
58
|
-
{ title: 'AgileFlow TUI' },
|
|
59
|
-
React.createElement(MainLayout)
|
|
60
|
-
)
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
// Handle clean exit
|
|
64
|
-
instance.waitUntilExit().then(() => {
|
|
65
|
-
console.log('AgileFlow TUI closed.');
|
|
66
|
-
process.exit(0);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
23
|
+
// Use the simple TUI implementation (pure Node.js, no React dependencies)
|
|
24
|
+
const { main } = require('./simple-tui');
|
|
69
25
|
|
|
70
26
|
// Run if executed directly
|
|
71
27
|
if (require.main === module) {
|
|
@@ -92,12 +92,12 @@ function createCheckpoint(sessionId = 'default', loopState = null) {
|
|
|
92
92
|
coverage_threshold: loopState.coverage_threshold || 80,
|
|
93
93
|
coverage_current: loopState.coverage_current || 0,
|
|
94
94
|
started_at: loopState.started_at,
|
|
95
|
-
conditions: loopState.conditions || []
|
|
95
|
+
conditions: loopState.conditions || [],
|
|
96
96
|
},
|
|
97
97
|
recovery_info: {
|
|
98
98
|
can_resume: true,
|
|
99
|
-
last_checkpoint: new Date().toISOString()
|
|
100
|
-
}
|
|
99
|
+
last_checkpoint: new Date().toISOString(),
|
|
100
|
+
},
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
const result = safeWriteJSON(checkpointPath, checkpoint);
|
|
@@ -105,7 +105,7 @@ function createCheckpoint(sessionId = 'default', loopState = null) {
|
|
|
105
105
|
return {
|
|
106
106
|
ok: result.ok,
|
|
107
107
|
checkpoint,
|
|
108
|
-
path: checkpointPath
|
|
108
|
+
path: checkpointPath,
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -128,7 +128,7 @@ function loadCheckpoint(sessionId = 'default') {
|
|
|
128
128
|
return {
|
|
129
129
|
ok: true,
|
|
130
130
|
exists: true,
|
|
131
|
-
checkpoint: result.data
|
|
131
|
+
checkpoint: result.data,
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -151,7 +151,9 @@ function checkRecoveryNeeded(sessionId = 'default') {
|
|
|
151
151
|
const checkpoint = checkpointResult.checkpoint;
|
|
152
152
|
|
|
153
153
|
// Check if checkpoint is stale (older than 1 hour without update)
|
|
154
|
-
const lastCheckpoint = new Date(
|
|
154
|
+
const lastCheckpoint = new Date(
|
|
155
|
+
checkpoint.recovery_info?.last_checkpoint || checkpoint.created_at
|
|
156
|
+
);
|
|
155
157
|
const now = new Date();
|
|
156
158
|
const hoursSinceCheckpoint = (now - lastCheckpoint) / (1000 * 60 * 60);
|
|
157
159
|
|
|
@@ -186,12 +188,12 @@ function checkRecoveryNeeded(sessionId = 'default') {
|
|
|
186
188
|
resume: {
|
|
187
189
|
iteration: loopState.iteration,
|
|
188
190
|
story: loopState.current_story,
|
|
189
|
-
epic: loopState.epic
|
|
191
|
+
epic: loopState.epic,
|
|
190
192
|
},
|
|
191
193
|
fresh: {
|
|
192
|
-
message: 'Start fresh from beginning of epic'
|
|
193
|
-
}
|
|
194
|
-
}
|
|
194
|
+
message: 'Start fresh from beginning of epic',
|
|
195
|
+
},
|
|
196
|
+
},
|
|
195
197
|
};
|
|
196
198
|
}
|
|
197
199
|
|
|
@@ -226,7 +228,7 @@ function resumeFromCheckpoint(sessionId = 'default') {
|
|
|
226
228
|
conditions: loopState.conditions,
|
|
227
229
|
started_at: loopState.started_at,
|
|
228
230
|
resumed_at: new Date().toISOString(),
|
|
229
|
-
resumed_from_checkpoint: true
|
|
231
|
+
resumed_from_checkpoint: true,
|
|
230
232
|
};
|
|
231
233
|
|
|
232
234
|
safeWriteJSON(statePath, state);
|
|
@@ -236,7 +238,7 @@ function resumeFromCheckpoint(sessionId = 'default') {
|
|
|
236
238
|
resumed: true,
|
|
237
239
|
iteration: loopState.iteration,
|
|
238
240
|
story: loopState.current_story,
|
|
239
|
-
epic: loopState.epic
|
|
241
|
+
epic: loopState.epic,
|
|
240
242
|
};
|
|
241
243
|
}
|
|
242
244
|
|
|
@@ -286,7 +288,7 @@ function getRecoveryStatus(sessionId = 'default') {
|
|
|
286
288
|
reason: recovery.reason,
|
|
287
289
|
hasCheckpoint: checkpoint.exists,
|
|
288
290
|
checkpoint: checkpoint.checkpoint,
|
|
289
|
-
options: recovery.recovery_options
|
|
291
|
+
options: recovery.recovery_options,
|
|
290
292
|
};
|
|
291
293
|
}
|
|
292
294
|
|
|
@@ -298,5 +300,5 @@ module.exports = {
|
|
|
298
300
|
resumeFromCheckpoint,
|
|
299
301
|
clearCheckpoint,
|
|
300
302
|
startFresh,
|
|
301
|
-
getRecoveryStatus
|
|
303
|
+
getRecoveryStatus,
|
|
302
304
|
};
|
|
@@ -29,13 +29,8 @@ class EventStream extends EventEmitter {
|
|
|
29
29
|
|
|
30
30
|
this.options = {
|
|
31
31
|
// Path to log file (defaults to agent bus)
|
|
32
|
-
logPath:
|
|
33
|
-
getProjectRoot(),
|
|
34
|
-
'docs',
|
|
35
|
-
'09-agents',
|
|
36
|
-
'bus',
|
|
37
|
-
'log.jsonl'
|
|
38
|
-
),
|
|
32
|
+
logPath:
|
|
33
|
+
options.logPath || path.join(getProjectRoot(), 'docs', '09-agents', 'bus', 'log.jsonl'),
|
|
39
34
|
// Polling interval in ms (fallback if fs.watch fails)
|
|
40
35
|
pollInterval: options.pollInterval || 1000,
|
|
41
36
|
// Maximum events to keep in buffer
|
|
@@ -43,7 +38,7 @@ class EventStream extends EventEmitter {
|
|
|
43
38
|
// Whether to emit historical events on start
|
|
44
39
|
emitHistory: options.emitHistory || false,
|
|
45
40
|
// How many historical events to emit
|
|
46
|
-
historyLimit: options.historyLimit || 10
|
|
41
|
+
historyLimit: options.historyLimit || 10,
|
|
47
42
|
};
|
|
48
43
|
|
|
49
44
|
this.buffer = [];
|
|
@@ -75,13 +70,13 @@ class EventStream extends EventEmitter {
|
|
|
75
70
|
|
|
76
71
|
// Try to use fs.watch (more efficient)
|
|
77
72
|
try {
|
|
78
|
-
this.watcher = fs.watch(this.options.logPath,
|
|
73
|
+
this.watcher = fs.watch(this.options.logPath, eventType => {
|
|
79
74
|
if (eventType === 'change') {
|
|
80
75
|
this._processNewLines();
|
|
81
76
|
}
|
|
82
77
|
});
|
|
83
78
|
|
|
84
|
-
this.watcher.on('error',
|
|
79
|
+
this.watcher.on('error', err => {
|
|
85
80
|
this.emit('error', err);
|
|
86
81
|
// Fall back to polling
|
|
87
82
|
this._startPolling();
|
|
@@ -271,9 +266,7 @@ function getDefaultStream() {
|
|
|
271
266
|
* Format event for display
|
|
272
267
|
*/
|
|
273
268
|
function formatEvent(event) {
|
|
274
|
-
const timestamp = event.timestamp
|
|
275
|
-
? new Date(event.timestamp).toLocaleTimeString()
|
|
276
|
-
: '';
|
|
269
|
+
const timestamp = event.timestamp ? new Date(event.timestamp).toLocaleTimeString() : '';
|
|
277
270
|
|
|
278
271
|
const agent = event.agent || 'unknown';
|
|
279
272
|
const eventType = event.event || event.type || 'unknown';
|
|
@@ -305,12 +298,12 @@ function formatEvent(event) {
|
|
|
305
298
|
agent,
|
|
306
299
|
eventType,
|
|
307
300
|
message,
|
|
308
|
-
raw: event
|
|
301
|
+
raw: event,
|
|
309
302
|
};
|
|
310
303
|
}
|
|
311
304
|
|
|
312
305
|
module.exports = {
|
|
313
306
|
EventStream,
|
|
314
307
|
getDefaultStream,
|
|
315
|
-
formatEvent
|
|
308
|
+
formatEvent,
|
|
316
309
|
};
|