bullswarm 0.18.4 → 0.18.5
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/package.json +1 -1
- package/src/workflow/dashboard.js +26 -6
package/package.json
CHANGED
|
@@ -1268,7 +1268,8 @@ function detailRow(bullswarmDir, token) {
|
|
|
1268
1268
|
}
|
|
1269
1269
|
|
|
1270
1270
|
export async function runDashboard(bullswarmDir, {
|
|
1271
|
-
input = process.stdin, output = process.stdout, refreshMs = 1000,
|
|
1271
|
+
input = process.stdin, output = process.stdout, refreshMs = 1000,
|
|
1272
|
+
spinnerMs = 400, token = null,
|
|
1272
1273
|
} = {}) {
|
|
1273
1274
|
if ((!input.isTTY || !output.isTTY) && !token) throw new Error('workflow dashboard requires a TTY, or pass a run ID for a static text tree');
|
|
1274
1275
|
if ((!input.isTTY || !output.isTTY) && token) {
|
|
@@ -1282,6 +1283,7 @@ export async function runDashboard(bullswarmDir, {
|
|
|
1282
1283
|
let lastGoodRow = null;
|
|
1283
1284
|
let rows = dashboardRows(bullswarmDir);
|
|
1284
1285
|
let selectedRunId = token ? detailRow(bullswarmDir, token).runId : (rows[selected]?.runId ?? null);
|
|
1286
|
+
let lastPaintedFrame = null;
|
|
1285
1287
|
const ui = {
|
|
1286
1288
|
focus: 0,
|
|
1287
1289
|
phaseIndex: null,
|
|
@@ -1297,6 +1299,24 @@ export async function runDashboard(bullswarmDir, {
|
|
|
1297
1299
|
mobileTimeline: true,
|
|
1298
1300
|
spinnerFrame: 0,
|
|
1299
1301
|
};
|
|
1302
|
+
// Clearing the entire alternate screen for every spinner frame produces a
|
|
1303
|
+
// visible blank flash on slower terminals, especially mobile SSH sessions.
|
|
1304
|
+
// Enter/clear the alternate screen once, then repaint each row in place.
|
|
1305
|
+
// Padding to the terminal height also removes remnants when switching from
|
|
1306
|
+
// a taller detail view to a shorter picker view.
|
|
1307
|
+
const writeFrame = (text) => {
|
|
1308
|
+
const clearPrefix = `${ESC}2J${ESC}H`;
|
|
1309
|
+
const source = String(text ?? '').startsWith(clearPrefix)
|
|
1310
|
+
? String(text ?? '').slice(clearPrefix.length)
|
|
1311
|
+
: String(text ?? '');
|
|
1312
|
+
const height = Math.max(1, Number(output.rows) || source.split('\n').length);
|
|
1313
|
+
const lines = source.split('\n').slice(0, height);
|
|
1314
|
+
while (lines.length < height) lines.push('');
|
|
1315
|
+
const frame = `${ESC}H${lines.map((line) => `${line}${ESC}K`).join('\n')}`;
|
|
1316
|
+
if (frame === lastPaintedFrame) return;
|
|
1317
|
+
lastPaintedFrame = frame;
|
|
1318
|
+
output.write(frame);
|
|
1319
|
+
};
|
|
1300
1320
|
const paintUnsafe = () => {
|
|
1301
1321
|
if (selected >= rows.length) selected = Math.max(0, rows.length - 1);
|
|
1302
1322
|
if (detail && selectedRunId) {
|
|
@@ -1311,7 +1331,7 @@ export async function runDashboard(bullswarmDir, {
|
|
|
1311
1331
|
});
|
|
1312
1332
|
ui.phaseIndex = model.phaseIndex;
|
|
1313
1333
|
ui.agentIndex = model.agentIndex;
|
|
1314
|
-
|
|
1334
|
+
writeFrame(renderWorkflowTui(row, {
|
|
1315
1335
|
width: output.columns,
|
|
1316
1336
|
height: output.rows,
|
|
1317
1337
|
...ui,
|
|
@@ -1319,7 +1339,7 @@ export async function runDashboard(bullswarmDir, {
|
|
|
1319
1339
|
}));
|
|
1320
1340
|
return;
|
|
1321
1341
|
}
|
|
1322
|
-
|
|
1342
|
+
writeFrame(renderDashboard({ rows, selected, message }));
|
|
1323
1343
|
};
|
|
1324
1344
|
// A render error must never kill the TUI or strand the terminal in
|
|
1325
1345
|
// alt-screen raw mode (crash observed 2026-08-29 at detailRow via the
|
|
@@ -1327,7 +1347,7 @@ export async function runDashboard(bullswarmDir, {
|
|
|
1327
1347
|
const paint = () => {
|
|
1328
1348
|
try { paintUnsafe(); } catch (err) {
|
|
1329
1349
|
message = `display error: ${err.message}`;
|
|
1330
|
-
try {
|
|
1350
|
+
try { writeFrame(renderDashboard({ rows, selected, message })); } catch { /* keep the loop alive */ }
|
|
1331
1351
|
}
|
|
1332
1352
|
};
|
|
1333
1353
|
const refresh = () => {
|
|
@@ -1339,13 +1359,13 @@ export async function runDashboard(bullswarmDir, {
|
|
|
1339
1359
|
};
|
|
1340
1360
|
input.setRawMode?.(true);
|
|
1341
1361
|
input.resume();
|
|
1342
|
-
output.write(`${ESC}?1049h${ESC}?25l`);
|
|
1362
|
+
output.write(`${ESC}?1049h${ESC}?25l${ESC}2J${ESC}H`);
|
|
1343
1363
|
paint();
|
|
1344
1364
|
const timer = setInterval(refresh, refreshMs);
|
|
1345
1365
|
const spinnerTimer = setInterval(() => {
|
|
1346
1366
|
ui.spinnerFrame = (ui.spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
1347
1367
|
if (detail) paint();
|
|
1348
|
-
},
|
|
1368
|
+
}, Math.max(50, Number(spinnerMs) || 400));
|
|
1349
1369
|
return new Promise((resolve) => {
|
|
1350
1370
|
const finish = () => {
|
|
1351
1371
|
clearInterval(timer);
|