bullswarm 0.10.0 → 0.10.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 +12 -0
- package/README.md +11 -0
- package/package.json +1 -1
- package/skill/SKILL.md +9 -0
- package/src/workflow/runs-cli.js +116 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# bullswarm changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.1 — initiated-time workflow history search
|
|
4
|
+
|
|
5
|
+
- Added `workflow runs --since <time> --until <time>` filtering against the
|
|
6
|
+
workflow's initiation timestamp (`startedAt`), with an inclusive lower bound
|
|
7
|
+
and exclusive upper bound.
|
|
8
|
+
- Added `--from`/`--to` and `--started-after`/`--started-before` aliases plus
|
|
9
|
+
ISO timestamps, local dates, calendar keywords, and relative durations such
|
|
10
|
+
as `7d`.
|
|
11
|
+
- Historical listing now falls back to `report.startedAt` when an older state
|
|
12
|
+
record lacks its initiation timestamp, and JSON output reports the normalized
|
|
13
|
+
range used for the audit.
|
|
14
|
+
|
|
3
15
|
## 0.10.0 — battle-tested advisory orchestration and agent activity
|
|
4
16
|
|
|
5
17
|
- Made `maxAgents` and `maxWorkflowSeconds` advisory planning targets instead
|
package/README.md
CHANGED
|
@@ -173,6 +173,9 @@ bullswarm workflow runs # ongoing only (default)
|
|
|
173
173
|
bullswarm workflow runs --all # ongoing + historical
|
|
174
174
|
bullswarm workflow runs --historical # only historical
|
|
175
175
|
bullswarm workflow runs --name audit-code # filter by workflow
|
|
176
|
+
bullswarm workflow runs --all --since 7d # initiated in the last 7 days
|
|
177
|
+
bullswarm workflow runs --historical --since yesterday --until today
|
|
178
|
+
bullswarm workflow runs --all --from 2026-08-20 --to 2026-08-27
|
|
176
179
|
bullswarm workflow runs --limit 20 # cap the result count
|
|
177
180
|
bullswarm workflow runs show <shortId> # state + report + summary
|
|
178
181
|
bullswarm runs show <shortId> # top-level shorthand
|
|
@@ -182,6 +185,14 @@ bullswarm workflow runs delete <shortId> --yes # remove the run dir
|
|
|
182
185
|
bullswarm workflow run audit-code --resume <shortId>
|
|
183
186
|
```
|
|
184
187
|
|
|
188
|
+
Run-history time filters always compare when the workflow was initiated
|
|
189
|
+
(`startedAt`), never when it finished. `--since` is inclusive and `--until` is
|
|
190
|
+
exclusive; `--started-after`/`--from` and `--started-before`/`--to` are aliases.
|
|
191
|
+
Values accept ISO timestamps, local `YYYY-MM-DD` dates, `today`, `yesterday`,
|
|
192
|
+
`tomorrow`, `now`, or relative durations such as `30m`, `24h`, `7d`, and `2w`.
|
|
193
|
+
Time filters preserve the existing scope, so use `--all` or `--historical` when
|
|
194
|
+
auditing completed runs.
|
|
195
|
+
|
|
185
196
|
### Live workflow dashboard
|
|
186
197
|
|
|
187
198
|
For ordinary observation, use the non-interactive watcher. It prints only when
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -166,10 +166,19 @@ Every run gets a 6-character shortId (Crockford-style alphabet, no
|
|
|
166
166
|
bullswarm workflow runs # ongoing only (default)
|
|
167
167
|
bullswarm workflow runs --all # ongoing + historical
|
|
168
168
|
bullswarm workflow runs --name <workflow> # filter by workflow name
|
|
169
|
+
bullswarm workflow runs --all --since 7d # initiated in the last 7 days
|
|
170
|
+
bullswarm workflow runs --historical --since yesterday --until today
|
|
169
171
|
bullswarm workflow runs show <shortId> # state + report + summary
|
|
170
172
|
bullswarm workflow runs delete <shortId> --yes
|
|
171
173
|
```
|
|
172
174
|
|
|
175
|
+
Historical time ranges filter the workflow's initiation time (`startedAt`),
|
|
176
|
+
not completion time. `--since` is inclusive and `--until` is exclusive, with
|
|
177
|
+
`--from`/`--to` and `--started-after`/`--started-before` aliases. Bounds accept
|
|
178
|
+
ISO timestamps, local dates, today/yesterday/tomorrow/now, or durations such as
|
|
179
|
+
`7d`. Add `--all` or `--historical`; time filters do not silently change the
|
|
180
|
+
normal ongoing-only scope.
|
|
181
|
+
|
|
173
182
|
Before authoring or choosing a workflow, agents can inspect the live execution
|
|
174
183
|
fabric and the workflow document itself:
|
|
175
184
|
|
package/src/workflow/runs-cli.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
// bullswarm workflow runs --all # list ongoing + historical
|
|
5
5
|
// bullswarm workflow runs --historical # only historical
|
|
6
6
|
// bullswarm workflow runs --name <workflow> # filter by workflow name
|
|
7
|
+
// bullswarm workflow runs --all --since 7d # initiated in the last 7 days
|
|
8
|
+
// bullswarm workflow runs --all --since yesterday --until today
|
|
7
9
|
// bullswarm workflow runs --limit N # cap result count
|
|
8
10
|
// bullswarm workflow runs show <id> # dump state + report
|
|
9
11
|
// bullswarm workflow runs delete <id> --yes # remove the run directory
|
|
@@ -32,29 +34,60 @@ export function cmdRuns(args) {
|
|
|
32
34
|
export function runsUsage() {
|
|
33
35
|
return `usage:
|
|
34
36
|
bullswarm workflow runs # list ongoing runs
|
|
35
|
-
bullswarm workflow runs [--all | --historical] [--name <workflow>]
|
|
37
|
+
bullswarm workflow runs [--all | --historical] [--name <workflow>]
|
|
38
|
+
[--since <time>] [--until <time>] [--limit N] [--json]
|
|
36
39
|
bullswarm workflow runs show <id> # <id> = shortId (6 chars) or full runId
|
|
37
|
-
bullswarm workflow runs delete <id> --yes # remove the run directory
|
|
40
|
+
bullswarm workflow runs delete <id> --yes # remove the run directory
|
|
41
|
+
|
|
42
|
+
Time filters compare the workflow initiation timestamp (startedAt):
|
|
43
|
+
--since / --started-after / --from inclusive lower bound
|
|
44
|
+
--until / --started-before / --to exclusive upper bound
|
|
45
|
+
Values accept ISO timestamps, local dates, today/yesterday/tomorrow/now,
|
|
46
|
+
or relative durations such as 30m, 24h, 7d, and 2w.`;
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
function parseRunsFlags(argv) {
|
|
41
50
|
const out = { _positional: [] };
|
|
51
|
+
const valueFlags = new Map([
|
|
52
|
+
['name', 'name'],
|
|
53
|
+
['limit', 'limit'],
|
|
54
|
+
['since', 'since'],
|
|
55
|
+
['started-after', 'since'],
|
|
56
|
+
['from', 'since'],
|
|
57
|
+
['until', 'until'],
|
|
58
|
+
['started-before', 'until'],
|
|
59
|
+
['to', 'until'],
|
|
60
|
+
]);
|
|
42
61
|
for (let i = 0; i < argv.length; i++) {
|
|
43
62
|
const a = argv[i];
|
|
44
63
|
if (a === '--json') out.json = true;
|
|
45
64
|
else if (a === '--all') out.all = true;
|
|
46
65
|
else if (a === '--historical') out.historical = true;
|
|
47
66
|
else if (a === '--yes' || a === '-y') out.yes = true;
|
|
48
|
-
else if (a === '--name') out.name = argv[++i];
|
|
49
|
-
else if (a === '--limit') out.limit = Number(argv[++i]);
|
|
50
67
|
else if (a === '--force') out.force = true;
|
|
51
|
-
else if (a.startsWith('--'))
|
|
68
|
+
else if (a.startsWith('--')) {
|
|
69
|
+
const eq = a.indexOf('=');
|
|
70
|
+
const key = a.slice(2, eq > 0 ? eq : undefined);
|
|
71
|
+
const target = valueFlags.get(key);
|
|
72
|
+
if (target) {
|
|
73
|
+
const value = eq > 0 ? a.slice(eq + 1) : argv[++i];
|
|
74
|
+
out[target] = target === 'limit' ? Number(value) : value;
|
|
75
|
+
} else {
|
|
76
|
+
out[key] = eq > 0 ? a.slice(eq + 1) : true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
52
79
|
else out._positional.push(a);
|
|
53
80
|
}
|
|
54
81
|
return out;
|
|
55
82
|
}
|
|
56
83
|
|
|
57
84
|
function runsList(opts) {
|
|
85
|
+
let initiatedRange;
|
|
86
|
+
try {
|
|
87
|
+
initiatedRange = resolveInitiatedRange(opts);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return err(error.message);
|
|
90
|
+
}
|
|
58
91
|
const all = listRuns(BULLSWARM_DIR());
|
|
59
92
|
let filtered = all;
|
|
60
93
|
if (opts.name) filtered = filtered.filter((r) => r.state?.workflow === opts.name);
|
|
@@ -64,10 +97,22 @@ function runsList(opts) {
|
|
|
64
97
|
} else if (opts.historical) {
|
|
65
98
|
filtered = filtered.filter((r) => !r.ongoing);
|
|
66
99
|
}
|
|
100
|
+
if (initiatedRange.sinceMs != null) {
|
|
101
|
+
filtered = filtered.filter((r) => {
|
|
102
|
+
const startedMs = Date.parse(runStartedAt(r) ?? '');
|
|
103
|
+
return Number.isFinite(startedMs) && startedMs >= initiatedRange.sinceMs;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (initiatedRange.untilMs != null) {
|
|
107
|
+
filtered = filtered.filter((r) => {
|
|
108
|
+
const startedMs = Date.parse(runStartedAt(r) ?? '');
|
|
109
|
+
return Number.isFinite(startedMs) && startedMs < initiatedRange.untilMs;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
67
112
|
// Newest first.
|
|
68
113
|
filtered.sort((a, b) => {
|
|
69
|
-
const ta = a
|
|
70
|
-
const tb = b
|
|
114
|
+
const ta = runStartedAt(a) ?? '';
|
|
115
|
+
const tb = runStartedAt(b) ?? '';
|
|
71
116
|
return tb.localeCompare(ta);
|
|
72
117
|
});
|
|
73
118
|
if (opts.limit && opts.limit > 0) filtered = filtered.slice(0, opts.limit);
|
|
@@ -77,6 +122,15 @@ function runsList(opts) {
|
|
|
77
122
|
ongoing: opts.all || !opts.historical,
|
|
78
123
|
historical: opts.all || opts.historical,
|
|
79
124
|
name: opts.name ?? null,
|
|
125
|
+
initiatedRange: {
|
|
126
|
+
field: 'startedAt',
|
|
127
|
+
sinceInclusive: initiatedRange.sinceMs == null
|
|
128
|
+
? null
|
|
129
|
+
: new Date(initiatedRange.sinceMs).toISOString(),
|
|
130
|
+
untilExclusive: initiatedRange.untilMs == null
|
|
131
|
+
? null
|
|
132
|
+
: new Date(initiatedRange.untilMs).toISOString(),
|
|
133
|
+
},
|
|
80
134
|
count: filtered.length,
|
|
81
135
|
runs: filtered.map(summarize),
|
|
82
136
|
}, opts);
|
|
@@ -96,7 +150,7 @@ function runsList(opts) {
|
|
|
96
150
|
for (const r of filtered) {
|
|
97
151
|
const wf = r.state?.workflow ?? '?';
|
|
98
152
|
const status = r.state?.status ?? (r.ongoing ? 'running' : 'unknown');
|
|
99
|
-
const age = humanAge(r
|
|
153
|
+
const age = humanAge(runStartedAt(r));
|
|
100
154
|
const phases = `${r.state?.steps?.filter((s) => s.ok).length ?? 0}/${r.state?.steps?.length ?? 0}`;
|
|
101
155
|
console.log(
|
|
102
156
|
`${r.ongoing ? '●' : '○'} ${(r.shortId ?? '------').padEnd(8)} ` +
|
|
@@ -167,8 +221,8 @@ function summarize(r) {
|
|
|
167
221
|
shortId: r.shortId,
|
|
168
222
|
workflow: r.state?.workflow ?? null,
|
|
169
223
|
status: r.state?.status ?? null,
|
|
170
|
-
startedAt: r
|
|
171
|
-
finishedAt: r.state?.finishedAt ?? null,
|
|
224
|
+
startedAt: runStartedAt(r),
|
|
225
|
+
finishedAt: r.state?.finishedAt ?? r.report?.finishedAt ?? null,
|
|
172
226
|
ongoing: r.ongoing,
|
|
173
227
|
stepsOk: r.state?.steps?.filter((s) => s.ok).length ?? 0,
|
|
174
228
|
stepsFailed: r.state?.steps?.filter((s) => s.ok === false).length ?? 0,
|
|
@@ -177,6 +231,58 @@ function summarize(r) {
|
|
|
177
231
|
};
|
|
178
232
|
}
|
|
179
233
|
|
|
234
|
+
function runStartedAt(run) {
|
|
235
|
+
return run.state?.startedAt ?? run.report?.startedAt ?? null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function resolveInitiatedRange(opts, nowMs = Date.now()) {
|
|
239
|
+
const sinceMs = opts.since == null ? null : parseTimeBound(opts.since, nowMs, '--since');
|
|
240
|
+
const untilMs = opts.until == null ? null : parseTimeBound(opts.until, nowMs, '--until');
|
|
241
|
+
if (sinceMs != null && untilMs != null && sinceMs >= untilMs) {
|
|
242
|
+
throw new Error('initiated-time range is empty: --since must be earlier than --until');
|
|
243
|
+
}
|
|
244
|
+
return { sinceMs, untilMs };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function parseTimeBound(value, nowMs, flag) {
|
|
248
|
+
const raw = String(value ?? '').trim();
|
|
249
|
+
if (!raw) throw new Error(`${flag} requires a time value`);
|
|
250
|
+
const normalized = raw.toLowerCase();
|
|
251
|
+
|
|
252
|
+
if (normalized === 'now') return nowMs;
|
|
253
|
+
if (['yesterday', 'today', 'tomorrow'].includes(normalized)) {
|
|
254
|
+
const date = new Date(nowMs);
|
|
255
|
+
date.setHours(0, 0, 0, 0);
|
|
256
|
+
if (normalized === 'yesterday') date.setDate(date.getDate() - 1);
|
|
257
|
+
if (normalized === 'tomorrow') date.setDate(date.getDate() + 1);
|
|
258
|
+
return date.getTime();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const duration = normalized.match(/^(\d+(?:\.\d+)?)(m|h|d|w)$/);
|
|
262
|
+
if (duration) {
|
|
263
|
+
const unitMs = { m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 }[duration[2]];
|
|
264
|
+
return nowMs - Number(duration[1]) * unitMs;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const localDate = normalized.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
268
|
+
if (localDate) {
|
|
269
|
+
const [, year, month, day] = localDate;
|
|
270
|
+
const date = new Date(Number(year), Number(month) - 1, Number(day));
|
|
271
|
+
if (date.getFullYear() !== Number(year)
|
|
272
|
+
|| date.getMonth() !== Number(month) - 1
|
|
273
|
+
|| date.getDate() !== Number(day)) {
|
|
274
|
+
throw new Error(`${flag} has an invalid calendar date: "${raw}"`);
|
|
275
|
+
}
|
|
276
|
+
return date.getTime();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const parsed = Date.parse(raw);
|
|
280
|
+
if (!Number.isFinite(parsed)) {
|
|
281
|
+
throw new Error(`${flag} has an invalid time: "${raw}"`);
|
|
282
|
+
}
|
|
283
|
+
return parsed;
|
|
284
|
+
}
|
|
285
|
+
|
|
180
286
|
function humanAge(iso) {
|
|
181
287
|
if (!iso) return '—';
|
|
182
288
|
const ms = Date.now() - Date.parse(iso);
|