bullswarm 0.10.6 → 0.10.7
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 +9 -0
- package/README.md +5 -3
- package/package.json +1 -1
- package/skill/SKILL.md +4 -2
- package/src/workflow/dashboard.js +99 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# bullswarm changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.7 — clearer orchestration overview
|
|
4
|
+
|
|
5
|
+
- Replaced the orchestrator trace dump with a summary-first view showing what
|
|
6
|
+
it is doing now, the latest decision and reason, the next action, worker
|
|
7
|
+
progress, and the last three semantic events.
|
|
8
|
+
- Added `v` progressive disclosure for provider sessions, checkpoint prompts,
|
|
9
|
+
per-turn usage, full decisions, and artifact paths, keeping audit detail
|
|
10
|
+
available without making it the default human experience.
|
|
11
|
+
|
|
3
12
|
## 0.10.6 — safe setup and boolean flags
|
|
4
13
|
|
|
5
14
|
- Marked the deterministic `echo` connector as a test fixture and excluded it
|
package/README.md
CHANGED
|
@@ -276,9 +276,11 @@ bullswarm workflow watch <shortId> --once # one current/terminal snapshot
|
|
|
276
276
|
autonomous goal its left navigation stacks a compact Orchestrator panel above
|
|
277
277
|
the Phases panel; internal planner turns never appear as workers or phases.
|
|
278
278
|
Select Orchestration and press Enter, or press `o`
|
|
279
|
-
anywhere, to
|
|
280
|
-
|
|
281
|
-
|
|
279
|
+
anywhere, to open a summary-first orchestration overview: what it is doing now,
|
|
280
|
+
its latest decision in plain language, why it chose that path, what happens
|
|
281
|
+
next, progress, and the last three semantic actions. Press `v` to toggle the
|
|
282
|
+
technical view with provider session, every checkpoint turn, usage, prompt,
|
|
283
|
+
and artifact paths. Status marks are consistent throughout the tree: `○` not started,
|
|
282
284
|
an animated Braille spinner for active work, `⧖` waiting, `✓` finished, and
|
|
283
285
|
`✗` failed or interrupted. The non-emoji `⧖` avoids the inconsistent cell
|
|
284
286
|
width of `⌛` across terminal fonts. It watches ongoing runs from disk and supports `j`/`k` or arrow-key selection, Enter for
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -263,8 +263,10 @@ cannot be guaranteed. Restore eligibility with `strategy include-model`.
|
|
|
263
263
|
|
|
264
264
|
In the human TUI, the autonomous orchestrator has a compact selectable panel
|
|
265
265
|
stacked above the phase tree. Select it and press Enter, or press
|
|
266
|
-
`o`, to
|
|
267
|
-
|
|
266
|
+
`o`, to see a summary-first overview of its current role, latest decision,
|
|
267
|
+
reason, next action, progress, and recent semantic activity. Press `v` for the
|
|
268
|
+
durable provider session, checkpoint prompts and turns, usage, and artifact
|
|
269
|
+
paths; `v` returns to the overview and Esc returns to phases.
|
|
268
270
|
The shared state marks are `○` not started, animated Braille spinner active,
|
|
269
271
|
`⧖` waiting, `✓` finished, and `✗` failed or interrupted.
|
|
270
272
|
|
|
@@ -345,7 +345,8 @@ export function workflowPanelModel(row, {
|
|
|
345
345
|
export function renderWorkflowTui(row, {
|
|
346
346
|
width = 120, height = 36, focus = 0, phaseIndex = null, agentIndex = null,
|
|
347
347
|
detailScroll = 0, message = null, confirmCancel = false,
|
|
348
|
-
controlSelected = false, orchestratorDetail = false,
|
|
348
|
+
controlSelected = false, orchestratorDetail = false, orchestratorVerbose = false,
|
|
349
|
+
spinnerFrame = 0,
|
|
349
350
|
} = {}) {
|
|
350
351
|
width = Math.max(38, Number(width) || 120);
|
|
351
352
|
height = Math.max(18, Number(height) || 36);
|
|
@@ -367,10 +368,12 @@ export function renderWorkflowTui(row, {
|
|
|
367
368
|
];
|
|
368
369
|
const footer = confirmCancel
|
|
369
370
|
? ' Stop this workflow? y confirm · n/Esc keep running'
|
|
370
|
-
:
|
|
371
|
+
: orchestratorDetail
|
|
372
|
+
? ` ↑/↓ scroll · v ${orchestratorVerbose ? 'overview' : 'technical details'} · Esc back · c stop · q detach`
|
|
373
|
+
: ` ↑/↓ select · Enter drill in · Esc back · o orchestrator · c stop · q detach`;
|
|
371
374
|
const rawMessageLine = message
|
|
372
375
|
? ` ${truncate(message, width - 2)}`
|
|
373
|
-
: ` ${orchestratorDetail ?
|
|
376
|
+
: ` ${orchestratorDetail ? `Orchestrator ${orchestratorVerbose ? 'technical details' : 'overview'}` : focus === 0 ? 'Phases' : focus === 1 ? 'Agents' : 'Agent activity'} · r refresh · workflow continues after detach`;
|
|
374
377
|
const messageLine = truncate(rawMessageLine, width);
|
|
375
378
|
const bodyHeight = Math.max(10, height - header.length - 3);
|
|
376
379
|
|
|
@@ -409,6 +412,7 @@ export function renderWorkflowTui(row, {
|
|
|
409
412
|
model,
|
|
410
413
|
Math.max(20, (orchestratorDetail ? width : rightWidth) - 4),
|
|
411
414
|
spinnerFrame,
|
|
415
|
+
{ verbose: orchestratorVerbose },
|
|
412
416
|
);
|
|
413
417
|
const detail = orchestratorDetail
|
|
414
418
|
? orchestrationLines
|
|
@@ -444,7 +448,7 @@ export function renderWorkflowTui(row, {
|
|
|
444
448
|
|
|
445
449
|
let body;
|
|
446
450
|
if (orchestratorDetail) {
|
|
447
|
-
body = renderPanel(
|
|
451
|
+
body = renderPanel(`Orchestrator · ${orchestratorVerbose ? 'technical details' : 'overview'}`, visibleDetail, width, bodyHeight);
|
|
448
452
|
} else if (narrow) {
|
|
449
453
|
const mobile = focus === 0
|
|
450
454
|
? {
|
|
@@ -495,7 +499,7 @@ function joinPanels(left, right) {
|
|
|
495
499
|
return left.map((line, index) => `${line}${right[index] ?? ''}`);
|
|
496
500
|
}
|
|
497
501
|
|
|
498
|
-
function orchestratorDetailLines(model, width, spinnerFrame) {
|
|
502
|
+
function orchestratorDetailLines(model, width, spinnerFrame, { verbose = false } = {}) {
|
|
499
503
|
const { orchestrator, state } = model;
|
|
500
504
|
if (!orchestrator.autonomous) {
|
|
501
505
|
return wrapLines(['This workflow has no autonomous orchestrator thread.'], width);
|
|
@@ -505,10 +509,57 @@ function orchestratorDetailLines(model, width, spinnerFrame) {
|
|
|
505
509
|
const liveActions = active?.lastActions ?? latest?.lastActions ?? [];
|
|
506
510
|
const totalActions = active?.actionCount ?? latest?.actionCount ?? liveActions.length;
|
|
507
511
|
const conversations = Object.entries(state.orchestration?.conversations ?? {});
|
|
512
|
+
const decisions = state.decisions ?? [];
|
|
513
|
+
const latestDecision = decisions.at(-1);
|
|
514
|
+
const workerAttempts = (state.attempts ?? []).filter((attempt) => attempt.actionId !== orchestrator.actionId);
|
|
515
|
+
const completedWorkers = workerAttempts.filter((attempt) => TERMINAL_ACTIONS.has(attempt.status)).length;
|
|
516
|
+
const activeWorkers = Object.values(state.activeAgents ?? {})
|
|
517
|
+
.filter((agent) => agent.stepId !== orchestrator.actionId);
|
|
518
|
+
const nextActions = latestDecision?.actions?.map((action) => action.id).filter(Boolean) ?? [];
|
|
519
|
+
const stateLabel = active
|
|
520
|
+
? 'Choosing the next smallest useful action'
|
|
521
|
+
: activeWorkers.length
|
|
522
|
+
? `Waiting for ${activeWorkers.length} worker${activeWorkers.length === 1 ? '' : 's'} to finish`
|
|
523
|
+
: state.finishedAt
|
|
524
|
+
? 'Workflow finished'
|
|
525
|
+
: 'Reviewing completed evidence';
|
|
508
526
|
const lines = [
|
|
509
527
|
`${statusIcon(active ? 'running' : latest?.status ?? orchestrator.status, spinnerFrame)} ${orchestrator.status} · ${orchestrator.pool} · ${orchestrator.model}`,
|
|
510
|
-
|
|
528
|
+
'',
|
|
529
|
+
`Now · ${stateLabel}`,
|
|
530
|
+
`Progress · ${completedWorkers}/${workerAttempts.length} worker attempts finished · ${orchestrator.attempts.length} planning checkpoint${orchestrator.attempts.length === 1 ? '' : 's'}`,
|
|
511
531
|
];
|
|
532
|
+
if (latestDecision) {
|
|
533
|
+
lines.push(`Latest decision · ${decisionLabel(latestDecision.decision)}`);
|
|
534
|
+
if (latestDecision.reason) lines.push(`Why · ${sentencePreview(latestDecision.reason)}`);
|
|
535
|
+
lines.push(`Next · ${nextActions.length ? nextActions.join(', ') : latestDecision.decision === 'complete' ? 'Return the verified result' : 'Wait for current work, then reassess'}`);
|
|
536
|
+
} else {
|
|
537
|
+
lines.push('Latest decision · Planning has not completed its first checkpoint yet');
|
|
538
|
+
}
|
|
539
|
+
if (active?.stall?.status === 'suspected_stalled') {
|
|
540
|
+
lines.push(`Attention · No new evidence for ${active.stall.silentForSec}s; the agent has not been auto-killed`);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (!verbose) {
|
|
544
|
+
if (state.finishedAt) {
|
|
545
|
+
const verified = state.outcome?.verified === true;
|
|
546
|
+
lines.push('', `Result · ${verified ? 'Verified delivery is ready' : state.status === 'completed_with_concerns' ? 'Best useful delivery is ready with concerns' : state.status === 'blocked' ? 'No useful delivery could be completed' : 'Workflow is terminal'}`);
|
|
547
|
+
const concernCount = state.outcome?.concerns?.length ?? 0;
|
|
548
|
+
if (concernCount) lines.push(`Concerns · ${concernCount} recorded in the result envelope`);
|
|
549
|
+
}
|
|
550
|
+
lines.push('', 'Recent activity');
|
|
551
|
+
if (!liveActions.length) lines.push('· waiting for semantic action events');
|
|
552
|
+
const firstVisibleActionNumber = Math.max(1, totalActions - liveActions.length + 1);
|
|
553
|
+
liveActions.slice(-3).forEach((action, index, visible) => {
|
|
554
|
+
const number = Math.max(firstVisibleActionNumber, totalActions - visible.length + 1) + index;
|
|
555
|
+
lines.push(`#${number} ${statusIcon(action.status, spinnerFrame)} ${friendlyActionKind(action.kind)}${action.summary ? ` · ${friendlyActionSummary(action)}` : ''}`);
|
|
556
|
+
});
|
|
557
|
+
lines.push('', 'Press v for checkpoint prompts, sessions, usage, and artifact paths.');
|
|
558
|
+
return wrapLines(lines, width);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
lines.push('', 'Technical thread');
|
|
562
|
+
lines.push(`Logical thread · ${orchestrator.attempts.length} checkpoint turn${orchestrator.attempts.length === 1 ? '' : 's'}`);
|
|
512
563
|
for (const [pool, thread] of conversations) {
|
|
513
564
|
lines.push(`Session · ${pool} · ${thread.sessionId ?? '—'}${thread.started ? ' · resumable' : ' · pending first turn'}`);
|
|
514
565
|
}
|
|
@@ -551,6 +602,38 @@ function orchestratorDetailLines(model, width, spinnerFrame) {
|
|
|
551
602
|
return wrapLines(lines, width);
|
|
552
603
|
}
|
|
553
604
|
|
|
605
|
+
function decisionLabel(decision) {
|
|
606
|
+
return ({
|
|
607
|
+
needs_more_work: 'Continue with bounded work',
|
|
608
|
+
complete: 'Finish and deliver',
|
|
609
|
+
stop: 'Stop with the best useful outcome',
|
|
610
|
+
})[decision] ?? String(decision ?? 'Pending');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function sentencePreview(value, maxChars = 420) {
|
|
614
|
+
const text = String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
615
|
+
const sentences = text.match(/.*?[.!?](?:\s|$)/g)?.slice(0, 2).map((part) => part.trim()).join(' ') ?? text;
|
|
616
|
+
return truncate(sentences || text, maxChars);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function friendlyActionKind(kind) {
|
|
620
|
+
return ({
|
|
621
|
+
read_file: 'Read file',
|
|
622
|
+
write_file: 'Write file',
|
|
623
|
+
response: 'Response',
|
|
624
|
+
tool: 'Tool',
|
|
625
|
+
bash: 'Command',
|
|
626
|
+
})[kind] ?? String(kind ?? 'Action').replaceAll('_', ' ');
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function friendlyActionSummary(action) {
|
|
630
|
+
const summary = String(action.summary ?? '').replace(/\s+/g, ' ').trim();
|
|
631
|
+
if (action.kind === 'response' && /workflow\.decision|"decision"|needs_more_work/.test(summary)) {
|
|
632
|
+
return 'Planner decision recorded';
|
|
633
|
+
}
|
|
634
|
+
return truncate(summary, 180);
|
|
635
|
+
}
|
|
636
|
+
|
|
554
637
|
function agentDetailLines(model, width, spinnerFrame) {
|
|
555
638
|
const agent = model.selectedAgent;
|
|
556
639
|
if (!agent) {
|
|
@@ -751,6 +834,7 @@ export async function runDashboard(bullswarmDir, {
|
|
|
751
834
|
confirmCancel: false,
|
|
752
835
|
controlSelected: false,
|
|
753
836
|
orchestratorDetail: false,
|
|
837
|
+
orchestratorVerbose: false,
|
|
754
838
|
spinnerFrame: 0,
|
|
755
839
|
};
|
|
756
840
|
const paint = () => {
|
|
@@ -866,6 +950,7 @@ export async function runDashboard(bullswarmDir, {
|
|
|
866
950
|
if (key === '\u001b' || key === 'b') {
|
|
867
951
|
if (ui.orchestratorDetail) {
|
|
868
952
|
ui.orchestratorDetail = false;
|
|
953
|
+
ui.orchestratorVerbose = false;
|
|
869
954
|
ui.focus = 0;
|
|
870
955
|
ui.controlSelected = true;
|
|
871
956
|
ui.detailScroll = 0;
|
|
@@ -891,6 +976,7 @@ export async function runDashboard(bullswarmDir, {
|
|
|
891
976
|
const model = workflowPanelModel(row, { phaseIndex: ui.phaseIndex, agentIndex: ui.agentIndex });
|
|
892
977
|
if (model.orchestrator.autonomous) {
|
|
893
978
|
ui.orchestratorDetail = true;
|
|
979
|
+
ui.orchestratorVerbose = false;
|
|
894
980
|
ui.controlSelected = true;
|
|
895
981
|
ui.detailScroll = 0;
|
|
896
982
|
message = null;
|
|
@@ -900,6 +986,12 @@ export async function runDashboard(bullswarmDir, {
|
|
|
900
986
|
if (key === '1' && detail) { ui.focus = 0; return paint(); }
|
|
901
987
|
if (key === '2' && detail) { ui.focus = 1; return paint(); }
|
|
902
988
|
if (key === '3' && detail) { ui.focus = 2; return paint(); }
|
|
989
|
+
if (key === 'v' && ui.orchestratorDetail) {
|
|
990
|
+
ui.orchestratorVerbose = !ui.orchestratorVerbose;
|
|
991
|
+
ui.detailScroll = 0;
|
|
992
|
+
message = null;
|
|
993
|
+
return paint();
|
|
994
|
+
}
|
|
903
995
|
if (key === '\r' || key === '\n') {
|
|
904
996
|
if (!detail) {
|
|
905
997
|
selectedRunId = rows[selected]?.runId ?? selectedRunId;
|
|
@@ -909,6 +1001,7 @@ export async function runDashboard(bullswarmDir, {
|
|
|
909
1001
|
} else if (ui.focus === 0) {
|
|
910
1002
|
if (ui.controlSelected) {
|
|
911
1003
|
ui.orchestratorDetail = true;
|
|
1004
|
+
ui.orchestratorVerbose = false;
|
|
912
1005
|
ui.detailScroll = 0;
|
|
913
1006
|
} else ui.focus = 1;
|
|
914
1007
|
} else if (ui.focus === 1) {
|