bullswarm 0.28.0 → 0.28.2
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 +148 -3
- package/README.md +42 -1
- package/data/openrouter-benchmarks.json +10416 -10292
- package/package.json +1 -1
- package/skill/references/operations.md +11 -2
- package/src/cli.js +12 -3
- package/src/help.js +5 -4
- package/src/lib/config.js +44 -6
- package/src/lib/forecast.js +3 -2
- package/src/lib/glyphs.js +104 -0
- package/src/lib/route.js +36 -19
- package/src/lib/spend.js +40 -20
- package/src/lib/strategy.js +6 -0
- package/src/meters/framework.js +82 -6
- package/src/strategy-cli.js +33 -3
- package/src/strategy-dashboard.js +6 -5
- package/src/workflow/dashboard.js +33 -33
- package/src/workflow/runs-cli.js +5 -1
- package/src/workflow/v2-dispatch.js +1 -1
- package/src/workflow/watch-cli.js +23 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,149 @@
|
|
|
1
1
|
# bullswarm changelog
|
|
2
2
|
|
|
3
|
+
## 0.28.2 — terminal glyph fallback, issue watcher, health on a fresh home
|
|
4
|
+
|
|
5
|
+
- `bullswarm workflow` repainted flashing `?` characters on macOS
|
|
6
|
+
Terminal.app. Cause is font coverage, not encoding: parsing the `cmap` of
|
|
7
|
+
every monospace face Terminal.app offers shows **zero** glyphs in the
|
|
8
|
+
Braille block `U+2800-U+28FF` — Andale Mono, Menlo, SF Mono, Monaco and
|
|
9
|
+
Courier New are all 0/256 — and none of the five has `⧖` (`U+29D6`) or `⟡`
|
|
10
|
+
(`U+27E1`). The dashboard's 10-frame Braille spinner advances every 400ms,
|
|
11
|
+
so every repaint drew a question mark that flashed; the static `✓ ✗ ⊘ ◇ ↳
|
|
12
|
+
⚠ ● ○ ■ ♡ ◆ ↻ ↺ ▶ ⚙` icons beside it were the steady ones. Terminal.app
|
|
13
|
+
does not substitute another face for these ranges.
|
|
14
|
+
- New `src/lib/glyphs.js` holds one table per mode and the detection order:
|
|
15
|
+
`BULLSWARM_ASCII` forces ASCII, `BULLSWARM_UNICODE` forces Unicode and
|
|
16
|
+
overrides detection, then a non-UTF-8 locale, `TERM=dumb`/`TERM=linux`, and
|
|
17
|
+
`TERM_PROGRAM=Apple_Terminal` select ASCII. Every replacement is one column
|
|
18
|
+
of printable ASCII, because panel width maths uses `String#length` and a
|
|
19
|
+
wide substitute would shear the borders. Box drawing, `· › × … ≈ ≥ █` are in
|
|
20
|
+
all five fonts and are left alone, so panels keep their borders.
|
|
21
|
+
- Wired through the three live-refreshing renderers only:
|
|
22
|
+
`src/workflow/dashboard.js`, `src/workflow/watch-cli.js` and
|
|
23
|
+
`src/strategy-dashboard.js`. One-shot `✓`/`✗` message prefixes in
|
|
24
|
+
`src/workflow/cli.js`, `src/help.js`, `src/integrate.js`,
|
|
25
|
+
`src/workflow/runs-cli.js`, `src/strategy-cli.js` and
|
|
26
|
+
`src/lib/cli-flags.js` are untouched — they do not flash and are a separate
|
|
27
|
+
change.
|
|
28
|
+
- `spinnerGlyph` now parks a non-finite frame counter on frame 0. The previous
|
|
29
|
+
expression `SPINNER_FRAMES[Math.abs(Number(f) || 0) % SPINNER_FRAMES.length]`
|
|
30
|
+
returned `undefined` for `Infinity`, so a repaint could have painted the
|
|
31
|
+
string "undefined" into the frame.
|
|
32
|
+
- `tests/glyphs.test.js` covers the detection matrix (including that `''`,
|
|
33
|
+
`'0'` and `'false'` do not switch tables, and that `LC_ALL` outranks
|
|
34
|
+
`LANG`), asserts every Unicode entry has a one-column pure-ASCII twin that
|
|
35
|
+
is never `?`, and renders both dashboards, `renderWatchSnapshot`,
|
|
36
|
+
`renderWatchEvent` over 15 event shapes and `renderAnalysisProgress` at five
|
|
37
|
+
spinner frames, asserting no substituted glyph survives in ASCII mode while
|
|
38
|
+
the Braille spinner still appears in Unicode mode.
|
|
39
|
+
- `tests/strategy-cli.test.js`, `tests/workflow-dashboard.test.js` and
|
|
40
|
+
`tests/workflow-watch.test.js` assert the Unicode presentation, so they now
|
|
41
|
+
pin `BULLSWARM_UNICODE=1` and `delete process.env.BULLSWARM_ASCII`; without
|
|
42
|
+
the pin the suite's own output depended on which terminal the developer ran
|
|
43
|
+
it in, and without the delete an ambient `BULLSWARM_ASCII=1` — the very
|
|
44
|
+
workaround the README hands an affected user — outranked the pin and broke
|
|
45
|
+
22 tests.
|
|
46
|
+
- The four arrows `↑ ↓ ← →` are deliberately not substituted. They are only
|
|
47
|
+
missing from Monaco, and they live in the frozen `DASHBOARD_KEYS` constant,
|
|
48
|
+
which binds at import time — before any env pin could apply.
|
|
49
|
+
- `scripts/issue-watcher/` — a durable GitHub issue watcher for this
|
|
50
|
+
repository, installed as a launchd agent
|
|
51
|
+
(`node scripts/issue-watcher/install.mjs`). Idle costs ZERO model tokens:
|
|
52
|
+
a pass is one `gh issue list` and nothing else. Only when an issue appears
|
|
53
|
+
that is new since install does it delegate — one
|
|
54
|
+
`bullswarm run --lane analyze` to triage (label + plain-words comment),
|
|
55
|
+
and for a bug it judged fixable at confidence >= 0.7 one
|
|
56
|
+
`bullswarm run --lane build` to fix, which the watcher then verifies
|
|
57
|
+
itself (test suite green in its own clone, diff non-empty) before pushing
|
|
58
|
+
`fix/issue-<n>` and opening a pull request. Both delegations run on the
|
|
59
|
+
owner's subscriptions through normal bullswarm routing, never API tokens.
|
|
60
|
+
Capped at 6 triages and 2 fix attempts per UTC day, one fix attempt per
|
|
61
|
+
issue; guarded by a pass lock, a `paused` file, and a 3-attempt triage
|
|
62
|
+
retry cap. Delegates get no GitHub credentials and issue text is quoted as
|
|
63
|
+
untrusted data. It never merges, closes, or releases — a maintainer does.
|
|
64
|
+
|
|
65
|
+
## 0.28.1 — summary bytes on the wire, monthly pacing
|
|
66
|
+
|
|
67
|
+
- `workflow runs result <id> --summary` was budgeted by `fitResultSummary`
|
|
68
|
+
against compact `JSON.stringify(summary)` (`RESULT_SUMMARY_BYTE_BUDGET =
|
|
69
|
+
4096` in `src/workflow/v2-outcome.js`) but `jsonOut` printed
|
|
70
|
+
`JSON.stringify(obj, null, 2)`, so the bytes on the wire exceeded the
|
|
71
|
+
budget. `--summary` now prints compact single-line JSON
|
|
72
|
+
(`JSON.stringify(obj)` in `src/workflow/runs-cli.js`); `--json` without
|
|
73
|
+
`--summary` still pretty-prints the full envelope, and `--summary --json`
|
|
74
|
+
stays identical to `--summary`. Measured on
|
|
75
|
+
`tests/fixtures/real-result-ze5xz2.json` through the summariser / CLI:
|
|
76
|
+
compact `--summary` is 3,786 bytes
|
|
77
|
+
(`tests/workflow-result-summary.test.js` prints `result-summary size:
|
|
78
|
+
full=57141 summary=3786`; same figure as
|
|
79
|
+
`Buffer.byteLength(JSON.stringify(summarizeV2Result(fixture)))`); the
|
|
80
|
+
full envelope as `--json` prints it is 60,709 bytes
|
|
81
|
+
(`JSON.stringify(envelope, null, 2)` plus the trailing newline
|
|
82
|
+
`console.log` adds — `tests/workflow-result-summary.test.js` prints
|
|
83
|
+
`result-summary cli: prettyFull=60709`).
|
|
84
|
+
|
|
85
|
+
- Routing paced every pool by `windows.seven_day ?? windows.monthly`
|
|
86
|
+
(`paceSnapshot` in `src/meters/framework.js`), so command-code — whose real
|
|
87
|
+
budget is a monthly credit allocation — was paced by its weekly rate-limit
|
|
88
|
+
window. Live meter, captured 2026-09-09T10:45:28Z and evaluated at
|
|
89
|
+
2026-09-09T11:09:44.982Z: weekly `used 73.1% elapsed 91.8% surplus +18.7`
|
|
90
|
+
against monthly `used 79.4% elapsed 75.3% surplus -4.1`, with 14.43 of 70
|
|
91
|
+
credits left for the 7.66 days to the 2026-09-17T03:06:55Z reset. Routing
|
|
92
|
+
therefore called it "the most-behind capable pool" and kept sending it work
|
|
93
|
+
while its monthly budget was already 4.1 points overspent. Pacing is now per
|
|
94
|
+
pool: `pacingWindowFor({connector, subscription})` resolves
|
|
95
|
+
`state.strategy.subscriptions[pool].quotaWindow`, then
|
|
96
|
+
`connector.subscription.quotaWindow`, normalised to `weekly` | `monthly` |
|
|
97
|
+
`null` (any other label — including a pre-0.28.1 free-text one — is ignored
|
|
98
|
+
for pacing and keeps the old weekly-first order). `paceSnapshot(snapshot,
|
|
99
|
+
nowMs, {pacingWindow})` takes `monthly` → `windows.monthly ??
|
|
100
|
+
windows.seven_day`, `weekly`/`null` → `windows.seven_day ??
|
|
101
|
+
windows.monthly`, and returns `pacingWindow` naming the window actually
|
|
102
|
+
used. `src/lib/config.js` `buildPools` resolves the choice once per pool
|
|
103
|
+
(where the connector and the state both are) and re-paces `usedPct`,
|
|
104
|
+
`elapsedPct`, `pace` and `paceResetsAt` off `reading.windows`, so cache,
|
|
105
|
+
stale and live readings are paced identically; the pool view carries
|
|
106
|
+
`pacingWindow`. The 5h gate is untouched: `command-code` still gates on 5h
|
|
107
|
+
`25.2%`. Connectors already declared this — `command-code` and the kaihk
|
|
108
|
+
pools `monthly`, `claude-code`/`codex`/`grok` `weekly`; nothing read it for
|
|
109
|
+
pacing before.
|
|
110
|
+
- The spend model follows the pacing window. `WINDOW_KEYS` (framework.js)
|
|
111
|
+
gains `monthly: {snapshot: 'monthly', history: 'monthly', windowMs: null}`,
|
|
112
|
+
and `rateForWindow` (`src/lib/spend.js`) derives the bootstrap window start
|
|
113
|
+
with `meta.windowMs ?? monthlyWindowMs(resetsAtMs)` — the calendar month
|
|
114
|
+
ending at the provider's `resets_at` (M2), never an assumed 30 days.
|
|
115
|
+
`attachSpend` now writes `pool.spend.monthly` and `pool.projectedMonthlyPct`
|
|
116
|
+
next to the fiveHour/weekly fields, plus `pool.spend.pacing = {window,
|
|
117
|
+
ratePerMinute, source, samples}` and `pool.projectedPacingPct` for the
|
|
118
|
+
window that paces the pool (default `weekly`, so a pool that declares
|
|
119
|
+
nothing keeps its old numbers). `inflightLoad` (`src/lib/route.js`) charges
|
|
120
|
+
the in-flight penalty from `spend.pacing?.ratePerMinute ??
|
|
121
|
+
spend.weekly?.ratePerMinute` with that rate's own source label, so the
|
|
122
|
+
surplus and the penalty are measured in the same window; candidate rows gain
|
|
123
|
+
`pacingWindow` and `projectedPacingPct` beside the unchanged
|
|
124
|
+
`projectedWeeklyPct`.
|
|
125
|
+
- Operator control and display. `bullswarm strategy set-subscription <pool>
|
|
126
|
+
--quota-window <weekly|monthly>` now selects the window that paces routing
|
|
127
|
+
(help text in `src/help.js`) and validates it: anything else exits 2 with
|
|
128
|
+
`--quota-window must be weekly or monthly (or unknown to clear)`, and
|
|
129
|
+
`unknown` clears the override back to the connector's declaration. Labels
|
|
130
|
+
already stored are ignored for pacing, never rejected on read. `bullswarm
|
|
131
|
+
pools` names the window in the meter column — `cmd-fixture cost=5
|
|
132
|
+
lanes=chore monthly used 79.4% elapsed 75.3% [cache] surplus=-4.1
|
|
133
|
+
inflight=0 5h=25.2% ready` — and `pools --json` entries carry
|
|
134
|
+
`pacingWindow`. `strategy refresh`/`show` print the window on each
|
|
135
|
+
subscription line (`command-code: GOAT · ... · monthly 79.4% used · surplus
|
|
136
|
+
-4.1`) and carry `pacingWindow` next to the free-text `quotaWindow` label in
|
|
137
|
+
`--json`; `strategy inventory --json` carries it per provider.
|
|
138
|
+
- Tests: 738 -> 750, 0 failures. The new behaviour is covered in
|
|
139
|
+
`tests/meters.test.js` (the live command-code snapshot as a fixture, the
|
|
140
|
+
helper's precedence, `buildPools` pacing), `tests/spend.test.js` (monthly
|
|
141
|
+
bootstrap window start, `spend.pacing`/`projectedPacingPct`),
|
|
142
|
+
`tests/route.test.js` (the penalty on the pacing window; weekly-only pools
|
|
143
|
+
unchanged), `tests/strategy-cli.test.js` (`--quota-window` validation) and
|
|
144
|
+
`tests/assignments.test.js` (the `pools` meter column and `--json`
|
|
145
|
+
`pacingWindow`).
|
|
146
|
+
|
|
3
147
|
## 0.28.0 — context diet
|
|
4
148
|
|
|
5
149
|
- `workflow runs result <id> --summary` prints a compact status-loop
|
|
@@ -77,9 +221,10 @@
|
|
|
77
221
|
39,288; compact `JSON.stringify` of the parsed envelope is 57,141.
|
|
78
222
|
`summarizeV2Result` of that fixture is 3,786 bytes —
|
|
79
223
|
`tests/workflow-result-summary.test.js` prints `result-summary size:
|
|
80
|
-
full=57141 summary=3786
|
|
81
|
-
inputs as 60,790 bytes; `wc -c` of the
|
|
82
|
-
`.diet-inputs/` sums to 46,022
|
|
224
|
+
full=57141 summary=3786` (numbers re-measured in 0.28.1). The 0.28.0
|
|
225
|
+
goal recorded the integrator's inputs as 60,790 bytes; `wc -c` of the
|
|
226
|
+
seven dependency out-files under `.diet-inputs/` sums to 46,022
|
|
227
|
+
(out-surface 18,659, out-routing-cleanup
|
|
83
228
|
10,202, out-state-bugs 7,052, out-docs 6,831, out-dead-kernel 1,377,
|
|
84
229
|
out-verify-gate 974, out-dead-code 927) and the integrator task file is
|
|
85
230
|
14,768 (`wc -c .diet-inputs/task-integrate-attempt-1.md`), which
|
package/README.md
CHANGED
|
@@ -51,7 +51,12 @@ detaches safely.
|
|
|
51
51
|
passing verification.
|
|
52
52
|
2. **Pace by meter.** The scheduling resource is the subscription window:
|
|
53
53
|
elapsed% minus used%, most-behind pool wins. Pace may only promote a
|
|
54
|
-
*cheaper* pool. Lanes are work-nature, never hard-coded to pools.
|
|
54
|
+
*cheaper* pool. Lanes are work-nature, never hard-coded to pools. Which
|
|
55
|
+
window paces one pool is the subscription window that pool's connector
|
|
56
|
+
declares (`quotaWindow`: weekly for claude-code, codex and grok; monthly
|
|
57
|
+
for command-code and the kaihk pools), overridable per pool with
|
|
58
|
+
`bullswarm strategy set-subscription <pool> --quota-window <weekly|monthly>`
|
|
59
|
+
— `bullswarm pools` names it in the meter column. The
|
|
55
60
|
5-hour window never paces — it gates: a pool at or above 75% of it is
|
|
56
61
|
chosen only when no eligible pool below that line exists, and one at or
|
|
57
62
|
above 90% is not dispatched at all.
|
|
@@ -656,6 +661,10 @@ These are UTF-8 byte counts, never tokens.
|
|
|
656
661
|
requirement as `{ id, status, mandatory, evidenceCount, why }`, each action as
|
|
657
662
|
`{ id, kind, lane, effort, status, pool, model, reasoning, wallSec, outFile,
|
|
658
663
|
bytes }`, `concerns: { count, first }`, `usage`, and `next: { full, runDir, outputs }` — every output name is a basename inside `next.runDir`.
|
|
664
|
+
`--summary` is single-line JSON (`JSON.stringify`), so the bytes on the wire
|
|
665
|
+
match the 4,096-byte fitter budget. As printed by the CLI on
|
|
666
|
+
`tests/fixtures/real-result-ze5xz2.json`, the compact summary is 3,786 bytes
|
|
667
|
+
and the pretty full envelope (`--json` alone) is 60,709 bytes.
|
|
659
668
|
The full `bullswarm.workflow.result.v2` envelope is unchanged and remains the
|
|
660
669
|
default. Read it (`--json` alone) on a failed or partial run, or before judging
|
|
661
670
|
evidence. A terminal `workflow watch` prints the same compact command as
|
|
@@ -791,6 +800,25 @@ to toggle Timeline and Phases, then use Enter/Esc for agents and activity.
|
|
|
791
800
|
bullswarm workflow tui
|
|
792
801
|
```
|
|
793
802
|
|
|
803
|
+
#### Terminal glyphs
|
|
804
|
+
|
|
805
|
+
The live views draw a Braille spinner and symbol status icons. macOS
|
|
806
|
+
Terminal.app cannot render them: Andale Mono, Menlo, SF Mono, Monaco and
|
|
807
|
+
Courier New all have zero glyphs in `U+2800-U+28FF`, and none has `⧖`, so the
|
|
808
|
+
dashboard repaints a flashing `?` where each one should be. Apple Terminal is
|
|
809
|
+
detected and given a one-column ASCII table instead (`|/-\` spinner, `+`
|
|
810
|
+
succeeded, `x` failed, `:` waiting, `#` blocked). Panel borders are unchanged —
|
|
811
|
+
box drawing is present in every one of those fonts.
|
|
812
|
+
|
|
813
|
+
Override the detection either way:
|
|
814
|
+
|
|
815
|
+
```bash
|
|
816
|
+
BULLSWARM_ASCII=1 bullswarm workflow # force ascii (any terminal showing ?)
|
|
817
|
+
BULLSWARM_UNICODE=1 bullswarm workflow # force unicode (font does have them)
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
A non-UTF-8 locale, `TERM=dumb` and `TERM=linux` also select ASCII.
|
|
821
|
+
|
|
794
822
|
The same dashboard is agent-friendly and never requires a TTY when used with
|
|
795
823
|
JSON/control flags:
|
|
796
824
|
|
|
@@ -879,6 +907,19 @@ runtime value and uses its matching connector rate metadata for the attempt's
|
|
|
879
907
|
cost estimate. Unknown or provider-hidden model identity remains explicitly
|
|
880
908
|
unknown.
|
|
881
909
|
|
|
910
|
+
## Repository operations
|
|
911
|
+
|
|
912
|
+
`scripts/issue-watcher/` is a launchd agent that watches this repository's
|
|
913
|
+
GitHub issues. Idle it costs zero model tokens — a pass is one `gh issue
|
|
914
|
+
list` and nothing else. When an issue arrives that is new since install, it
|
|
915
|
+
delegates one analyze run to triage it (label plus a plain-words comment),
|
|
916
|
+
and for a bug it judged fixable at confidence >= 0.7 one build run to fix
|
|
917
|
+
it, verifying the result itself (suite green in its own clone, diff
|
|
918
|
+
non-empty) before pushing `fix/issue-<n>` and opening a pull request. Both
|
|
919
|
+
delegations route through `bullswarm run` on the owner's subscriptions. It
|
|
920
|
+
never merges, closes, or releases. Install, guards, cost model and paths:
|
|
921
|
+
[`scripts/issue-watcher/README.md`](scripts/issue-watcher/README.md).
|
|
922
|
+
|
|
882
923
|
## The verdict
|
|
883
924
|
|
|
884
925
|
```json
|