bullswarm 0.18.2 → 0.18.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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # bullswarm changelog
2
2
 
3
+ ## 0.18.3 — mobile timeline polish
4
+
5
+ - Auto-follow now begins at a timestamped milestone instead of exposing an
6
+ orphaned detail line when a long timeline is clipped on a small terminal.
7
+ - Qualified terminal runs state the number of concerns and direct the user to
8
+ review them in the result envelope.
9
+
3
10
  ## 0.18.2 — truthful recovery timeline and verifier cleanup
4
11
 
5
12
  - Dependency-blocked plan branches now appear as a single skipped phase in the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bullswarm",
3
- "version": "0.18.2",
3
+ "version": "0.18.3",
4
4
  "description": "Route work across coding-agent CLI subscriptions — paced by live quota meters, verified by content, never trusting exit codes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -559,13 +559,18 @@ function renderWorkflowOverviewPanel(model, width, height, spinnerFrame, timelin
559
559
  const timelineRows = Math.max(1, contentRows - liveRows - nextRows);
560
560
  const maxTimelineScroll = Math.max(0, timeline.lines.length - timelineRows);
561
561
  const scroll = clamp(timelineScroll, 0, maxTimelineScroll);
562
- const start = Math.max(0, timeline.lines.length - timelineRows - scroll);
563
- let visibleTimeline = timeline.lines.slice(start, start + timelineRows);
564
- if (start > 0 && visibleTimeline.length) {
565
- visibleTimeline[0] = dimText(`↑ ${start + 1} earlier timeline rows`, inner);
562
+ let start = Math.max(0, timeline.lines.length - timelineRows - scroll);
563
+ if (start > 0) {
564
+ while (start < timeline.lines.length && !/^\d{2}:\d{2}\s/.test(timeline.lines[start])) start += 1;
566
565
  }
567
- if (start + timelineRows < timeline.lines.length && visibleTimeline.length) {
568
- visibleTimeline[visibleTimeline.length - 1] = dimText(`↓ ${timeline.lines.length - start - timelineRows + 1} newer timeline rows`, inner);
566
+ const historyRows = start > 0 ? Math.max(0, timelineRows - 1) : timelineRows;
567
+ let visibleTimeline = timeline.lines.slice(start, start + historyRows);
568
+ if (start > 0) {
569
+ visibleTimeline.unshift(dimText(`↑ ${start} earlier timeline rows`, inner));
570
+ }
571
+ const end = start + historyRows;
572
+ if (end < timeline.lines.length && visibleTimeline.length) {
573
+ visibleTimeline[visibleTimeline.length - 1] = dimText(`↓ ${timeline.lines.length - end} newer timeline rows`, inner);
569
574
  }
570
575
  const visibleLive = live.lines.slice(0, liveRows);
571
576
  const visibleNext = next.slice(0, nextRows);
@@ -784,14 +789,16 @@ function workflowLiveLines(model, width, spinnerFrame) {
784
789
  lines.push('');
785
790
  }
786
791
  if (!lines.length) lines.push(state.finishedAt
787
- ? `${statusIcon(state.status)} No agents running · ${terminalWorkflowLabel(state.status)}`
792
+ ? `${statusIcon(state.status)} No agents running · ${terminalWorkflowLabel(state.status, state.outcome?.concerns?.length)}`
788
793
  : '⧖ Waiting for the next dispatch');
789
794
  return { lines, running, waiting };
790
795
  }
791
796
 
792
- function terminalWorkflowLabel(status) {
797
+ function terminalWorkflowLabel(status, concernCount = 0) {
793
798
  if (status === 'completed') return 'workflow finished';
794
- if (status === 'completed_with_concerns') return 'workflow finished with concerns';
799
+ if (status === 'completed_with_concerns') return concernCount
800
+ ? `workflow finished with ${concernCount} concern${concernCount === 1 ? '' : 's'}`
801
+ : 'workflow finished with concerns';
795
802
  if (status === 'blocked') return 'workflow stopped with blockers';
796
803
  if (status === 'failed') return 'workflow failed';
797
804
  if (status === 'cancelled') return 'workflow cancelled';
@@ -802,14 +809,17 @@ function terminalWorkflowLabel(status) {
802
809
  function workflowNextLines(model, width) {
803
810
  const { state, orchestrator } = model;
804
811
  if (state.finishedAt) {
805
- const next = state.status === 'completed' || state.status === 'completed_with_concerns'
812
+ const concernCount = state.outcome?.concerns?.length ?? 0;
813
+ const next = state.status === 'completed'
806
814
  ? 'result ready'
815
+ : state.status === 'completed_with_concerns'
816
+ ? concernCount ? `review ${concernCount} concern${concernCount === 1 ? '' : 's'} in result` : 'review concerns in result'
807
817
  : state.status === 'blocked' ? 'review blockers and partial work'
808
818
  : state.status === 'failed' ? 'inspect the failure before using partial work'
809
819
  : state.status === 'cancelled' ? 'review any partial work'
810
820
  : state.status === 'interrupted' ? 'resume the workflow or inspect partial work'
811
821
  : 'inspect the workflow result';
812
- const label = terminalWorkflowLabel(state.status);
822
+ const label = terminalWorkflowLabel(state.status, concernCount);
813
823
  return [truncate(`${statusIcon(state.status)} ${label[0].toUpperCase()}${label.slice(1)} · ${next}`, width)];
814
824
  }
815
825
  const ledger = state.actionLedger ?? [];