bullswarm 0.17.0 → 0.18.1
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 +80 -1
- package/README.md +23 -6
- package/docs/experiments/2026-08-29-dogfood-bullswarm-builds-bullswarm.md +38 -0
- package/package.json +1 -1
- package/skill/SKILL.md +15 -5
- package/src/help.js +10 -4
- package/src/workflow/cli.js +98 -10
- package/src/workflow/dashboard.js +64 -18
- package/src/workflow/decision.js +41 -3
- package/src/workflow/goal.js +80 -12
- package/src/workflow/result.js +78 -1
- package/src/workflow/runner.js +62 -11
- package/src/workflow/runtime.js +91 -10
- package/src/workflow/validate.js +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,85 @@
|
|
|
1
1
|
# bullswarm changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.18.1 — original-goal verification and denser timeline
|
|
4
|
+
|
|
5
|
+
- Goal workflows now derive a durable requirement ledger from the original
|
|
6
|
+
user goal. Planner verify actions declare which requirements they cover, and
|
|
7
|
+
neither explicit nor program-level completion is accepted until every
|
|
8
|
+
requirement has a successful verifier with specific evidence. The verifier
|
|
9
|
+
receives the original goal from the runtime, so a reduced planner scope can
|
|
10
|
+
no longer silently omit requested APIs, events, tests, or documentation.
|
|
11
|
+
- Successful verifier concerns are preserved in a verified
|
|
12
|
+
`completed_with_concerns` result instead of being discarded or triggering
|
|
13
|
+
unnecessary follow-up spending.
|
|
14
|
+
- The human timeline calls its first accepted planner decision `plan created`,
|
|
15
|
+
later decisions `plan updated`, and the final one `completion confirmed`.
|
|
16
|
+
Execution milestones are rendered as one dense block without blank rows.
|
|
17
|
+
Finished workflows now say `No agents running · workflow finished` and
|
|
18
|
+
`Workflow finished · result ready` instead of control-plane terminology.
|
|
19
|
+
|
|
20
|
+
## 0.18.0 — exact routes, cheaper plans, clearer results
|
|
21
|
+
|
|
22
|
+
- `workflow goal` can now guarantee an exact planner model and a separate exact
|
|
23
|
+
worker route with `--orchestrator-model`, `--worker-pool`, and
|
|
24
|
+
`--worker-model`. The worker lock is runtime-owned and propagates through the
|
|
25
|
+
scout, ordinary actions, fan-out items, verification repairs, reverification,
|
|
26
|
+
and extraction helpers; unsupported or excluded models fail closed instead
|
|
27
|
+
of silently substituting another model.
|
|
28
|
+
- An action-bearing planner `proceed` is normalized to the schema-equivalent
|
|
29
|
+
`needs_more_work` program before validation. This removes a redundant
|
|
30
|
+
correction turn without changing the proposed graph or weakening any safety
|
|
31
|
+
check (the prior real run spent four frontier planner turns correcting this
|
|
32
|
+
exact representation mismatch).
|
|
33
|
+
- `workflow runs result` now selects the latest successful verifier that
|
|
34
|
+
transitively covers the delivery, so a final suite verifier depending on
|
|
35
|
+
unit verifiers is surfaced ahead of a narrower direct unit check.
|
|
36
|
+
- The same result envelope now adds a backward-compatible `deliveries[]`
|
|
37
|
+
frontier for parallel multi-worker outcomes while preserving the singular
|
|
38
|
+
`delivery` field for existing callers.
|
|
39
|
+
- The autonomous planner now batches cheap homogeneous edits instead of paying
|
|
40
|
+
for a worker and unit verifier per tiny file; substantial independent units
|
|
41
|
+
still fan out and retain focused verification before the final suite.
|
|
42
|
+
- Narrow SSH and phone terminals now open on a full-width workflow timeline;
|
|
43
|
+
`t` toggles between that overview and the phase browser without affecting the
|
|
44
|
+
existing Enter/Esc agent drill-down.
|
|
45
|
+
- Goal-level `--orchestrator <pool>` is now a preference with immediate
|
|
46
|
+
fallback when that pool is quota-gated, ineligible, or unavailable. Exact
|
|
47
|
+
provider testing moves to `--strict-orchestrator <pool>`. Quota waits now
|
|
48
|
+
refresh the durable runner heartbeat at least every 30 seconds, preventing a
|
|
49
|
+
live waiting run from being falsely reconciled as interrupted and making
|
|
50
|
+
cooperative cancellation responsive during long meter-poll intervals.
|
|
51
|
+
|
|
52
|
+
- The runtime now owns the acceptance bar for every verify and re-verify. Each
|
|
53
|
+
verifier's instructions end with a fixed "Acceptance standard (runtime-owned;
|
|
54
|
+
it overrides any stricter rule in the instructions above)": `ok:false` means
|
|
55
|
+
the work is unusable — its acceptance command fails, a required deliverable
|
|
56
|
+
is missing, or the answer is nonsense — and everything else (style, scope,
|
|
57
|
+
cosmetic mismatches, process rules the goal never stated such as append-only,
|
|
58
|
+
files changed by other actions in the shared tree) goes in `concerns` under
|
|
59
|
+
`ok:true`. A re-verify rejects only when the work is still unusable or the
|
|
60
|
+
repair broke the acceptance checks. Direction from the user after `8ebi8a`:
|
|
61
|
+
"unless it is completely nonsense or unable to finish I don't see a reason to
|
|
62
|
+
reject so easily".
|
|
63
|
+
- Repair prompts carry a runtime-owned shared-tree rule: edit only the files
|
|
64
|
+
the reviewed work owns; a concern about other files is not the repair's to
|
|
65
|
+
resolve; never revert, checkout or delete other actions' changes. Earned on
|
|
66
|
+
`8ebi8a`: `verify-docs` rejected on a repo-wide `git diff --stat` scope check
|
|
67
|
+
while siblings were writing, and its repair reverted five `src/` files it did
|
|
68
|
+
not own to satisfy the concern.
|
|
69
|
+
- Planner contract: rule 2 requires exactly one owner per file, including any
|
|
70
|
+
existing test the change breaks (the `workflow-adaptive.test.js:206` gap for
|
|
71
|
+
the fourth time); rule 7 restates the lenient bar above; the validator line
|
|
72
|
+
now says ids are unique across the whole run, finished and failed actions
|
|
73
|
+
included (turn 2 of `8ebi8a` re-proposed the blocked id `verify-suite` and
|
|
74
|
+
spent a 97 s correction turn on it).
|
|
75
|
+
- Goal-4 rerun on `7724da1` (`8ebi8a`, rule 7 + PR #5): 42 min 03 s, three
|
|
76
|
+
planner turns (775 s, 31 %), parallelism 1.34, 23 dispatches (20 on
|
|
77
|
+
`kaihk/gpt-5.6-luna`), three repair rounds each rejected on re-verify for
|
|
78
|
+
reasons the prompts caused, tail of five actions blocked, recovery program
|
|
79
|
+
auto-completed, 315/315, existing tests +179/−1. Goal-4 line:
|
|
80
|
+
44 → 72 → 37 → 25 → 36 → 42 min.
|
|
81
|
+
|
|
82
|
+
## 0.17.0 — the timeline tells the execution story
|
|
4
83
|
|
|
5
84
|
- Workflow timeline (PR #5) hardened after a 16-agent adversarial review against
|
|
6
85
|
real run state (23 findings, 21 confirmed): worker rows now name their phase
|
package/README.md
CHANGED
|
@@ -188,8 +188,23 @@ Resume a process-interrupted autonomous run from its persisted workflow:
|
|
|
188
188
|
bullswarm workflow goal --resume <shortId> --json
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
-
`--orchestrator <pool>`
|
|
192
|
-
|
|
191
|
+
`--orchestrator <pool>` expresses a preference and immediately falls back to
|
|
192
|
+
another eligible pool if that provider is quota-gated or unavailable. Ordinary
|
|
193
|
+
use can leave selection on `auto`. For controlled provider QA only,
|
|
194
|
+
`--strict-orchestrator <pool>` requires that exact pool and may wait for its
|
|
195
|
+
quota window. Controlled comparisons can additionally pin the exact planner
|
|
196
|
+
and worker routes without changing global strategy:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
bullswarm workflow goal "Implement and verify the change" --cwd . \
|
|
200
|
+
--strict-orchestrator codex --orchestrator-model gpt-5.6-sol \
|
|
201
|
+
--worker-pool opencode2 --worker-model kaihk/gpt-5.6-luna
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The worker lock covers the scout, ordinary runs, fan-out items, repairs,
|
|
205
|
+
re-verification, and runtime extraction helpers. A pool that cannot guarantee
|
|
206
|
+
the requested model is ineligible rather than silently substituting another
|
|
207
|
+
model. `--max-agents` and `--max-workflow-seconds` are
|
|
193
208
|
advisory planning targets; `--max-expansion-rounds` is also an advisory
|
|
194
209
|
convergence target. Hard structural safeguards are adjusted with
|
|
195
210
|
`--max-actions` and `--max-items-per-expansion`.
|
|
@@ -258,8 +273,9 @@ Values accept ISO timestamps, local `YYYY-MM-DD` dates, `today`, `yesterday`,
|
|
|
258
273
|
After a workflow reaches a terminal state, agents should consume
|
|
259
274
|
`workflow runs result <id> --json` instead of probing `state.json`, task files,
|
|
260
275
|
or provider-specific output. The versioned `bullswarm.workflow.result.v1`
|
|
261
|
-
envelope
|
|
262
|
-
|
|
276
|
+
envelope retains the primary `delivery`, adds a `deliveries[]` frontier when
|
|
277
|
+
parallel workers jointly form the outcome, and identifies their strongest
|
|
278
|
+
matching verification verdict. It also includes progress, step logs, tokens, and an explicitly
|
|
263
279
|
complete-or-partial tool-call total. `runs show` remains the low-level debugging
|
|
264
280
|
surface.
|
|
265
281
|
Goal launch output includes an `instructions` handoff with four named paths:
|
|
@@ -308,7 +324,8 @@ width of `⌛` across terminal fonts. It watches ongoing runs from disk and supp
|
|
|
308
324
|
details, Esc to go back, `c` to request a confirmed cooperative stop, `r` to
|
|
309
325
|
refresh, and `q` to detach. Its responsive drill-down fits both desktop and
|
|
310
326
|
mobile SSH terminals without squeezing phase, agent, and activity into three
|
|
311
|
-
narrow columns.
|
|
327
|
+
narrow columns. Below 100 columns it opens on a full-width timeline; press `t`
|
|
328
|
+
to toggle Timeline and Phases, then use Enter/Esc for agents and activity.
|
|
312
329
|
|
|
313
330
|
```bash
|
|
314
331
|
bullswarm workflow tui
|
|
@@ -436,7 +453,7 @@ appending anything. It executes ready actions, observes their durable results,
|
|
|
436
453
|
and calls the planner again. `events.jsonl`, `state.json`, the TUI, and JSON
|
|
437
454
|
inspection expose the same plan, actions, attempts, decisions, budgets, and
|
|
438
455
|
artifacts. See `workflows/adaptive-code-review.json` for a complete example.
|
|
439
|
-
Planner actions cannot set `pool`, `addDir`, or `taskFile`. If those need to be
|
|
456
|
+
Planner actions cannot set `pool`, `model`, `addDir`, or `taskFile`. If those need to be
|
|
440
457
|
fixed by the initiator, declare them under the `decide` step's `actionDefaults`;
|
|
441
458
|
otherwise eligible capable pools are ranked by live quota surplus.
|
|
442
459
|
|
|
@@ -351,3 +351,41 @@ Cost of the false rejection: the 266 s planner turn plus the serialised tail ≈
|
|
|
351
351
|
Fix committed after the run, unreleased (`71960ae`): rule 7 — "A verify checks the goal's own acceptance criteria …
|
|
352
352
|
never add a process rule the goal does not state (append-only, tests untouched); when the implementation changes what
|
|
353
353
|
an existing assertion pins, a worker must own updating it." Proof pending a rerun on that commit.
|
|
354
|
+
|
|
355
|
+
## Run `8ebi8a` — runtime `7724da1` (rule 7 `71960ae` + PR #5 merge), luna pinned, fixture g4-bs-v6
|
|
356
|
+
|
|
357
|
+
Launched 2026-08-29 16:42 Z as the live proof of rule 7. Result: **42 min 03 s**, worse than `euh622` (36 min) and
|
|
358
|
+
`bizp4s` (25 min). Measured (`bs-g4-v6-metrics.json`): 3 planner turns / 775 s (31 % of wall; turn 2 430 s, correction
|
|
359
|
+
turn 97 s, turn 1 ≈ 248 s derived), parallelism 1.34, max 3 concurrent, 23 dispatches (20 workers on
|
|
360
|
+
`kaihk/gpt-5.6-luna`, 3 planner turns on claude-code/opus), 3 repair rounds — every re-verify rejected — 1 validator
|
|
361
|
+
correction, auto-completed by `program-completion`, `npm test` 315/315, existing tests +179/−1 (the mandated `:206`
|
|
362
|
+
extension, finally done by a named action `fix-pinned-test`).
|
|
363
|
+
|
|
364
|
+
The three rejections were each legitimate under the re-verify rule of `9af8fdf`; the defect was in the prompts the
|
|
365
|
+
planner wrote, and rule 7 did not stop it:
|
|
366
|
+
- `verify-impl` r1: real concern. Repair 1 removed `outputSchema` from `programFeatures` so the OLD assertion at
|
|
367
|
+
`workflow-adaptive.test.js:206` would pass — because `impl` was told "do NOT modify existing tests", `tests-runtime`
|
|
368
|
+
(owner of that file) was never told to extend `:206`, and `verify-impl` expected `impl` to have done it. Re-verify
|
|
369
|
+
rejected (a regression: item 5 mandates the entry). r2 re-added it; the old assertion failed again; rejected.
|
|
370
|
+
Turn 2's reason names it: "my round-1 prompt asked for it". Nobody owned the assertion — the fourth run with this gap.
|
|
371
|
+
- `verify-docs` r1: its prompt said "only those three doc files were changed by this worker (`git diff --stat`)"; the
|
|
372
|
+
repo-wide diff showed `impl`'s files, so it rejected on scope. The repair, told "Do not touch src/", still reverted
|
|
373
|
+
five `src/` files to make `git diff --name-only` show three files (its report: "git diff --name-only reports exactly
|
|
374
|
+
the three requested documentation files… 299 tests"). Re-verify rejected on the missing implementation.
|
|
375
|
+
- Tail blocked: `verify-tests-schema`, `verify-tests-runtime`, `verify-suite`, `report`, `verify-report` depended on
|
|
376
|
+
`verify-impl` (a verdict, chosen so repairs would not edit the same files) → `failed_terminal` → planner turn 2,
|
|
377
|
+
which re-proposed the blocked id `verify-suite` → validator rejection → 97 s correction → recovery program
|
|
378
|
+
`restore-src` → `fix-pinned-test` → `verify-src` / `verify-tests` → `verify-full-suite` → `final-report` →
|
|
379
|
+
`verify-final-report`, all ok.
|
|
380
|
+
|
|
381
|
+
Where the extra time went (vs `bizp4s`): ≈ 17 min in the two failed verify loops, the blocked tail, turn 2 and the
|
|
382
|
+
correction; `impl` 493 s vs 377 s is variance.
|
|
383
|
+
|
|
384
|
+
Conclusion and fix (unreleased, committed after the run): three runs in a row failed on a different planner-authored
|
|
385
|
+
constraint the goal never stated (append-only → contradictory ownership → repo-wide scope check), so contract text
|
|
386
|
+
alone is whack-a-mole. The runtime now owns the bar: every verify/re-verify instruction ends with a fixed acceptance
|
|
387
|
+
standard (ok:false = unusable; everything else is a concern under ok:true; other actions' files are never this unit's
|
|
388
|
+
defect), every repair prompt says to edit only the reviewed work's files and never revert others' changes, rule 2
|
|
389
|
+
requires one owner per file including an existing test the change breaks, and the validator line states run-wide id
|
|
390
|
+
uniqueness. Direction from the user: "unless it is completely nonsense or unable to finish I don't see a reason to
|
|
391
|
+
reject so easily". Claim to test on the next rerun: none of the three `8ebi8a` rejection reasons can produce ok:false.
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -130,8 +130,15 @@ terminal-owned execution.
|
|
|
130
130
|
The detached runner does not depend on the initiating CLI remaining alive.
|
|
131
131
|
Resume a process-interrupted run from its persisted definition with
|
|
132
132
|
`bullswarm workflow goal --resume <shortId> --json`. Leave orchestrator
|
|
133
|
-
selection automatic in normal use
|
|
134
|
-
|
|
133
|
+
selection automatic in normal use. `--orchestrator=<pool>` is a preference
|
|
134
|
+
that falls back when the pool is quota-gated or unavailable;
|
|
135
|
+
`--strict-orchestrator=<pool>` is the exact-provider control for QA and may
|
|
136
|
+
wait for that pool's quota window. For a controlled model comparison, add
|
|
137
|
+
`--orchestrator-model=<model>`, `--worker-pool=<pool>`, and
|
|
138
|
+
`--worker-model=<model>`. The worker constraints cover scout, runs, fan-out
|
|
139
|
+
items, repairs, re-verification, and extraction helpers; a pool that cannot
|
|
140
|
+
guarantee the model is excluded rather than silently substituting it.
|
|
141
|
+
`SIGTERM`/`SIGINT` cooperatively terminate the active delegate and
|
|
135
142
|
persist `interrupted`; later workflow commands also reconcile dead or stale
|
|
136
143
|
owners into that explicit resumable state.
|
|
137
144
|
|
|
@@ -223,8 +230,9 @@ ISO timestamps, local dates, today/yesterday/tomorrow/now, or durations such as
|
|
|
223
230
|
normal ongoing-only scope.
|
|
224
231
|
|
|
225
232
|
When the run is terminal, use `workflow runs result <id> --json` as the
|
|
226
|
-
handoff contract. Its versioned result envelope
|
|
227
|
-
|
|
233
|
+
handoff contract. Its versioned result envelope keeps one primary `delivery`,
|
|
234
|
+
adds every jointly delivered parallel artifact under `deliveries[]`, and points
|
|
235
|
+
to the strongest dependent verification verdict, progress, and usage. Do not guess
|
|
228
236
|
the output schema by scraping task files or assume the last provider response is
|
|
229
237
|
the deliverable; `runs show` is for low-level debugging.
|
|
230
238
|
|
|
@@ -287,6 +295,8 @@ Select Workflow Planner and press Enter, or press
|
|
|
287
295
|
reason, next action, progress, and recent semantic activity. Press `v` for the
|
|
288
296
|
durable provider session, checkpoint prompts and turns, usage, and artifact
|
|
289
297
|
paths; `v` returns to the overview and Esc returns to phases.
|
|
298
|
+
Below 100 columns the TUI opens on a full-width timeline; `t` toggles Timeline
|
|
299
|
+
and Phases, and Enter/Esc continues through agents and activity.
|
|
290
300
|
The shared state marks are `○` not started, animated Braille spinner active,
|
|
291
301
|
`⧖` waiting, `✓` finished, and `✗` failed or interrupted.
|
|
292
302
|
|
|
@@ -404,7 +414,7 @@ orchestrator for the rest of the run and tries one other eligible pool
|
|
|
404
414
|
`completed_with_concerns` with a ready best-effort artifact when useful work
|
|
405
415
|
exists, or `blocked` when it does not; neither hides failed verification.
|
|
406
416
|
Use `workflows/adaptive-code-review.json` as the starting template.
|
|
407
|
-
Planner proposals cannot choose `pool`, `addDir`, or `taskFile`. Those fields
|
|
417
|
+
Planner proposals cannot choose `pool`, `model`, `addDir`, or `taskFile`. Those fields
|
|
408
418
|
are runtime-owned. An initiator may constrain them with a decide step's
|
|
409
419
|
`actionDefaults`; absent a pinned default, normal capability and quota routing
|
|
410
420
|
selects the worker.
|
package/src/help.js
CHANGED
|
@@ -581,7 +581,11 @@ const workflowGoalText = rich({
|
|
|
581
581
|
{ flag: '--watch', desc: 'immediately follow low-noise progress until terminal; only valid for a new human-readable independent launch — cannot combine with --detach, --foreground, --json, --resume, or --request', default: 'off' },
|
|
582
582
|
{ flag: '--foreground', desc: 'keep execution attached to this terminal instead of detaching', default: 'off (detaches into a background process)' },
|
|
583
583
|
{ flag: '--json', desc: 'print the launch/report document as JSON', default: 'human-readable launch instructions' },
|
|
584
|
-
{ flag: '--orchestrator <pool|auto>', desc: '
|
|
584
|
+
{ flag: '--orchestrator <pool|auto>', desc: 'prefer this orchestrator pool for a new goal or resumed run, falling back immediately when it is quota-gated, ineligible, or unavailable', default: 'auto (capability- and quota-based selection)' },
|
|
585
|
+
{ flag: '--strict-orchestrator <pool>', desc: 'require exactly this orchestrator pool for controlled provider QA; waits when that pool is quota-gated instead of falling back; mutually exclusive with --orchestrator', default: 'off' },
|
|
586
|
+
{ flag: '--orchestrator-model <model|auto>', desc: 'pin the exact model used by the autonomous planner; only pools that can guarantee this model remain eligible', default: 'auto (effort-tier strategy or connector default)' },
|
|
587
|
+
{ flag: '--worker-pool <pool|auto>', desc: 'pin every non-planner dispatch, including scout, fan-out items, repairs, and verifiers, to one pool', default: 'auto (normal routing)' },
|
|
588
|
+
{ flag: '--worker-model <model|auto>', desc: 'pin the exact model for every non-planner dispatch; only pools that can guarantee it remain eligible', default: 'auto (effort-tier strategy or connector default)' },
|
|
585
589
|
{ flag: '--max-agents <n>', desc: 'planning target for total dispatched agents (soft, not a hard stop)', default: '30 (max 500)' },
|
|
586
590
|
{ flag: '--max-expansion-rounds <n>', desc: 'planning target for planner replanning rounds', default: '8 (max 50)' },
|
|
587
591
|
{ flag: '--max-actions <n>', desc: 'planning target for total dispatched actions', default: '40 (max 1000)' },
|
|
@@ -605,6 +609,7 @@ const workflowGoalText = rich({
|
|
|
605
609
|
],
|
|
606
610
|
examples: [
|
|
607
611
|
{ cmd: 'bullswarm workflow goal "Audit this repo for TODOs and file a one-page summary" --cwd .' },
|
|
612
|
+
{ cmd: 'bullswarm workflow goal "Implement and verify the change" --cwd . --strict-orchestrator codex --orchestrator-model gpt-5.6-sol --worker-pool opencode2 --worker-model kaihk/gpt-5.6-luna', note: 'controlled Sol-planner/Luna-worker run' },
|
|
608
613
|
],
|
|
609
614
|
next: 'bullswarm workflow watch <shortId> to follow progress, or bullswarm workflow tui for the interactive browser.',
|
|
610
615
|
});
|
|
@@ -660,7 +665,7 @@ const workflowCapabilitiesText = rich({
|
|
|
660
665
|
options: [{ flag: '--json', desc: 'accepted for consistency with other commands, but has no effect', default: 'output is always JSON regardless of this flag' }],
|
|
661
666
|
safety: ['read-only — performs live pool discovery to populate pool/meter state; nothing is written'],
|
|
662
667
|
examples: [{ cmd: 'bullswarm workflow capabilities' }],
|
|
663
|
-
next: 'bullswarm workflow goal "<goal>" --orchestrator <pool> to
|
|
668
|
+
next: 'bullswarm workflow goal "<goal>" --orchestrator <pool> to prefer one of the reported pools with fallback, or bullswarm strategy show to review model tier assignments.',
|
|
664
669
|
});
|
|
665
670
|
|
|
666
671
|
const workflowInspectText = rich({
|
|
@@ -691,6 +696,7 @@ const workflowTuiText = rich({
|
|
|
691
696
|
'--cancel writes state.json (cancelRequested=true, status=cancelling) — cooperative, not a force-kill: the workflow stops at its next safe checkpoint',
|
|
692
697
|
'inside the interactive browser, q detaches without stopping the underlying workflow; c requests the same cancellation with a confirmation prompt',
|
|
693
698
|
'the default timeline is derived from durable state and events; press v for raw action-ledger and event evidence',
|
|
699
|
+
'below 100 columns the timeline remains full-width; press t to toggle Timeline and Phases, then Enter/Esc to drill into agents and activity',
|
|
694
700
|
],
|
|
695
701
|
examples: [
|
|
696
702
|
{ cmd: 'bullswarm workflow tui', note: 'interactive run picker' },
|
|
@@ -869,8 +875,8 @@ const workflowRunsShowText = rich({
|
|
|
869
875
|
|
|
870
876
|
const workflowRunsResultText = rich({
|
|
871
877
|
usage: 'bullswarm workflow runs result <shortId|runId> [--json]',
|
|
872
|
-
purpose: 'Print the stable
|
|
873
|
-
+ 'and usage
|
|
878
|
+
purpose: 'Print the stable caller envelope: primary delivery, parallel deliveries[] frontier, '
|
|
879
|
+
+ 'strongest verification verdict, progress, and usage for one run — the intended integration point for scripts and agents.',
|
|
874
880
|
args: [{ name: '<shortId|runId>', desc: 'run identifier' }],
|
|
875
881
|
options: [{ flag: '--json', desc: 'print the full result document as JSON', default: 'human-readable summary (delivery preview truncated to 64KB)' }],
|
|
876
882
|
safety: ['read-only'],
|
package/src/workflow/cli.js
CHANGED
|
@@ -295,21 +295,83 @@ export function shouldAutoWatchGoal(opts) {
|
|
|
295
295
|
opts.json !== true && opts.resume == null && opts.request == null;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
export function applyResumeOrchestratorOverride(doc, requested) {
|
|
299
|
-
if (!requested) return doc;
|
|
300
|
-
|
|
298
|
+
export function applyResumeOrchestratorOverride(doc, requested, strictRequested = null) {
|
|
299
|
+
if (!requested && !strictRequested) return doc;
|
|
300
|
+
if (requested && strictRequested) {
|
|
301
|
+
throw new Error('--orchestrator and --strict-orchestrator are mutually exclusive');
|
|
302
|
+
}
|
|
303
|
+
const strict = Boolean(strictRequested);
|
|
304
|
+
const selected = strictRequested ?? requested;
|
|
305
|
+
const pool = selected === 'auto' ? null : selected;
|
|
301
306
|
doc.intent ??= {};
|
|
302
307
|
doc.orchestration ??= {};
|
|
303
308
|
doc.intent.requestedOrchestrator = pool ?? 'auto';
|
|
304
309
|
doc.orchestration.requestedPool = pool;
|
|
310
|
+
doc.orchestration.strictPool = strict ? pool : null;
|
|
305
311
|
doc.orchestration.selection = pool
|
|
306
|
-
? 'user-
|
|
312
|
+
? (strict ? 'user-strict-for-testing' : 'user-preferred-with-fallback')
|
|
307
313
|
: 'capability-strategy-and-quota';
|
|
308
314
|
for (const phase of doc.phases ?? []) {
|
|
309
315
|
for (const step of phase.steps ?? []) {
|
|
310
316
|
if (step.type !== 'decide') continue;
|
|
311
|
-
|
|
312
|
-
|
|
317
|
+
delete step.pool;
|
|
318
|
+
delete step.preferredPool;
|
|
319
|
+
if (pool) step[strict ? 'pool' : 'preferredPool'] = pool;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return doc;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function applyResumeModelOverrides(doc, {
|
|
326
|
+
orchestratorModel = null,
|
|
327
|
+
workerPool = null,
|
|
328
|
+
workerModel = null,
|
|
329
|
+
} = {}) {
|
|
330
|
+
const normalize = (value) => value === 'auto' ? null : value;
|
|
331
|
+
const plannerModel = normalize(orchestratorModel);
|
|
332
|
+
const workers = normalize(workerPool);
|
|
333
|
+
const workerModelLock = normalize(workerModel);
|
|
334
|
+
if (orchestratorModel == null && workerPool == null && workerModel == null) return doc;
|
|
335
|
+
doc.intent ??= {};
|
|
336
|
+
doc.orchestration ??= {};
|
|
337
|
+
if (orchestratorModel != null) {
|
|
338
|
+
doc.intent.requestedOrchestratorModel = plannerModel ?? 'auto';
|
|
339
|
+
doc.orchestration.requestedModel = plannerModel;
|
|
340
|
+
}
|
|
341
|
+
if (workerPool != null) {
|
|
342
|
+
doc.intent.requestedWorkerPool = workers ?? 'auto';
|
|
343
|
+
doc.orchestration.workerPool = workers;
|
|
344
|
+
}
|
|
345
|
+
if (workerModel != null) {
|
|
346
|
+
doc.intent.requestedWorkerModel = workerModelLock ?? 'auto';
|
|
347
|
+
doc.orchestration.workerModel = workerModelLock;
|
|
348
|
+
}
|
|
349
|
+
for (const phase of doc.phases ?? []) {
|
|
350
|
+
for (const step of phase.steps ?? []) {
|
|
351
|
+
if (step.type === 'decide') {
|
|
352
|
+
step.actionDefaults ??= {};
|
|
353
|
+
if (orchestratorModel != null) {
|
|
354
|
+
if (plannerModel) step.model = plannerModel;
|
|
355
|
+
else delete step.model;
|
|
356
|
+
}
|
|
357
|
+
if (workerPool != null) {
|
|
358
|
+
if (workers) step.actionDefaults.pool = workers;
|
|
359
|
+
else delete step.actionDefaults.pool;
|
|
360
|
+
}
|
|
361
|
+
if (workerModel != null) {
|
|
362
|
+
if (workerModelLock) step.actionDefaults.model = workerModelLock;
|
|
363
|
+
else delete step.actionDefaults.model;
|
|
364
|
+
}
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
if (workerPool != null) {
|
|
368
|
+
if (workers) step.pool = workers;
|
|
369
|
+
else delete step.pool;
|
|
370
|
+
}
|
|
371
|
+
if (workerModel != null) {
|
|
372
|
+
if (workerModelLock) step.model = workerModelLock;
|
|
373
|
+
else delete step.model;
|
|
374
|
+
}
|
|
313
375
|
}
|
|
314
376
|
}
|
|
315
377
|
return doc;
|
|
@@ -324,6 +386,10 @@ async function wfGoal(opts) {
|
|
|
324
386
|
console.error('✗ --watch is only valid for a new human-readable independent launch; do not combine it with --detach, --foreground, --json, --resume, or --request');
|
|
325
387
|
return 2;
|
|
326
388
|
}
|
|
389
|
+
if (opts.orchestrator && opts['strict-orchestrator']) {
|
|
390
|
+
console.error('✗ --orchestrator and --strict-orchestrator are mutually exclusive');
|
|
391
|
+
return 2;
|
|
392
|
+
}
|
|
327
393
|
const { names, pools } = await livePoolNames();
|
|
328
394
|
let doc;
|
|
329
395
|
let resumeRunId = null;
|
|
@@ -345,7 +411,17 @@ async function wfGoal(opts) {
|
|
|
345
411
|
console.error(`✗ cannot load durable workflow for ${resumeRunId}: ${err.message}`);
|
|
346
412
|
return 1;
|
|
347
413
|
}
|
|
348
|
-
|
|
414
|
+
try {
|
|
415
|
+
applyResumeOrchestratorOverride(doc, opts.orchestrator, opts['strict-orchestrator']);
|
|
416
|
+
applyResumeModelOverrides(doc, {
|
|
417
|
+
orchestratorModel: opts['orchestrator-model'],
|
|
418
|
+
workerPool: opts['worker-pool'],
|
|
419
|
+
workerModel: opts['worker-model'],
|
|
420
|
+
});
|
|
421
|
+
} catch (err) {
|
|
422
|
+
console.error(`✗ invalid goal options: ${err.message}`);
|
|
423
|
+
return 2;
|
|
424
|
+
}
|
|
349
425
|
} else if (opts.request) {
|
|
350
426
|
try {
|
|
351
427
|
const request = JSON.parse(readFileSync(resolve(opts.request), 'utf8'));
|
|
@@ -364,13 +440,24 @@ async function wfGoal(opts) {
|
|
|
364
440
|
console.error(goalUsage());
|
|
365
441
|
return 2;
|
|
366
442
|
}
|
|
367
|
-
const
|
|
368
|
-
|
|
443
|
+
const requestedOrchestrator = opts['strict-orchestrator'] ?? opts.orchestrator;
|
|
444
|
+
const orchestrator = requestedOrchestrator && requestedOrchestrator !== 'auto'
|
|
445
|
+
? requestedOrchestrator : null;
|
|
446
|
+
const workerPool = opts['worker-pool'] && opts['worker-pool'] !== 'auto'
|
|
447
|
+
? opts['worker-pool'] : null;
|
|
448
|
+
const workerModel = opts['worker-model'] && opts['worker-model'] !== 'auto'
|
|
449
|
+
? opts['worker-model'] : null;
|
|
450
|
+
const orchestratorModel = opts['orchestrator-model'] && opts['orchestrator-model'] !== 'auto'
|
|
451
|
+
? opts['orchestrator-model'] : null;
|
|
369
452
|
try {
|
|
370
453
|
doc = buildGoalWorkflow({
|
|
371
454
|
goal,
|
|
372
455
|
cwd: opts.cwd ?? process.cwd(),
|
|
373
456
|
orchestrator,
|
|
457
|
+
strictOrchestrator: Boolean(opts['strict-orchestrator']),
|
|
458
|
+
orchestratorModel,
|
|
459
|
+
workerPool,
|
|
460
|
+
workerModel,
|
|
374
461
|
settings: goalSettings(opts),
|
|
375
462
|
scout: !opts.noScout,
|
|
376
463
|
worktreeIsolation: loadState(BULLSWARM_DIR()).config?.worktreeIsolation ?? 'agent-decides',
|
|
@@ -616,7 +703,8 @@ async function wfInspect(opts) {
|
|
|
616
703
|
function parseFlags(argv) {
|
|
617
704
|
const out = { inputs: {}, rest: [] };
|
|
618
705
|
const valueFlags = new Set([
|
|
619
|
-
'resume', 'after', 'cwd', 'orchestrator', '
|
|
706
|
+
'resume', 'after', 'cwd', 'orchestrator', 'strict-orchestrator', 'orchestrator-model',
|
|
707
|
+
'worker-pool', 'worker-model', 'request', 'run-id',
|
|
620
708
|
'max-agents', 'max-expansion-rounds', 'max-actions',
|
|
621
709
|
'max-items-per-expansion', 'max-workflow-seconds', 'concurrency',
|
|
622
710
|
'retry-attempts', 'interval', 'heartbeat', 'message',
|
|
@@ -363,7 +363,7 @@ export function renderWorkflowTui(row, {
|
|
|
363
363
|
width = 120, height = 36, focus = 0, phaseIndex = null, agentIndex = null,
|
|
364
364
|
detailScroll = 0, message = null, confirmCancel = false,
|
|
365
365
|
controlSelected = false, orchestratorDetail = false, orchestratorVerbose = false,
|
|
366
|
-
workflowVerbose = false,
|
|
366
|
+
workflowVerbose = false, mobileTimeline = true,
|
|
367
367
|
spinnerFrame = 0,
|
|
368
368
|
} = {}) {
|
|
369
369
|
width = Math.max(20, Number(width) || 120);
|
|
@@ -392,11 +392,13 @@ export function renderWorkflowTui(row, {
|
|
|
392
392
|
: workflowVerbose
|
|
393
393
|
? ' ↑/↓ scroll · v overview · Esc back · c stop · q detach'
|
|
394
394
|
: narrow
|
|
395
|
-
?
|
|
395
|
+
? mobileTimeline && focus === 0
|
|
396
|
+
? ' ↑/↓ timeline · t phases · Enter agents · o planner · v technical · q detach'
|
|
397
|
+
: ' ↑/↓ select · t timeline · Enter inspect · Esc back · o planner · v technical · q detach'
|
|
396
398
|
: ' ↑/↓ select · PgUp/PgDn timeline · Enter inspect · ←/→ switch · v technical · q detach';
|
|
397
399
|
const rawMessageLine = message
|
|
398
400
|
? ` ${truncate(message, width - 2)}`
|
|
399
|
-
: ` ${orchestratorDetail ? `Workflow Planner ${orchestratorVerbose ? 'technical details' : 'overview'}` : workflowVerbose ? 'Workflow technical details' : focus === 0 ? (narrow ? 'Phases' : 'Timeline · auto-following newest event') : focus === 1 ? 'Agents' : 'Agent activity'} · r refresh · workflow continues after detach`;
|
|
401
|
+
: ` ${orchestratorDetail ? `Workflow Planner ${orchestratorVerbose ? 'technical details' : 'overview'}` : workflowVerbose ? 'Workflow technical details' : focus === 0 ? (narrow && !mobileTimeline ? 'Phases' : 'Timeline · auto-following newest event') : focus === 1 ? 'Agents' : 'Agent activity'} · r refresh · workflow continues after detach`;
|
|
400
402
|
const messageLine = truncate(rawMessageLine, width);
|
|
401
403
|
const bodyHeight = Math.max(10, height - header.length - 3);
|
|
402
404
|
|
|
@@ -487,7 +489,9 @@ export function renderWorkflowTui(row, {
|
|
|
487
489
|
body = joinPanels(left, renderPanel('Workflow technical details', visibleTechnical, rightWidth, bodyHeight));
|
|
488
490
|
}
|
|
489
491
|
} else if (narrow) {
|
|
490
|
-
const mobile = focus === 0
|
|
492
|
+
const mobile = focus === 0 && mobileTimeline
|
|
493
|
+
? null
|
|
494
|
+
: focus === 0
|
|
491
495
|
? {
|
|
492
496
|
title: model.orchestrator.autonomous
|
|
493
497
|
? `Workflow · ${model.phases.length} phase${model.phases.length === 1 ? '' : 's'}`
|
|
@@ -497,7 +501,9 @@ export function renderWorkflowTui(row, {
|
|
|
497
501
|
: focus === 1
|
|
498
502
|
? { title: agentTitle, lines: visibleAgents }
|
|
499
503
|
: { title: detailTitle, lines: visibleDetail };
|
|
500
|
-
body =
|
|
504
|
+
body = mobile
|
|
505
|
+
? renderPanel(mobile.title, mobile.lines, width, bodyHeight)
|
|
506
|
+
: renderWorkflowOverviewPanel(model, width, bodyHeight, spinnerFrame, detailScroll);
|
|
501
507
|
} else if (focus < 2) {
|
|
502
508
|
const left = model.orchestrator.autonomous
|
|
503
509
|
? [
|
|
@@ -582,9 +588,9 @@ function workflowTimelineLines(model, width) {
|
|
|
582
588
|
const { state, orchestrator } = model;
|
|
583
589
|
const ledger = state.actionLedger ?? [];
|
|
584
590
|
const events = [];
|
|
585
|
-
const add = (at, lines, sequence = Number.MAX_SAFE_INTEGER) => {
|
|
591
|
+
const add = (at, lines, sequence = Number.MAX_SAFE_INTEGER, group = null) => {
|
|
586
592
|
if (!at) return;
|
|
587
|
-
events.push({ at, sequence, lines: Array.isArray(lines) ? lines : [lines] });
|
|
593
|
+
events.push({ at, sequence, group, lines: Array.isArray(lines) ? lines : [lines] });
|
|
588
594
|
};
|
|
589
595
|
const scout = ledger.find((action) => action.id === 'scout');
|
|
590
596
|
add(state.startedAt, [
|
|
@@ -614,10 +620,17 @@ function workflowTimelineLines(model, width) {
|
|
|
614
620
|
const decision = decisionForPlannerAttempt(state, attempt, index, orchestrator.attempts);
|
|
615
621
|
const summary = decision?.reason ? sentencePreview(decision.reason, Math.max(30, width - 10))
|
|
616
622
|
: decision ? decisionLabel(decision.decision) : 'No accepted decision; correction or retry turn';
|
|
623
|
+
const acceptedBefore = orchestrator.attempts.slice(0, index).filter((entry, priorIndex) =>
|
|
624
|
+
decisionForPlannerAttempt(state, entry, priorIndex, orchestrator.attempts)).length;
|
|
625
|
+
const plannerLabel = !decision
|
|
626
|
+
? `planning retry #${index + 1}`
|
|
627
|
+
: decision.decision === 'complete'
|
|
628
|
+
? 'completion confirmed'
|
|
629
|
+
: acceptedBefore === 0 ? 'plan created' : `plan updated #${acceptedBefore + 1}`;
|
|
617
630
|
add(attempt.finishedAt, [
|
|
618
|
-
timelineRow(attempt.finishedAt, `◆ [Workflow Planner]
|
|
631
|
+
timelineRow(attempt.finishedAt, `◆ [Workflow Planner] ${plannerLabel}`, durationText(attempt.startedAt, attempt.finishedAt), width),
|
|
619
632
|
timelineDetail(summary, width),
|
|
620
|
-
]);
|
|
633
|
+
], Number.MAX_SAFE_INTEGER, 'execution');
|
|
621
634
|
});
|
|
622
635
|
|
|
623
636
|
const phases = new Map();
|
|
@@ -632,7 +645,7 @@ function workflowTimelineLines(model, width) {
|
|
|
632
645
|
const startedAt = realStart ?? earliestTimestamp(actions.map((action) => actionFinishedAt(state, action)));
|
|
633
646
|
if (!startedAt) continue;
|
|
634
647
|
const label = phaseLabel(name, orchestrator);
|
|
635
|
-
add(startedAt, timelineRow(startedAt, `├─ [Phase: ${label}] ${realStart ? 'started' : 'blocked'}`, '', width));
|
|
648
|
+
add(startedAt, timelineRow(startedAt, `├─ [Phase: ${label}] ${realStart ? 'started' : 'blocked'}`, '', width), Number.MAX_SAFE_INTEGER, 'execution');
|
|
636
649
|
const finished = actions
|
|
637
650
|
.filter((action) => actionFinishedAt(state, action) && TERMINAL_ACTIONS.has(effectiveActionStatus(action, state)))
|
|
638
651
|
.sort((a, b) => Date.parse(actionFinishedAt(state, a)) - Date.parse(actionFinishedAt(state, b)));
|
|
@@ -646,24 +659,24 @@ function workflowTimelineLines(model, width) {
|
|
|
646
659
|
`${branch}${statusIcon(effectiveActionStatus(action, state))} [${label}] ${action.id}`,
|
|
647
660
|
actionStarted ? durationText(actionStarted, actionFinished) : '',
|
|
648
661
|
width,
|
|
649
|
-
));
|
|
662
|
+
), Number.MAX_SAFE_INTEGER, 'execution');
|
|
650
663
|
});
|
|
651
664
|
if (finished.length === actions.length && actions.length) {
|
|
652
665
|
const finishedAt = latestTimestamp(actions.map((action) => actionFinishedAt(state, action)));
|
|
653
666
|
const failed = actions.some((action) => String(effectiveActionStatus(action, state)).startsWith('failed'));
|
|
654
|
-
add(finishedAt, timelineRow(finishedAt, `└─${failed ? '✗' : '✓'} [Phase: ${label}] completed`, `${finished.length}/${actions.length}`, width));
|
|
667
|
+
add(finishedAt, timelineRow(finishedAt, `└─${failed ? '✗' : '✓'} [Phase: ${label}] completed`, `${finished.length}/${actions.length}`, width), Number.MAX_SAFE_INTEGER, 'execution');
|
|
655
668
|
}
|
|
656
669
|
}
|
|
657
670
|
|
|
658
671
|
for (const event of model.events) {
|
|
659
672
|
const detail = timelineControlEvent(event, width);
|
|
660
|
-
if (detail) add(event.committedAt, detail, Number(event.sequence));
|
|
673
|
+
if (detail) add(event.committedAt, detail, Number(event.sequence), event.type.startsWith('decision.') ? 'execution' : null);
|
|
661
674
|
}
|
|
662
675
|
|
|
663
676
|
events.sort((a, b) => Date.parse(a.at) - Date.parse(b.at) || a.sequence - b.sequence);
|
|
664
677
|
const lines = [];
|
|
665
678
|
events.forEach((event, index) => {
|
|
666
|
-
if (index) lines.push('');
|
|
679
|
+
if (index && (!event.group || event.group !== events[index - 1].group)) lines.push('');
|
|
667
680
|
lines.push(...event.lines);
|
|
668
681
|
});
|
|
669
682
|
return { lines: lines.length ? lines : ['Waiting for the first durable workflow milestone'], milestoneCount: events.length };
|
|
@@ -751,13 +764,35 @@ function workflowLiveLines(model, width, spinnerFrame) {
|
|
|
751
764
|
if (stream) lines.push(` ${stream}`);
|
|
752
765
|
lines.push('');
|
|
753
766
|
}
|
|
754
|
-
if (!lines.length) lines.push(state.finishedAt
|
|
767
|
+
if (!lines.length) lines.push(state.finishedAt
|
|
768
|
+
? `${statusIcon(state.status)} No agents running · ${terminalWorkflowLabel(state.status)}`
|
|
769
|
+
: '⧖ Waiting for the next dispatch');
|
|
755
770
|
return { lines, running, waiting };
|
|
756
771
|
}
|
|
757
772
|
|
|
773
|
+
function terminalWorkflowLabel(status) {
|
|
774
|
+
if (status === 'completed') return 'workflow finished';
|
|
775
|
+
if (status === 'completed_with_concerns') return 'workflow finished with concerns';
|
|
776
|
+
if (status === 'blocked') return 'workflow stopped with blockers';
|
|
777
|
+
if (status === 'failed') return 'workflow failed';
|
|
778
|
+
if (status === 'cancelled') return 'workflow cancelled';
|
|
779
|
+
if (status === 'interrupted') return 'workflow interrupted';
|
|
780
|
+
return 'workflow stopped';
|
|
781
|
+
}
|
|
782
|
+
|
|
758
783
|
function workflowNextLines(model, width) {
|
|
759
784
|
const { state, orchestrator } = model;
|
|
760
|
-
if (state.finishedAt)
|
|
785
|
+
if (state.finishedAt) {
|
|
786
|
+
const next = state.status === 'completed' || state.status === 'completed_with_concerns'
|
|
787
|
+
? 'result ready'
|
|
788
|
+
: state.status === 'blocked' ? 'review blockers and partial work'
|
|
789
|
+
: state.status === 'failed' ? 'inspect the failure before using partial work'
|
|
790
|
+
: state.status === 'cancelled' ? 'review any partial work'
|
|
791
|
+
: state.status === 'interrupted' ? 'resume the workflow or inspect partial work'
|
|
792
|
+
: 'inspect the workflow result';
|
|
793
|
+
const label = terminalWorkflowLabel(state.status);
|
|
794
|
+
return [truncate(`${statusIcon(state.status)} ${label[0].toUpperCase()}${label.slice(1)} · ${next}`, width)];
|
|
795
|
+
}
|
|
761
796
|
const ledger = state.actionLedger ?? [];
|
|
762
797
|
const pending = ledger.find((action) => action.id !== 'scout'
|
|
763
798
|
&& action.id !== orchestrator.actionId
|
|
@@ -1228,6 +1263,7 @@ export async function runDashboard(bullswarmDir, {
|
|
|
1228
1263
|
orchestratorDetail: false,
|
|
1229
1264
|
orchestratorVerbose: false,
|
|
1230
1265
|
workflowVerbose: false,
|
|
1266
|
+
mobileTimeline: true,
|
|
1231
1267
|
spinnerFrame: 0,
|
|
1232
1268
|
};
|
|
1233
1269
|
const paintUnsafe = () => {
|
|
@@ -1298,8 +1334,10 @@ export async function runDashboard(bullswarmDir, {
|
|
|
1298
1334
|
}
|
|
1299
1335
|
const row = detailRow(bullswarmDir, selectedRunId);
|
|
1300
1336
|
const model = workflowPanelModel(row, { phaseIndex: ui.phaseIndex, agentIndex: ui.agentIndex });
|
|
1301
|
-
|
|
1302
|
-
|
|
1337
|
+
const narrowTimeline = output.columns < 100 && ui.mobileTimeline && ui.focus === 0;
|
|
1338
|
+
if (ui.orchestratorDetail || ui.workflowVerbose || narrowTimeline) {
|
|
1339
|
+
if (narrowTimeline) ui.detailScroll = Math.max(0, ui.detailScroll - delta);
|
|
1340
|
+
else ui.detailScroll = Math.max(0, ui.detailScroll + delta);
|
|
1303
1341
|
return paint();
|
|
1304
1342
|
}
|
|
1305
1343
|
if (ui.focus === 0) {
|
|
@@ -1396,6 +1434,14 @@ export async function runDashboard(bullswarmDir, {
|
|
|
1396
1434
|
} else message = 'This workflow has no autonomous orchestrator thread.';
|
|
1397
1435
|
return paint();
|
|
1398
1436
|
}
|
|
1437
|
+
if (key === 't' && detail && output.columns < 100 && !ui.orchestratorDetail && !ui.workflowVerbose) {
|
|
1438
|
+
ui.mobileTimeline = !ui.mobileTimeline;
|
|
1439
|
+
ui.focus = 0;
|
|
1440
|
+
ui.controlSelected = false;
|
|
1441
|
+
ui.detailScroll = 0;
|
|
1442
|
+
message = null;
|
|
1443
|
+
return paint();
|
|
1444
|
+
}
|
|
1399
1445
|
if (key === '1' && detail) { ui.focus = 0; return paint(); }
|
|
1400
1446
|
if (key === '2' && detail) { ui.focus = 1; return paint(); }
|
|
1401
1447
|
if (key === '3' && detail) { ui.focus = 2; return paint(); }
|