bullswarm 0.10.4 → 0.10.6
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/AGENTS.md +1 -1
- package/CHANGELOG.md +14 -0
- package/connectors/echo.json +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +3 -3
- package/src/cli.js +12 -7
- package/src/lib/config.js +2 -1
- package/src/setup.js +45 -7
package/AGENTS.md
CHANGED
|
@@ -37,7 +37,7 @@ content. Published as `bullswarm` on npm.
|
|
|
37
37
|
## Development
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
npm test #
|
|
40
|
+
npm test # 261 tests, no network needed (meters read from cache)
|
|
41
41
|
node bin/bullswarm.js doctor --json # readiness report
|
|
42
42
|
node bin/bullswarm.js workflow list # discover workflows
|
|
43
43
|
node bin/bullswarm.js workflow runs # ongoing workflow instances
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# bullswarm changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.6 — safe setup and boolean flags
|
|
4
|
+
|
|
5
|
+
- Marked the deterministic `echo` connector as a test fixture and excluded it
|
|
6
|
+
from automatic setup, routing suggestions, connector readiness, and delegate
|
|
7
|
+
readiness. Existing installations receive a one-time migration that disables
|
|
8
|
+
an accidentally enabled fixture while preserving later explicit choices.
|
|
9
|
+
- Interactive setup labels test fixtures and defaults them to disabled;
|
|
10
|
+
`doctor` now requires at least one real enabled delegate instead of treating
|
|
11
|
+
canned fixture output as offload capability.
|
|
12
|
+
- Made top-level boolean flags explicit so options such as `--no-caller`,
|
|
13
|
+
`--force`, and `--dry-run` cannot swallow a following positional task.
|
|
14
|
+
- Thanks to @kwunlokng for reporting both defect classes and supplying focused
|
|
15
|
+
reproductions in #1, #2, and #3.
|
|
16
|
+
|
|
3
17
|
## 0.10.4 — durable interactive workflow viewer
|
|
4
18
|
|
|
5
19
|
- Added `workflow runs result <id> [--json]`, a stable
|
package/connectors/echo.json
CHANGED
|
@@ -23,6 +23,6 @@
|
|
|
23
23
|
"pricingSource": "local deterministic fixture",
|
|
24
24
|
"pricingUpdatedAt": "2026-08-27"
|
|
25
25
|
}],
|
|
26
|
-
"flags": { "stealth": false },
|
|
26
|
+
"flags": { "stealth": false, "testFixture": true },
|
|
27
27
|
"$comment-timeout": "No implicit wall-clock timeout; callers may opt in with --timeout or workflow step.timeoutSec"
|
|
28
28
|
}
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -353,8 +353,8 @@ work*: "In 200 words: (1) the top 3 risks, (2) cited file paths,
|
|
|
353
353
|
You do NOT need to set up bullswarm. Every verb self-initializes:
|
|
354
354
|
|
|
355
355
|
- Missing `~/.bullswarm/`? Created on first call.
|
|
356
|
-
- No enabled pools? `autoSetup` enables every installed agent CLI
|
|
357
|
-
|
|
356
|
+
- No enabled pools? `autoSetup` enables every installed real agent CLI. The
|
|
357
|
+
deterministic `echo` connector is a test fixture and is never auto-enabled.
|
|
358
358
|
- `bullswarm doctor --json` returns a readiness report; non-zero exit
|
|
359
359
|
only on a real problem. The first call to `doctor` self-heals.
|
|
360
360
|
|
|
@@ -376,7 +376,7 @@ of exact commands to fix anything missing.
|
|
|
376
376
|
### "Audit every file in this directory"
|
|
377
377
|
|
|
378
378
|
A discover-based fanout requires the discover delegate to return a bare
|
|
379
|
-
JSON array in its output file. The built-in `echo` pool is a
|
|
379
|
+
JSON array in its output file. The built-in `echo` pool is a disabled test
|
|
380
380
|
fixture and deliberately returns prose, so use a real provider for this
|
|
381
381
|
variant:
|
|
382
382
|
|
package/src/cli.js
CHANGED
|
@@ -29,14 +29,18 @@ export function getBullswarmDir() {
|
|
|
29
29
|
// call getBullswarmDir() so BULLSWARM_HOME is honored at invocation time.
|
|
30
30
|
export const BULLSWARM_DIR = getBullswarmDir();
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
const BOOLEAN_FLAGS = new Set([
|
|
33
|
+
'json', 'force', 'no-caller', 'yes', 'strategy', 'integrate', 'dry-run',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
export function parseArgs(argv) {
|
|
33
37
|
const args = {};
|
|
34
38
|
const rest = [];
|
|
35
39
|
for (let i = 0; i < argv.length; i++) {
|
|
36
40
|
if (argv[i].startsWith('--')) {
|
|
37
41
|
const eq = argv[i].indexOf('=');
|
|
38
42
|
const key = argv[i].slice(2, eq > 0 ? eq : undefined);
|
|
39
|
-
if (key
|
|
43
|
+
if (BOOLEAN_FLAGS.has(key)) args[key] = true;
|
|
40
44
|
else if (eq > 0) args[key] = argv[i].slice(eq + 1);
|
|
41
45
|
else if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) args[key] = argv[++i];
|
|
42
46
|
else args[key] = true;
|
|
@@ -355,12 +359,12 @@ async function cmdDoctor(opts) {
|
|
|
355
359
|
}
|
|
356
360
|
|
|
357
361
|
const discovered = discoverConnectors();
|
|
358
|
-
const found = discovered.filter((d) => d.discovered && !d.broken);
|
|
362
|
+
const found = discovered.filter((d) => d.discovered && !d.broken && !d.testFixture);
|
|
359
363
|
checks.push({
|
|
360
364
|
id: 'connectors',
|
|
361
365
|
ok: found.length > 0,
|
|
362
366
|
detail: `${found.length} agent CLI(s) found: ${found.map((d) => d.name).join(', ') || '(none)'}`,
|
|
363
|
-
fix: found.length ? null : 'install at least one agent CLI (codex, grok, opencode…)
|
|
367
|
+
fix: found.length ? null : 'install at least one agent CLI (codex, grok, opencode…)',
|
|
364
368
|
});
|
|
365
369
|
|
|
366
370
|
try {
|
|
@@ -369,6 +373,7 @@ async function cmdDoctor(opts) {
|
|
|
369
373
|
});
|
|
370
374
|
const live = pools.filter((p) => p.meterSource === 'live' || p.meterSource === 'cache');
|
|
371
375
|
const enabled = pools.filter((p) => p.enabled);
|
|
376
|
+
const delegates = enabled.filter((p) => !p.testFixture);
|
|
372
377
|
checks.push({
|
|
373
378
|
id: 'meters',
|
|
374
379
|
ok: enabled.length > 0,
|
|
@@ -377,9 +382,9 @@ async function cmdDoctor(opts) {
|
|
|
377
382
|
});
|
|
378
383
|
checks.push({
|
|
379
384
|
id: 'offload-capable',
|
|
380
|
-
ok:
|
|
381
|
-
detail: `enabled pools: ${
|
|
382
|
-
fix: null,
|
|
385
|
+
ok: delegates.length > 0,
|
|
386
|
+
detail: `enabled delegate pools: ${delegates.map((p) => p.name).join(', ') || 'none'}`,
|
|
387
|
+
fix: delegates.length ? null : 'install an agent CLI and run bullswarm setup --yes',
|
|
383
388
|
});
|
|
384
389
|
} catch (err) {
|
|
385
390
|
checks.push({ id: 'meters', ok: false, detail: err.message, fix: 'check network / re-run' });
|
package/src/lib/config.js
CHANGED
|
@@ -45,7 +45,8 @@ export function buildPools(bullswarmDir, now = Date.now(), readings = {}) {
|
|
|
45
45
|
const pool = {
|
|
46
46
|
name,
|
|
47
47
|
connector: conn,
|
|
48
|
-
|
|
48
|
+
testFixture: conn.flags?.testFixture === true,
|
|
49
|
+
enabled: conn.flags?.testFixture === true ? ps.enabled === true : ps.enabled !== false,
|
|
49
50
|
costRank: conn.costRank ?? 5,
|
|
50
51
|
lanes: conn.lanes,
|
|
51
52
|
capabilities: conn.capabilities ?? [],
|
package/src/setup.js
CHANGED
|
@@ -97,6 +97,7 @@ export function discoverConnectors() {
|
|
|
97
97
|
meter: conn.meter?.type ?? 'none',
|
|
98
98
|
costRank: conn.costRank,
|
|
99
99
|
lanes: conn.lanes,
|
|
100
|
+
testFixture: conn.flags?.testFixture === true,
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
103
|
return found;
|
|
@@ -187,6 +188,10 @@ export function upgradeConnectorMetadata(bullswarmDir) {
|
|
|
187
188
|
installed.model = packaged.model;
|
|
188
189
|
changed = true;
|
|
189
190
|
}
|
|
191
|
+
if (packaged.flags?.testFixture === true && installed.flags?.testFixture !== true) {
|
|
192
|
+
installed.flags = { ...(installed.flags ?? {}), testFixture: true };
|
|
193
|
+
changed = true;
|
|
194
|
+
}
|
|
190
195
|
if (installed.eventStream == null && packaged.eventStream != null) {
|
|
191
196
|
installed.eventStream = packaged.eventStream;
|
|
192
197
|
// Older packaged connectors used stdout extraction. Once JSONL flags
|
|
@@ -218,6 +223,34 @@ export function upgradeConnectorMetadata(bullswarmDir) {
|
|
|
218
223
|
return upgraded;
|
|
219
224
|
}
|
|
220
225
|
|
|
226
|
+
// One-time safety migration for installations created before connectors could
|
|
227
|
+
// identify deterministic test fixtures. Explicit choices made after this
|
|
228
|
+
// migration remain respected.
|
|
229
|
+
export function migrateTestFixturePools(bullswarmDir) {
|
|
230
|
+
const state = loadState(bullswarmDir);
|
|
231
|
+
state.config ??= {};
|
|
232
|
+
if (state.config.testFixturesMigrated === true) return [];
|
|
233
|
+
|
|
234
|
+
const connectorsDir = join(bullswarmDir, 'connectors');
|
|
235
|
+
const disabled = [];
|
|
236
|
+
if (existsSync(connectorsDir)) {
|
|
237
|
+
for (const file of readdirSync(connectorsDir)) {
|
|
238
|
+
if (!file.endsWith('.json') || file.startsWith('_')) continue;
|
|
239
|
+
try {
|
|
240
|
+
const connector = JSON.parse(readFileSync(join(connectorsDir, file), 'utf8'));
|
|
241
|
+
if (connector.flags?.testFixture !== true) continue;
|
|
242
|
+
state.pools ??= {};
|
|
243
|
+
state.pools[connector.name] ??= {};
|
|
244
|
+
if (state.pools[connector.name].enabled !== false) disabled.push(connector.name);
|
|
245
|
+
state.pools[connector.name].enabled = false;
|
|
246
|
+
} catch { /* connector repair owns malformed files */ }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
state.config.testFixturesMigrated = true;
|
|
250
|
+
saveState(bullswarmDir, state);
|
|
251
|
+
return disabled;
|
|
252
|
+
}
|
|
253
|
+
|
|
221
254
|
// --- auto-setup ---------------------------------------------------------------
|
|
222
255
|
// Zero-touch initialization: enable every discovered pool, write config,
|
|
223
256
|
// never prompt. Used by `setup --yes`, by any verb on first use, and by
|
|
@@ -229,13 +262,12 @@ export function autoSetup(bullswarmDir, { reason = 'auto' } = {}) {
|
|
|
229
262
|
const discovered = discoverConnectors();
|
|
230
263
|
const repaired = repairConnectors(bullswarmDir);
|
|
231
264
|
|
|
232
|
-
const usable = discovered.filter((d) => !d.broken && d.discovered);
|
|
233
|
-
// Always include the deterministic echo pool so offloading works even on
|
|
234
|
-
// a machine with zero other agent CLIs installed.
|
|
265
|
+
const usable = discovered.filter((d) => !d.broken && d.discovered && !d.testFixture);
|
|
235
266
|
const enabled = new Set(usable.map((d) => d.name));
|
|
236
|
-
enabled.add('echo');
|
|
237
267
|
|
|
238
268
|
state.pools ??= {};
|
|
269
|
+
state.config ??= {};
|
|
270
|
+
state.config.testFixturesMigrated = true;
|
|
239
271
|
for (const d of discovered.filter((x) => !x.broken)) {
|
|
240
272
|
state.pools[d.name] ??= {};
|
|
241
273
|
state.pools[d.name].enabled = enabled.has(d.name);
|
|
@@ -275,6 +307,7 @@ export function isConfigured(bullswarmDir) {
|
|
|
275
307
|
export function ensureSetup(bullswarmDir) {
|
|
276
308
|
if (isConfigured(bullswarmDir)) {
|
|
277
309
|
upgradeConnectorMetadata(bullswarmDir);
|
|
310
|
+
migrateTestFixturePools(bullswarmDir);
|
|
278
311
|
return null;
|
|
279
312
|
}
|
|
280
313
|
return autoSetup(bullswarmDir, { reason: 'first-use' });
|
|
@@ -306,7 +339,7 @@ export async function runWizard(bullswarmDir, opts = {}) {
|
|
|
306
339
|
? 'quota: unmetered'
|
|
307
340
|
: `quota: ${d.meter} window (burn rate: learning)`;
|
|
308
341
|
console.log(
|
|
309
|
-
` ${d.name.padEnd(14)} ${d.discovered ? 'found' : 'not found'} ${meter}`,
|
|
342
|
+
` ${d.name.padEnd(14)} ${d.discovered ? 'found' : 'not found'} ${meter}${d.testFixture ? ' TEST FIXTURE' : ''}`,
|
|
310
343
|
);
|
|
311
344
|
}
|
|
312
345
|
console.log('');
|
|
@@ -314,8 +347,11 @@ export async function runWizard(bullswarmDir, opts = {}) {
|
|
|
314
347
|
// 2. Toggle pools
|
|
315
348
|
const enabled = [];
|
|
316
349
|
for (const d of discovered.filter((x) => !x.broken && x.discovered)) {
|
|
317
|
-
const
|
|
318
|
-
|
|
350
|
+
const prompt = d.testFixture
|
|
351
|
+
? `enable ${d.name} test fixture? [y/N] `
|
|
352
|
+
: `enable ${d.name}? [Y/n] `;
|
|
353
|
+
const ans = (await rl.question(prompt)).trim().toLowerCase();
|
|
354
|
+
if (d.testFixture ? (ans === 'y' || ans === 'yes') : ans !== 'n') enabled.push(d.name);
|
|
319
355
|
}
|
|
320
356
|
|
|
321
357
|
if (enabled.length === 0) {
|
|
@@ -332,6 +368,8 @@ export async function runWizard(bullswarmDir, opts = {}) {
|
|
|
332
368
|
mkdirSync(join(bullswarmDir, 'connectors'), { recursive: true });
|
|
333
369
|
const repaired = repairConnectors(bullswarmDir);
|
|
334
370
|
state.pools ??= {};
|
|
371
|
+
state.config ??= {};
|
|
372
|
+
state.config.testFixturesMigrated = true;
|
|
335
373
|
for (const d of discovered.filter((x) => !x.broken)) {
|
|
336
374
|
state.pools[d.name] ??= {};
|
|
337
375
|
state.pools[d.name].enabled = enabled.includes(d.name);
|