bullswarm 0.18.2 → 0.18.4

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,17 @@
1
1
  # bullswarm changelog
2
2
 
3
+ ## 0.18.4 — reliable auto-follow tail
4
+
5
+ - Timestamp-aligned mobile auto-follow now preserves the newest workflow
6
+ milestone instead of replacing it with a spurious newer-rows marker.
7
+
8
+ ## 0.18.3 — mobile timeline polish
9
+
10
+ - Auto-follow now begins at a timestamped milestone instead of exposing an
11
+ orphaned detail line when a long timeline is clipped on a small terminal.
12
+ - Qualified terminal runs state the number of concerns and direct the user to
13
+ review them in the result envelope.
14
+
3
15
  ## 0.18.2 — truthful recovery timeline and verifier cleanup
4
16
 
5
17
  - 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.4",
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,20 @@ 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
+ const end = Math.max(0, timeline.lines.length - scroll);
563
+ let start = Math.max(0, end - timelineRows);
564
+ if (start > 0) {
565
+ start = Math.max(0, end - Math.max(0, timelineRows - 1));
566
+ while (start < end && !/^\d{2}:\d{2}\s/.test(timeline.lines[start])) start += 1;
566
567
  }
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);
568
+ let visibleTimeline = timeline.lines.slice(start, end);
569
+ if (start > 0) {
570
+ visibleTimeline.unshift(dimText(`↑ ${start} earlier timeline rows`, inner));
571
+ }
572
+ if (end < timeline.lines.length && visibleTimeline.length) {
573
+ const marker = dimText(`↓ ${timeline.lines.length - end} newer timeline rows`, inner);
574
+ if (visibleTimeline.length >= timelineRows) visibleTimeline[visibleTimeline.length - 1] = marker;
575
+ else visibleTimeline.push(marker);
569
576
  }
570
577
  const visibleLive = live.lines.slice(0, liveRows);
571
578
  const visibleNext = next.slice(0, nextRows);
@@ -784,14 +791,16 @@ function workflowLiveLines(model, width, spinnerFrame) {
784
791
  lines.push('');
785
792
  }
786
793
  if (!lines.length) lines.push(state.finishedAt
787
- ? `${statusIcon(state.status)} No agents running · ${terminalWorkflowLabel(state.status)}`
794
+ ? `${statusIcon(state.status)} No agents running · ${terminalWorkflowLabel(state.status, state.outcome?.concerns?.length)}`
788
795
  : '⧖ Waiting for the next dispatch');
789
796
  return { lines, running, waiting };
790
797
  }
791
798
 
792
- function terminalWorkflowLabel(status) {
799
+ function terminalWorkflowLabel(status, concernCount = 0) {
793
800
  if (status === 'completed') return 'workflow finished';
794
- if (status === 'completed_with_concerns') return 'workflow finished with concerns';
801
+ if (status === 'completed_with_concerns') return concernCount
802
+ ? `workflow finished with ${concernCount} concern${concernCount === 1 ? '' : 's'}`
803
+ : 'workflow finished with concerns';
795
804
  if (status === 'blocked') return 'workflow stopped with blockers';
796
805
  if (status === 'failed') return 'workflow failed';
797
806
  if (status === 'cancelled') return 'workflow cancelled';
@@ -802,14 +811,17 @@ function terminalWorkflowLabel(status) {
802
811
  function workflowNextLines(model, width) {
803
812
  const { state, orchestrator } = model;
804
813
  if (state.finishedAt) {
805
- const next = state.status === 'completed' || state.status === 'completed_with_concerns'
814
+ const concernCount = state.outcome?.concerns?.length ?? 0;
815
+ const next = state.status === 'completed'
806
816
  ? 'result ready'
817
+ : state.status === 'completed_with_concerns'
818
+ ? concernCount ? `review ${concernCount} concern${concernCount === 1 ? '' : 's'} in result` : 'review concerns in result'
807
819
  : state.status === 'blocked' ? 'review blockers and partial work'
808
820
  : state.status === 'failed' ? 'inspect the failure before using partial work'
809
821
  : state.status === 'cancelled' ? 'review any partial work'
810
822
  : state.status === 'interrupted' ? 'resume the workflow or inspect partial work'
811
823
  : 'inspect the workflow result';
812
- const label = terminalWorkflowLabel(state.status);
824
+ const label = terminalWorkflowLabel(state.status, concernCount);
813
825
  return [truncate(`${statusIcon(state.status)} ${label[0].toUpperCase()}${label.slice(1)} · ${next}`, width)];
814
826
  }
815
827
  const ledger = state.actionLedger ?? [];