@gobing-ai/spur 0.4.26 → 0.4.27
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/.claude-plugin/marketplace.json +1 -1
- package/config/rules/boundary/dao-boundary.yaml +2 -0
- package/config/rules/structure/test-location.yaml +2 -0
- package/package.json +2 -2
- package/plugins/sp/commands/dev-runall.md +6 -5
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +1 -1
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +16 -8
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +29 -5
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +9 -0
- package/spur.js +190 -97
|
@@ -88,6 +88,8 @@ rules:
|
|
|
88
88
|
exclude:
|
|
89
89
|
# the CLI migration journal — 43 DDL statements (verified load-bearing)
|
|
90
90
|
- "packages/domain/src/migrations.ts"
|
|
91
|
+
# 0037's embedded mirror, generated verbatim from released drizzle SQL (task 1312)
|
|
92
|
+
- "packages/domain/src/migration-sql.generated.ts"
|
|
91
93
|
|
|
92
94
|
- id: raw-sql-only-in-domain
|
|
93
95
|
description: >
|
|
@@ -44,6 +44,8 @@ rules:
|
|
|
44
44
|
- "**/schema/**"
|
|
45
45
|
- "**/schema.ts"
|
|
46
46
|
- "**/migrations.ts"
|
|
47
|
+
# generated migration SQL; scripts/commands/emit-migration-sql.test.ts pins it (task 1312)
|
|
48
|
+
- "packages/domain/src/migration-sql.generated.ts"
|
|
47
49
|
- "**/db.ts"
|
|
48
50
|
## Entity declarations — contracts are transport DTOs tested by the single contract.test.ts
|
|
49
51
|
- "packages/contracts/src/**"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.27",
|
|
4
4
|
"description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spur",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dev": "bun run src/index.ts",
|
|
51
51
|
"build": "bun run ../../scripts/spur-dev.ts build-cli",
|
|
52
52
|
"build:binaries": "bun run ../../scripts/spur-dev.ts build-binaries",
|
|
53
|
-
"build:bundle": "bun run ../../scripts/spur-dev.ts bundle-web && bun run ../../scripts/spur-dev.ts emit-board-types && bun build src/index.ts --target=bun --outfile spur.js --external @johnhenry/backend-webgpu && bun run ../../scripts/spur-dev.ts bundle-config config && bun run ../../scripts/spur-dev.ts bundle-laya-worker && bun run ../../scripts/spur-dev.ts bundle-plugins",
|
|
53
|
+
"build:bundle": "bun run ../../scripts/spur-dev.ts bundle-web && bun run ../../scripts/spur-dev.ts emit-board-types && bun run ../../scripts/spur-dev.ts emit-migration-sql && bun build src/index.ts --target=bun --outfile spur.js --external @johnhenry/backend-webgpu && bun run ../../scripts/spur-dev.ts bundle-config config && bun run ../../scripts/spur-dev.ts bundle-laya-worker && bun run ../../scripts/spur-dev.ts bundle-plugins",
|
|
54
54
|
"test": "bun test tests --coverage --coverage-dir=../../.coverage --pass-with-no-tests",
|
|
55
55
|
"typecheck": "tsc --noEmit",
|
|
56
56
|
"prepack": "bun run build:bundle",
|
|
@@ -17,9 +17,9 @@ Wraps the **sp:spur-dev** skill.
|
|
|
17
17
|
| `--tasks` `<selector>` | Task selector to run. | required |
|
|
18
18
|
| `--feature` `<id>` | Restrict to a feature. | omitted |
|
|
19
19
|
| `--mode` `<sequential\|parallel\|auto>` | Batch execution order. `auto` computes the frozen batch's dependency waves and runs parallel only when a wave holds ≥2 independent tasks (see below). | sequential |
|
|
20
|
-
| `--keep-going` | Continue past per-task failures. | off |
|
|
21
|
-
| `--auto` | Skip objective HITL gates. | off |
|
|
22
|
-
| `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver with unified inline semantics (task 0687 — see below). `auto`, a name, parallel mode, and headless invocation use subprocesses. The opt-in `fleet` selector (0942) maps each task run to `executor: 'fleet'` so `agent.run` stages dispatch through the fleet control plane (ADR-126) instead of a subprocess. | omit |
|
|
20
|
+
| `--keep-going` | Continue past per-task failures (implied by `--auto`). | off |
|
|
21
|
+
| `--auto` | Skip objective HITL gates, and treat the batch as unattended: `--auto` implies `--keep-going` (a failed task is `failed`, its in-batch dependents `skipped`, independents keep running). | off |
|
|
22
|
+
| `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver with unified inline semantics (task 0687 — see below). `auto`, a name, parallel mode, and headless invocation use subprocesses; `auto` is resolved **once per batch** — the first run's implement `result.executor` is pinned for every later task, and only a `failed-agent` ending re-resolves it, exactly once (1318 R2/R3). The opt-in `fleet` selector (0942) maps each task run to `executor: 'fleet'` so `agent.run` stages dispatch through the fleet control plane (ADR-126) instead of a subprocess. | omit |
|
|
23
23
|
| `--json` | Emit structured JSON. | off |
|
|
24
24
|
| `--wrap` | Run the wrap hop **once for the batch** over the `done` subset only (Step 6 of execution-batch.md): `vars.feature` is passed only when every frozen task is `done`/`cancelled`; an empty done subset skips the wrap with a reason. The `--agent` selector is preserved into the `/sp:dev-wrap` handoff when supplied; omission remains omission. | off |
|
|
25
25
|
| `--next` | Chain-to-completion via the next-router. | off |
|
|
@@ -50,8 +50,9 @@ see `execution-parallel-isolation.md`; `auto` picks one per batch — see below)
|
|
|
50
50
|
(parallel-mode worker bound — at most <n> task pipelines at once, default 2; a dependent task starts
|
|
51
51
|
only after its in-set dependencies are **integrated** onto the base ref), `--keep-going`
|
|
52
52
|
(batch failure policy — skip a failed task's in-batch dependents, continue independents; default
|
|
53
|
-
halts on first failure), `--auto`
|
|
54
|
-
(sets `profile=auto` on each per-task run, skipping the HITL approve gate
|
|
53
|
+
halts on first failure, and `--auto` implies `--keep-going`), `--auto`
|
|
54
|
+
(sets `profile=auto` on each per-task run, skipping the HITL approve gate; makes the batch
|
|
55
|
+
unattended, so it also implies `--keep-going`), `--agent <inline|auto|name>`
|
|
55
56
|
(names who runs the pipeline's stages — interactive sequential omit/`inline` uses the host driver;
|
|
56
57
|
`auto` or a name is merged into each per-task `vars.agent` and `vars.implementAgent`; `fleet` maps each task run to `executor: 'fleet'` instead (fleet dispatch, 0942); parallel mode
|
|
57
58
|
retains isolated subprocesses, ADR-047), `--json` (emit the
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -481,7 +481,7 @@ gate commands, pending questions and approval requirements.>
|
|
|
481
481
|
### 13. runall
|
|
482
482
|
|
|
483
483
|
- **Purpose:** Run a batch of tasks through their pipelines in dependency-correct order — resolve a set, topo-sort, run each via `task-pipeline.yaml`, inspect verdicts, apply the failure policy, emit a batch report.
|
|
484
|
-
- **Inputs:** `--tasks <selector>` (required — explicit WBS list, status pseudo-list, `feature:<id>`, or `ready`). `--mode <sequential|parallel>` (default `sequential`; `parallel` fans out a proven-independent subset per `execution-batch.md` § Parallel Execution). `--keep-going` skips a failed task's in-batch dependents and continues independents (default
|
|
484
|
+
- **Inputs:** `--tasks <selector>` (required — explicit WBS list, status pseudo-list, `feature:<id>`, or `ready`). `--mode <sequential|parallel>` (default `sequential`; `parallel` fans out a proven-independent subset per `execution-batch.md` § Parallel Execution). `--keep-going` skips a failed task's in-batch dependents and continues independents (default off; `--auto` implies `--keep-going`, so an unattended batch never stops for a human — 1318 R1). `--auto` sets `profile=auto` on each per-task run (skips the HITL approve gate). `--feature <id>` is sugar for `--tasks feature:<id>`; when the effective selector is feature-derived, the batch runs `spur feature check <id> --fix --json` then `spur feature check <id> --strict --json` **once** before task resolution — a non-zero strict check aborts with verdict `aborted`, zero attempted tasks, and the structured findings (task 0510 R2); scoped: `L4.scenario-unverified` and `L4.verifying-incomplete-tasks` (expected pre-run states of a not-yet-run or reopened feature) are reported verbatim but do not abort — the non-aborting set is `NON_ABORTING_PREFLIGHT_CODES` behind `spur task preflight --classify-feature` and `--fix` repairs are listed under `autoRepairs` — any other strict error aborts. Explicit WBS/status/`ready` selectors add no feature check. The orchestrator loop itself continues in this session; interactive sequential omit/`inline` uses the host driver — host-controlled with eligible `agent.run` stages dispatching once to a native subagent and host fallback (task 0508) — while `--agent auto`/a name, parallel mode, and headless invocation keep the isolated per-task workflow subprocess boundary. `--agent <inline|auto|name>` pins the executor for the per-task stages (see [SSOT](cross-cutting.md#inline-default-execution-surface)); the value crosses into per-task `vars.agent`, not the orchestrator. `--json` emits the report as JSON. `--wrap` triggers `wrapup-pipeline.yaml` **once for the batch** over the `done` subset only (execution-batch.md Step 6: `vars.feature` only when every frozen task is `done`/`cancelled`; empty done subset skips with a reason), `--next` chains each task to terminal status then runs the same batch-once wrap, `--continue` resumes from checkpoint. `--no-summary` skips the [execution summary](#execution-summary) printed after the batch report (one row per attempted run).
|
|
485
485
|
- **Three orthogonal axes (do not confuse):** `--keep-going` = batch failure policy (halt vs skip dependents); `--continue` = resume from checkpoint against the batch's original frozen identity (execution-batch.md § Batch continuation, task 0919); `--next` = per-task lifecycle chaining (advance status on a verdict — `dev-verify`/`dev-verifyall` only). `routing-table.md` offers `--continue` and `--next` as competing options for the same situation only when the batch was interrupted mid-run; otherwise they address different problems.
|
|
486
486
|
- **Backing:** `sp:spur-dev` skill, `runall` operation → delegates the driver loop to the **`sp:super-planner`** agent (the batch orchestrator).
|
|
487
487
|
- **Behavior:** The orchestrator reads [execution-batch.md](execution-batch.md) and drives: resolve selector → freeze set → topo-sort by `dependencies[]` (Kahn, WBS-ascending tie-break; cycle aborts) → resolve out-of-set deps by status (done → allow, else → block subtree) → run each task via `spur workflow run task-pipeline.yaml --async` + `spur workflow trace` polling → inspect terminal state + `.spur/run/<wbs>-verdict.json` → stop-the-batch default or `--keep-going` subtree skip → emit batch report. Per-task pipeline is invoked **verbatim** — no new FSM, no step edits. `--auto`/`--agent` are the only flags that cross the orchestrator→pipeline boundary (both into per-task `--vars`).
|
|
@@ -20,21 +20,29 @@ results). A `paused` task is non-terminal — the report marks it `paused` (neve
|
|
|
20
20
|
the resume command. Unless `--no-summary`, follow it with the measured
|
|
21
21
|
[execution summary](dev-operations.md#execution-summary), one `--progress <wbs>=<file>` per attempted run.
|
|
22
22
|
|
|
23
|
+
Under `--auto` the report's mode field reads `Mode: --auto (keep-going)`, and `--auto` implies `--keep-going`:
|
|
24
|
+
an unattended batch never stops for a human (1318 R1). For an `--agent auto` or
|
|
25
|
+
`--agent <name>` batch, print the header `Executor pin: <name> (from <wbs>|--agent)` and fill the
|
|
26
|
+
per-task `Executor` column with the pinned executor, or `A → B (failed-agent)` for a run that was
|
|
27
|
+
re-dispatched once on a new executor (1318 R2–R4). Leave that line and column out for host-inline
|
|
28
|
+
runs, which have no executor to pin.
|
|
29
|
+
|
|
23
30
|
```
|
|
24
31
|
## Batch Report — <selector>
|
|
25
32
|
|
|
26
33
|
**Selector:** <value>
|
|
27
34
|
**Plan:** <n> tasks (ordered: <wbs-list>) · <m> blocked · <p> not-attempted
|
|
28
|
-
**Mode:** stop-the-batch | --keep-going | --auto
|
|
35
|
+
**Mode:** stop-the-batch | --keep-going | --auto (keep-going)
|
|
36
|
+
**Executor pin:** <name> (from <wbs>|--agent) ← `--agent auto`/`--agent <name>` runs only
|
|
29
37
|
**Verdict:** clean | halted | aborted
|
|
30
38
|
|
|
31
|
-
| WBS | Status | Reason |
|
|
32
|
-
|
|
33
|
-
| 0040 | done | — |
|
|
34
|
-
| 0042 | failed | verify verdict PARTIAL (see .spur/run/0042-verdict.json) |
|
|
35
|
-
| 0050 | not-attempted | batch halted after 0042 (stop-the-batch) |
|
|
36
|
-
| 0051 | skipped | dependency 0040 failed (--keep-going) |
|
|
37
|
-
| 0060 | blocked | unmet out-of-set dep: 0099 is wip |
|
|
39
|
+
| WBS | Status | Executor | Reason |
|
|
40
|
+
|-----|--------|----------|--------|
|
|
41
|
+
| 0040 | done | claude | — |
|
|
42
|
+
| 0042 | failed | codex-a → codex-b (failed-agent) | verify verdict PARTIAL (see .spur/run/0042-verdict.json) |
|
|
43
|
+
| 0050 | not-attempted | — | batch halted after 0042 (stop-the-batch) |
|
|
44
|
+
| 0051 | skipped | — | dependency 0040 failed (--keep-going) |
|
|
45
|
+
| 0060 | blocked | — | unmet out-of-set dep: 0099 is wip |
|
|
38
46
|
|
|
39
47
|
**Next:** <one-line action — pick up halted run / resolve 0099 / all green, feature H1 complete>
|
|
40
48
|
|
|
@@ -23,6 +23,9 @@ see_also:
|
|
|
23
23
|
[execution-batch-continuation.md](execution-batch-continuation.md) for `--continue`.
|
|
24
24
|
- **Never re-derive the plan** — the frozen plan is the persisted report; a new selector match is
|
|
25
25
|
`not-admitted`, not a plan change.
|
|
26
|
+
- **Fresh session per segment** — when host context nears its limit, stop after a task terminal and
|
|
27
|
+
resume in a new session with `--continue`; the persisted report and markers carry the state. A
|
|
28
|
+
near-full context slows every later turn and degrades judgment.
|
|
26
29
|
|
|
27
30
|
## What this file owns
|
|
28
31
|
|
|
@@ -146,6 +149,13 @@ recorded in the batch report, and independent tasks keep running. The batch does
|
|
|
146
149
|
- **without `--auto`:** unchanged halt behavior — the batch stops on the failing task (`--keep-going`
|
|
147
150
|
still skips only its in-batch dependents).
|
|
148
151
|
|
|
152
|
+
**`--auto` implies `--keep-going` (1318 R1).** An unattended batch never stops for a human: under
|
|
153
|
+
`--auto` every per-task failure follows §4.2 whether or not the flag is passed — a failed,
|
|
154
|
+
cancelled, `stale-evidence` or worker-lost (1317) task is recorded `failed`, its in-batch
|
|
155
|
+
dependents are `skipped` with `dependency <wbs> failed`, and independent tasks keep running. The
|
|
156
|
+
refine pass above is only the extra step before that, not an exception. Without `--auto` the §4.1
|
|
157
|
+
stop-the-batch default is unchanged. No new flag.
|
|
158
|
+
|
|
149
159
|
`--tasks <value>` (or the effective value after normalization) resolves to a frozen set of task WBS numbers. Resolution happens **once, at
|
|
150
160
|
kickoff** — the driver never re-queries `spur task list` to recompute membership mid-batch (R2.1).
|
|
151
161
|
|
|
@@ -408,6 +418,18 @@ The host session remains the orchestrator for interactive sequential omit/inline
|
|
|
408
418
|
owns explicit-executor and parallel paths; there the flag pins the per-task step executor, not the
|
|
409
419
|
orchestrator.
|
|
410
420
|
|
|
421
|
+
**Executor pin (1318 R2/R3).** `--agent auto` is resolved **once per batch**, not once per task:
|
|
422
|
+
after the first task run, read the resolved implement executor from that run's implement `agent.run`
|
|
423
|
+
trace event — `spur workflow trace <run> --json` → `.events[] | select(.node=="implement" and
|
|
424
|
+
.actionKind=="agent.run") | .result.executor` — and dispatch every later task with
|
|
425
|
+
`--agent <that executor>`. The report's header records `Executor pin: <name> (from <wbs>)`; an
|
|
426
|
+
explicit `--agent <name>` records `(from --agent)` and needs no resolution. A task whose run ends
|
|
427
|
+
with `terminalReason == "failed-agent"` is re-dispatched **exactly once** with `--agent auto`; a
|
|
428
|
+
different resolved executor then becomes the pin for the remaining tasks, and a second
|
|
429
|
+
`failed-agent` records the task `failed` under the failure policy with the pin kept. No other terminal reason
|
|
430
|
+
is ever retried on a new executor. `--agent inline` (host driver) has no executor
|
|
431
|
+
to pin, so R2/R3 do not apply.
|
|
432
|
+
|
|
411
433
|
### 3.3 Terminal-state inspection
|
|
412
434
|
|
|
413
435
|
Each pipeline run ends in one of two terminal states:
|
|
@@ -525,10 +547,11 @@ belongs to a subagent or the operator, not to the batch controller's report loop
|
|
|
525
547
|
|
|
526
548
|
## Step 4 — Failure policy (R3)
|
|
527
549
|
|
|
528
|
-
### 4.1 Stop-the-batch (default) (R3.1)
|
|
550
|
+
### 4.1 Stop-the-batch (default unless `--auto`) (R3.1)
|
|
529
551
|
|
|
530
|
-
By default
|
|
531
|
-
as `not-attempted`. The report lists succeeded, failed,
|
|
552
|
+
By default — i.e. without `--auto` (1318 R1) — the **first** pipeline failure halts the batch.
|
|
553
|
+
Remaining tasks in the plan are reported as `not-attempted`. The report lists succeeded, failed,
|
|
554
|
+
and not-attempted tasks.
|
|
532
555
|
|
|
533
556
|
**Run-failed / task-done (observed 2026-10-19, feature P1).** A run can fail *after* the task is
|
|
534
557
|
already finished: the `done` state's close commands run last, and a lint or anchor rule can refuse
|
|
@@ -540,9 +563,10 @@ contract, not to a run whose last recorded state was superseded. Surface it in t
|
|
|
540
563
|
way (never silently continue), and never re-run the pipeline just to flip a run row from `failed` to
|
|
541
564
|
`done` — a second run re-executes model stages for a task that is already closed.
|
|
542
565
|
|
|
543
|
-
### 4.2 `--keep-going` (R3.2)
|
|
566
|
+
### 4.2 `--keep-going` (R3.2), or implied by `--auto`
|
|
544
567
|
|
|
545
|
-
With `--keep-going
|
|
568
|
+
With `--keep-going` — passed explicitly, or implied by `--auto` (1318 R1) — a failed task does
|
|
569
|
+
**not** halt the batch. Instead:
|
|
546
570
|
|
|
547
571
|
- The failed task's **in-batch dependents** (tasks in the plan that transitively depend on it) are
|
|
548
572
|
marked `skipped` with the failed dependency as the reason — they cannot run because their dep did
|
|
@@ -138,6 +138,11 @@ Only declared where the command already has at least one HITL gate the flag can
|
|
|
138
138
|
taste gates (idea-eval, design-approval) — every gate there is reversible corpus writes. A gate
|
|
139
139
|
without an actionable recommendation (missing eval recommendation, FAIL design check) still pauses.
|
|
140
140
|
|
|
141
|
+
**Batch policy** (`dev-runall`): `--auto` implies `--keep-going` — an unattended batch never stops
|
|
142
|
+
for a human. With `--agent auto` the driver also pins ONE executor for the batch — `Executor pin:
|
|
143
|
+
<name>`, read from the first run's implement `result.executor` — and only a `failed-agent` ending
|
|
144
|
+
re-resolves it, exactly once (1318 R1–R3).
|
|
145
|
+
|
|
141
146
|
### `--keep-going` — batch failure policy: skip dependents, continue independents
|
|
142
147
|
|
|
143
148
|
**Anchor:** `#flag-keep-going`.
|
|
@@ -146,6 +151,10 @@ Batch operation only (`dev-refineall`, `dev-runall`). When a task in the batch
|
|
|
146
151
|
fails, skip its in-batch dependents and continue the independent ones, instead of the default
|
|
147
152
|
halt-on-first-failure. Never silently retried; the failure is still reported.
|
|
148
153
|
|
|
154
|
+
**Default:** off, except under `--auto` — `--auto` implies `--keep-going` (1318 R1), because an
|
|
155
|
+
unattended batch that stops to wait for a human is the defect it was supposed to avoid. To stop at
|
|
156
|
+
the first failure, run without `--auto`.
|
|
157
|
+
|
|
149
158
|
### `--defer-gate` — opt-in parallel-batch gate policy (task 1111 / H15)
|
|
150
159
|
|
|
151
160
|
**Applies to:** `/sp-dev-runall --mode parallel`, and `--mode auto` when it resolves to parallel.
|
package/spur.js
CHANGED
|
@@ -45392,6 +45392,32 @@ var init_forensic_query = __esm(() => {
|
|
|
45392
45392
|
END`;
|
|
45393
45393
|
});
|
|
45394
45394
|
|
|
45395
|
+
// ../../packages/domain/src/analytics/selector-time-range.ts
|
|
45396
|
+
function buildTimeRangeClauses(range, target) {
|
|
45397
|
+
if (!COLUMN.test(target.column))
|
|
45398
|
+
throw new Error(`invalid time-range column: ${target.column}`);
|
|
45399
|
+
const bound = BOUND_SQL[target.grain];
|
|
45400
|
+
const clauses = [];
|
|
45401
|
+
const params = [];
|
|
45402
|
+
if (range.since !== null) {
|
|
45403
|
+
clauses.push(`${target.column} >= ${bound}`);
|
|
45404
|
+
params.push(range.since);
|
|
45405
|
+
}
|
|
45406
|
+
if (range.until !== null) {
|
|
45407
|
+
clauses.push(`${target.column} <= ${bound}`);
|
|
45408
|
+
params.push(range.until);
|
|
45409
|
+
}
|
|
45410
|
+
return { clauses, params };
|
|
45411
|
+
}
|
|
45412
|
+
var BOUND_SQL, COLUMN;
|
|
45413
|
+
var init_selector_time_range = __esm(() => {
|
|
45414
|
+
BOUND_SQL = {
|
|
45415
|
+
day: "DATE(?)",
|
|
45416
|
+
timestamp: "strftime('%Y-%m-%dT%H:%M:%SZ', ?)"
|
|
45417
|
+
};
|
|
45418
|
+
COLUMN = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/;
|
|
45419
|
+
});
|
|
45420
|
+
|
|
45395
45421
|
// ../../packages/domain/src/analytics/history-board-marts.ts
|
|
45396
45422
|
function resolveSummaryReadPath(input) {
|
|
45397
45423
|
if (!input.fresh)
|
|
@@ -45544,17 +45570,7 @@ async function deriveDimensionMarts(db, days) {
|
|
|
45544
45570
|
await db.batch(deriveDimensionMartsOps(days));
|
|
45545
45571
|
}
|
|
45546
45572
|
function dayClauses(sel) {
|
|
45547
|
-
|
|
45548
|
-
const params = [];
|
|
45549
|
-
if (sel.since !== null) {
|
|
45550
|
-
clauses.push("d.day >= DATE(?)");
|
|
45551
|
-
params.push(sel.since);
|
|
45552
|
-
}
|
|
45553
|
-
if (sel.until !== null) {
|
|
45554
|
-
clauses.push("d.day <= DATE(?)");
|
|
45555
|
-
params.push(sel.until);
|
|
45556
|
-
}
|
|
45557
|
-
return { clauses, params };
|
|
45573
|
+
return buildTimeRangeClauses(sel, { column: "d.day", grain: "day" });
|
|
45558
45574
|
}
|
|
45559
45575
|
function dimensionKeyClauses(sel, dimension) {
|
|
45560
45576
|
let values = null;
|
|
@@ -45771,6 +45787,7 @@ async function historyBoardSummaryFromMart(db, sel, dimension) {
|
|
|
45771
45787
|
}
|
|
45772
45788
|
var MART_DIMENSIONS, MART_MIN_RANGE_DAYS = 7, MEASURE_COLS = "messages, tool_calls, skill_calls, fresh_input_tokens, cache_read_tokens, cache_write_tokens, output_tokens, duration_ms, duration_samples", MART_INSERT_COLS, MSG_DAY_PRED = "(m.bucket_start >= ? AND m.bucket_start < DATE(?, '+1 day'))", TOOL_DAY_PRED = "(t.bucket_start >= ? AND t.bucket_start < DATE(?, '+1 day'))", SKILL_DAY_PRED = "(s.bucket_start >= ? AND s.bucket_start < DATE(?, '+1 day'))", KPI_RANGE_KEY = "trend";
|
|
45773
45789
|
var init_history_board_marts = __esm(() => {
|
|
45790
|
+
init_selector_time_range();
|
|
45774
45791
|
MART_DIMENSIONS = ["model", "source", "tool", "skill"];
|
|
45775
45792
|
MART_INSERT_COLS = `${MEASURE_COLS}, errors`;
|
|
45776
45793
|
});
|
|
@@ -46201,16 +46218,10 @@ function appendRankedSteps(operations, kind, rows) {
|
|
|
46201
46218
|
});
|
|
46202
46219
|
}
|
|
46203
46220
|
function buildRollupWhere(sel, alias, options) {
|
|
46204
|
-
const clauses =
|
|
46205
|
-
|
|
46206
|
-
|
|
46207
|
-
|
|
46208
|
-
params.push(sel.since);
|
|
46209
|
-
}
|
|
46210
|
-
if (sel.until !== null) {
|
|
46211
|
-
clauses.push(`${alias}.${options.timestamp} <= ${options.dateOnly ? "DATE(?)" : "strftime('%Y-%m-%dT%H:%M:%SZ', ?)"}`);
|
|
46212
|
-
params.push(sel.until);
|
|
46213
|
-
}
|
|
46221
|
+
const { clauses, params } = buildTimeRangeClauses(sel, {
|
|
46222
|
+
column: `${alias}.${options.timestamp}`,
|
|
46223
|
+
grain: options.dateOnly ? "day" : "timestamp"
|
|
46224
|
+
});
|
|
46214
46225
|
if (sel.sources !== null && sel.sources.length > 0) {
|
|
46215
46226
|
clauses.push(`${alias}.source IN (${sel.sources.map(() => "?").join(", ")})`);
|
|
46216
46227
|
params.push(...sel.sources);
|
|
@@ -47363,6 +47374,7 @@ var init_history_board_rollup = __esm(() => {
|
|
|
47363
47374
|
init_forensic_query();
|
|
47364
47375
|
init_history_board_marts();
|
|
47365
47376
|
init_rollup_watermark();
|
|
47377
|
+
init_selector_time_range();
|
|
47366
47378
|
init_tool_alias();
|
|
47367
47379
|
init_tool_name_sql();
|
|
47368
47380
|
init_tool_name_sql();
|
|
@@ -58119,7 +58131,7 @@ var init_run_dao = __esm(() => {
|
|
|
58119
58131
|
LIMIT ?6`, filter.workflow ?? null, filter.status ?? null, filter.since ?? null, filter.before?.started_at ?? null, filter.before?.id ?? null, filter.limit);
|
|
58120
58132
|
}
|
|
58121
58133
|
traceRowById(runId) {
|
|
58122
|
-
return this.adapter.queryFirst("SELECT id, workflow_name, mode, status, agent, started_at, completed_at, terminal_reason, metadata_json FROM runs WHERE id = ?", runId);
|
|
58134
|
+
return this.adapter.queryFirst("SELECT id, workflow_name, mode, status, agent, started_at, completed_at, terminal_reason, metadata_json, pid FROM runs WHERE id = ?", runId);
|
|
58123
58135
|
}
|
|
58124
58136
|
mergeMetadata(runId, patch) {
|
|
58125
58137
|
return this.adapter.run(`UPDATE runs
|
|
@@ -58132,6 +58144,17 @@ var init_run_dao = __esm(() => {
|
|
|
58132
58144
|
stampMetadata(runId, metadata) {
|
|
58133
58145
|
return this.mergeMetadata(runId, metadata);
|
|
58134
58146
|
}
|
|
58147
|
+
mergeMetadataIfStatus(runId, patch, statuses) {
|
|
58148
|
+
if (statuses.length === 0)
|
|
58149
|
+
return Promise.resolve();
|
|
58150
|
+
const placeholders = statuses.map((_, i) => `?${i + 3}`).join(", ");
|
|
58151
|
+
return this.adapter.run(`UPDATE runs
|
|
58152
|
+
SET metadata_json = json_patch(
|
|
58153
|
+
COALESCE(NULLIF(metadata_json, ''), '{}'),
|
|
58154
|
+
?1
|
|
58155
|
+
)
|
|
58156
|
+
WHERE id = ?2 AND status IN (${placeholders})`, JSON.stringify(patch), runId, ...statuses);
|
|
58157
|
+
}
|
|
58135
58158
|
stampRunIdentity(runId, definitionDigest, workflowVersion, source) {
|
|
58136
58159
|
const sourceSet = source === undefined ? "" : `,
|
|
58137
58160
|
'$.definitionSource.path', ?4,
|
|
@@ -72279,6 +72302,64 @@ var init_dist11 = __esm(() => {
|
|
|
72279
72302
|
init_types3();
|
|
72280
72303
|
});
|
|
72281
72304
|
|
|
72305
|
+
// ../../packages/domain/src/migration-sql.generated.ts
|
|
72306
|
+
var HISTORY_DIMENSION_MARTS_SCHEMA_SQL = `-- 0743: Dimension marts (task 0743).
|
|
72307
|
+
-- Two day-grain mart tables carrying the ADR-106 nine-measure additive vector for the
|
|
72308
|
+
-- four materialized Summary dimensions. Every measure column is nullable for exactly one
|
|
72309
|
+
-- reason: ADR-106 records a measure that is not well defined at a dimension as NULL, never
|
|
72310
|
+
-- as a zero that would be indistinguishable from a measured absence of activity.
|
|
72311
|
+
--
|
|
72312
|
+
-- - tool dimension: skill_calls is not applicable -> NULL
|
|
72313
|
+
-- - skill dimension: tool_calls is not applicable -> NULL
|
|
72314
|
+
-- - source dimension: duration_ms / duration_samples are not applicable -> NULL
|
|
72315
|
+
--
|
|
72316
|
+
-- history_board_kpi_window stores the current and previous aggregate window KPIs for a
|
|
72317
|
+
-- named range window. All statements are idempotent (CREATE TABLE IF NOT EXISTS).
|
|
72318
|
+
|
|
72319
|
+
CREATE TABLE IF NOT EXISTS history_board_dimension_daily (
|
|
72320
|
+
dimension TEXT NOT NULL,
|
|
72321
|
+
dimension_key TEXT NOT NULL,
|
|
72322
|
+
day TEXT NOT NULL,
|
|
72323
|
+
-- ADR-106 nine-member additive measure vector. NULLABLE, never a NOT NULL column with a
|
|
72324
|
+
-- zero default: a not-applicable measure is stored as NULL.
|
|
72325
|
+
messages INTEGER,
|
|
72326
|
+
tool_calls INTEGER,
|
|
72327
|
+
skill_calls INTEGER,
|
|
72328
|
+
fresh_input_tokens REAL,
|
|
72329
|
+
cache_read_tokens REAL,
|
|
72330
|
+
cache_write_tokens REAL,
|
|
72331
|
+
output_tokens REAL,
|
|
72332
|
+
duration_ms INTEGER,
|
|
72333
|
+
duration_samples INTEGER,
|
|
72334
|
+
-- Per-(dimension, key, day) tool-error count (not part of the ADR-106 additive vector but
|
|
72335
|
+
-- needed by the top-tools error-rate projection. NULLABLE like every other scalar.
|
|
72336
|
+
errors INTEGER,
|
|
72337
|
+
PRIMARY KEY (dimension, dimension_key, day)
|
|
72338
|
+
);
|
|
72339
|
+
|
|
72340
|
+
CREATE TABLE IF NOT EXISTS history_board_kpi_window (
|
|
72341
|
+
range_key TEXT NOT NULL,
|
|
72342
|
+
window_kind TEXT NOT NULL CHECK (window_kind IN ('current', 'previous')),
|
|
72343
|
+
-- ADR-106 nine-member additive measure vector, plus the non-additive totals the KPI
|
|
72344
|
+
-- projection needs (sessions and tool_errors are counts, never summed as means).
|
|
72345
|
+
messages INTEGER,
|
|
72346
|
+
tool_calls INTEGER,
|
|
72347
|
+
skill_calls INTEGER,
|
|
72348
|
+
fresh_input_tokens REAL,
|
|
72349
|
+
cache_read_tokens REAL,
|
|
72350
|
+
cache_write_tokens REAL,
|
|
72351
|
+
output_tokens REAL,
|
|
72352
|
+
duration_ms INTEGER,
|
|
72353
|
+
duration_samples INTEGER,
|
|
72354
|
+
sessions INTEGER,
|
|
72355
|
+
tool_errors INTEGER,
|
|
72356
|
+
PRIMARY KEY (range_key, window_kind)
|
|
72357
|
+
);
|
|
72358
|
+
|
|
72359
|
+
CREATE INDEX IF NOT EXISTS idx_history_board_dimension_daily_day
|
|
72360
|
+
ON history_board_dimension_daily (dimension, day);
|
|
72361
|
+
`;
|
|
72362
|
+
|
|
72282
72363
|
// ../../packages/domain/src/schema/inbox-messages.ts
|
|
72283
72364
|
var inboxMessagesTable, inboxMessages4, INBOX_MESSAGES_TABLE_SQL;
|
|
72284
72365
|
var init_inbox_messages3 = __esm(() => {
|
|
@@ -73224,49 +73305,6 @@ CREATE TABLE IF NOT EXISTS history_board_rollup_bucket (
|
|
|
73224
73305
|
|
|
73225
73306
|
CREATE INDEX IF NOT EXISTS idx_history_message_imported_at
|
|
73226
73307
|
ON history_message (imported_at);
|
|
73227
|
-
`, HISTORY_DIMENSION_MARTS_SCHEMA_SQL = `
|
|
73228
|
-
CREATE TABLE IF NOT EXISTS history_board_dimension_daily (
|
|
73229
|
-
dimension TEXT NOT NULL,
|
|
73230
|
-
dimension_key TEXT NOT NULL,
|
|
73231
|
-
day TEXT NOT NULL,
|
|
73232
|
-
-- ADR-106 nine-member additive measure vector. NULLABLE, never a NOT NULL column with a
|
|
73233
|
-
-- zero default: a not-applicable measure is stored as NULL.
|
|
73234
|
-
messages INTEGER,
|
|
73235
|
-
tool_calls INTEGER,
|
|
73236
|
-
skill_calls INTEGER,
|
|
73237
|
-
fresh_input_tokens REAL,
|
|
73238
|
-
cache_read_tokens REAL,
|
|
73239
|
-
cache_write_tokens REAL,
|
|
73240
|
-
output_tokens REAL,
|
|
73241
|
-
duration_ms INTEGER,
|
|
73242
|
-
duration_samples INTEGER,
|
|
73243
|
-
-- Per-(dimension, key, day) tool-error count (not part of the ADR-106 additive vector but
|
|
73244
|
-
-- needed by the top-tools error-rate projection. NULLABLE like every other scalar.
|
|
73245
|
-
errors INTEGER,
|
|
73246
|
-
PRIMARY KEY (dimension, dimension_key, day)
|
|
73247
|
-
);
|
|
73248
|
-
|
|
73249
|
-
CREATE TABLE IF NOT EXISTS history_board_kpi_window (
|
|
73250
|
-
range_key TEXT NOT NULL,
|
|
73251
|
-
window_kind TEXT NOT NULL CHECK (window_kind IN ('current', 'previous')),
|
|
73252
|
-
-- ADR-106 nine-member additive measure vector, plus the non-additive totals the KPI
|
|
73253
|
-
-- projection needs (sessions and tool_errors are counts, never summed as means).
|
|
73254
|
-
messages INTEGER,
|
|
73255
|
-
tool_calls INTEGER,
|
|
73256
|
-
skill_calls INTEGER,
|
|
73257
|
-
fresh_input_tokens REAL,
|
|
73258
|
-
cache_read_tokens REAL,
|
|
73259
|
-
cache_write_tokens REAL,
|
|
73260
|
-
output_tokens REAL,
|
|
73261
|
-
duration_ms INTEGER,
|
|
73262
|
-
duration_samples INTEGER,
|
|
73263
|
-
sessions INTEGER,
|
|
73264
|
-
tool_errors INTEGER,
|
|
73265
|
-
PRIMARY KEY (range_key, window_kind)
|
|
73266
|
-
);
|
|
73267
|
-
|
|
73268
|
-
CREATE INDEX IF NOT EXISTS idx_history_board_dimension_daily_day
|
|
73269
|
-
ON history_board_dimension_daily (dimension, day);
|
|
73270
73308
|
`, RETENTION_COMPACTION_META_SCHEMA_SQL = `
|
|
73271
73309
|
CREATE TABLE IF NOT EXISTS spur_retention_meta (
|
|
73272
73310
|
kind TEXT NOT NULL,
|
|
@@ -100265,6 +100303,31 @@ import {
|
|
|
100265
100303
|
writeSync as writeSync2
|
|
100266
100304
|
} from "fs";
|
|
100267
100305
|
import { dirname as dirname8, join as join18 } from "path";
|
|
100306
|
+
function writeStateFile(statePath, state) {
|
|
100307
|
+
const temp = `${statePath}.tmp`;
|
|
100308
|
+
try {
|
|
100309
|
+
writeFileSync3(temp, `${JSON.stringify(state, null, 2)}
|
|
100310
|
+
`);
|
|
100311
|
+
renameSync3(temp, statePath);
|
|
100312
|
+
} catch {
|
|
100313
|
+
try {
|
|
100314
|
+
unlinkSync2(temp);
|
|
100315
|
+
} catch {}
|
|
100316
|
+
}
|
|
100317
|
+
}
|
|
100318
|
+
function projectRunStateStatus(dir, runId, status, at) {
|
|
100319
|
+
const statePath = join18(dir, `${runId}.state.json`);
|
|
100320
|
+
let prior;
|
|
100321
|
+
try {
|
|
100322
|
+
const parsed = JSON.parse(readFileSync5(statePath, "utf8"));
|
|
100323
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
100324
|
+
return;
|
|
100325
|
+
prior = parsed;
|
|
100326
|
+
} catch {
|
|
100327
|
+
return;
|
|
100328
|
+
}
|
|
100329
|
+
writeStateFile(statePath, { ...prior, status, updatedAt: at, finalizedAt: at });
|
|
100330
|
+
}
|
|
100268
100331
|
|
|
100269
100332
|
class WorkflowRunLogSink {
|
|
100270
100333
|
filePath;
|
|
@@ -100404,16 +100467,7 @@ class WorkflowRunLogSink {
|
|
|
100404
100467
|
...this.lineage?.startState !== undefined ? { startState: this.lineage.startState } : {},
|
|
100405
100468
|
...this.lineage?.continuedFrom !== undefined ? { continuedFrom: this.lineage.continuedFrom } : {}
|
|
100406
100469
|
};
|
|
100407
|
-
|
|
100408
|
-
try {
|
|
100409
|
-
writeFileSync3(temp, `${JSON.stringify(state, null, 2)}
|
|
100410
|
-
`);
|
|
100411
|
-
renameSync3(temp, this.statePath);
|
|
100412
|
-
} catch {
|
|
100413
|
-
try {
|
|
100414
|
-
unlinkSync2(temp);
|
|
100415
|
-
} catch {}
|
|
100416
|
-
}
|
|
100470
|
+
writeStateFile(this.statePath, state);
|
|
100417
100471
|
}
|
|
100418
100472
|
onSteering(ack) {
|
|
100419
100473
|
const note = ack.note === undefined ? "" : ` \xB7 ${bounded3(ack.note, MAX_STEERING_NOTE_CHARS)}`;
|
|
@@ -131099,6 +131153,32 @@ var init_workflow_catalog_scan = __esm(() => {
|
|
|
131099
131153
|
import { realpathSync as realpathSync8, statSync as statSync12 } from "fs";
|
|
131100
131154
|
import { readdir as readdir3, rm as rm2 } from "fs/promises";
|
|
131101
131155
|
import { dirname as dirname32, join as join58, relative as relative14, resolve as resolve21, sep as sep11 } from "path";
|
|
131156
|
+
async function reapRun(db, engine, run, reasons, cwd) {
|
|
131157
|
+
const dao = new RunDao(db);
|
|
131158
|
+
const interrupted = run.status === "running" ? await engine.interruptRun(run.id, "interrupted") : undefined;
|
|
131159
|
+
if (interrupted === undefined) {
|
|
131160
|
+
await dao.finalizeStale(run.id, reasons.finalized);
|
|
131161
|
+
} else {
|
|
131162
|
+
await dao.mergeMetadataIfStatus(run.id, { staleReason: reasons.interrupted }, ["interrupted"]);
|
|
131163
|
+
}
|
|
131164
|
+
projectRunStateStatus(runStoragePaths(cwd).recordsDir, run.id, interrupted === undefined ? "failed" : "interrupted", new Date().toISOString());
|
|
131165
|
+
return interrupted === undefined ? "failed" : "interrupted";
|
|
131166
|
+
}
|
|
131167
|
+
async function reapIfWorkerLost(db, cwd, runId) {
|
|
131168
|
+
const dao = new RunDao(db);
|
|
131169
|
+
const row = await dao.traceRowById(runId);
|
|
131170
|
+
if (row === undefined)
|
|
131171
|
+
return { row, reaped: false };
|
|
131172
|
+
if (row.status !== "running" && row.status !== "pending")
|
|
131173
|
+
return { row, reaped: false };
|
|
131174
|
+
const pid = row.pid;
|
|
131175
|
+
if (pid === null || isPidAlive(pid))
|
|
131176
|
+
return { row, reaped: false };
|
|
131177
|
+
const reason = `worker-lost: pid ${pid} not alive`;
|
|
131178
|
+
const engine = new WorkflowService(createDefaultWorkflowEngineHost({}), new DbWorkflowPersistenceAdapter(db));
|
|
131179
|
+
await reapRun(db, engine, row, { interrupted: reason, finalized: reason }, cwd);
|
|
131180
|
+
return { row, reaped: true };
|
|
131181
|
+
}
|
|
131102
131182
|
|
|
131103
131183
|
class WorkflowCleanOps {
|
|
131104
131184
|
ctx;
|
|
@@ -131113,14 +131193,10 @@ class WorkflowCleanOps {
|
|
|
131113
131193
|
if (!dryRun) {
|
|
131114
131194
|
const engine = new WorkflowService(createDefaultWorkflowEngineHost({}), new DbWorkflowPersistenceAdapter(db));
|
|
131115
131195
|
for (const run of stale) {
|
|
131116
|
-
|
|
131117
|
-
|
|
131118
|
-
|
|
131119
|
-
}
|
|
131120
|
-
await dao.mergeMetadata(run.id, {
|
|
131121
|
-
staleReason: `stale: owner lost > ${olderThanMinutes}m (spur workflow clean)`
|
|
131122
|
-
});
|
|
131123
|
-
}
|
|
131196
|
+
await reapRun(db, engine, run, {
|
|
131197
|
+
interrupted: `stale: owner lost > ${olderThanMinutes}m (spur workflow clean)`,
|
|
131198
|
+
finalized: `stale: non-terminal > ${olderThanMinutes}m (spur workflow clean)`
|
|
131199
|
+
}, this.ctx.cwd);
|
|
131124
131200
|
}
|
|
131125
131201
|
}
|
|
131126
131202
|
return {
|
|
@@ -131337,7 +131413,9 @@ var init_workflow_clean_ops = __esm(() => {
|
|
|
131337
131413
|
init_src2();
|
|
131338
131414
|
init_dist9();
|
|
131339
131415
|
init_dist5();
|
|
131416
|
+
init_workflow_run_log_sink();
|
|
131340
131417
|
init_checkpoint_contract();
|
|
131418
|
+
init_project_process();
|
|
131341
131419
|
init_run_storage();
|
|
131342
131420
|
init_workflow_catalog_scan();
|
|
131343
131421
|
});
|
|
@@ -135113,6 +135191,8 @@ function rowToTraceEntry(row, cwd) {
|
|
|
135113
135191
|
isDryRun = meta.dryRun === true;
|
|
135114
135192
|
if (typeof meta.failureReason === "string" && meta.failureReason !== "") {
|
|
135115
135193
|
failureReason = meta.failureReason;
|
|
135194
|
+
} else if (typeof meta.staleReason === "string" && meta.staleReason !== "") {
|
|
135195
|
+
failureReason = meta.staleReason;
|
|
135116
135196
|
}
|
|
135117
135197
|
if ("workflowVersion" in meta && (typeof meta.workflowVersion === "string" || meta.workflowVersion === null)) {
|
|
135118
135198
|
version = meta.workflowVersion;
|
|
@@ -135483,9 +135563,9 @@ class WorkflowInspectOps {
|
|
|
135483
135563
|
total: rows.length
|
|
135484
135564
|
};
|
|
135485
135565
|
}
|
|
135486
|
-
async traceRun(db, runId) {
|
|
135566
|
+
async traceRun(db, runId, preloadedRow) {
|
|
135487
135567
|
const runDao = new RunDao(db);
|
|
135488
|
-
const row = await runDao.traceRowById(runId);
|
|
135568
|
+
const row = preloadedRow ?? await runDao.traceRowById(runId);
|
|
135489
135569
|
if (!row)
|
|
135490
135570
|
throw new Error(`Run not found: ${runId}`);
|
|
135491
135571
|
const run = rowToTraceEntry(row, this.ctx.cwd);
|
|
@@ -136636,8 +136716,10 @@ class WorkflowRunOps {
|
|
|
136636
136716
|
await dao.cancelRun(runId);
|
|
136637
136717
|
const after = await dao.traceRowById(runId);
|
|
136638
136718
|
const finalized = after?.status === "failed" && isNonTerminal;
|
|
136639
|
-
if (finalized)
|
|
136719
|
+
if (finalized) {
|
|
136720
|
+
projectRunStateStatus(runStoragePaths(this.ctx.cwd).recordsDir, runId, "failed", new Date().toISOString());
|
|
136640
136721
|
await releaseWorktreeRunLease(db, this.ctx.cwd, runId);
|
|
136722
|
+
}
|
|
136641
136723
|
return { runId, finalized, status: after?.status ?? "not_found", killed };
|
|
136642
136724
|
}
|
|
136643
136725
|
async stampFailureReason(runId, result) {
|
|
@@ -136657,11 +136739,13 @@ var init_workflow_run_ops = __esm(() => {
|
|
|
136657
136739
|
init_src2();
|
|
136658
136740
|
init_dist9();
|
|
136659
136741
|
init_dist5();
|
|
136742
|
+
init_workflow_run_log_sink();
|
|
136660
136743
|
init_composition_baseline();
|
|
136661
136744
|
init_host_executor();
|
|
136662
136745
|
init_start_state();
|
|
136663
136746
|
init_workflow_resolver();
|
|
136664
136747
|
init_event_bridge();
|
|
136748
|
+
init_run_storage();
|
|
136665
136749
|
init_workflow_run_recording();
|
|
136666
136750
|
init_workflow_run_vars();
|
|
136667
136751
|
init_workflow_worktree_lease();
|
|
@@ -136722,7 +136806,8 @@ class WorkflowAppService {
|
|
|
136722
136806
|
async trace(runIdOrFilter) {
|
|
136723
136807
|
const db = await this.ctx.getDb();
|
|
136724
136808
|
if (typeof runIdOrFilter === "string") {
|
|
136725
|
-
|
|
136809
|
+
const probe = await reapIfWorkerLost(db, this.ctx.cwd, runIdOrFilter);
|
|
136810
|
+
return this.inspectOps.traceRun(db, runIdOrFilter, probe.reaped ? undefined : probe.row);
|
|
136726
136811
|
}
|
|
136727
136812
|
return this.inspectOps.traceList(db, runIdOrFilter ?? {});
|
|
136728
136813
|
}
|
|
@@ -138263,6 +138348,7 @@ var init_src4 = __esm(() => {
|
|
|
138263
138348
|
init_token_ledger_watcher();
|
|
138264
138349
|
init_verify_answer_lint();
|
|
138265
138350
|
init_warm_turn_dispatch();
|
|
138351
|
+
init_workflow_clean_ops();
|
|
138266
138352
|
init_workflow_service();
|
|
138267
138353
|
init_worktree_persist();
|
|
138268
138354
|
init_write_slot_service();
|
|
@@ -164625,7 +164711,7 @@ init_src2();
|
|
|
164625
164711
|
var CLI_CONFIG = {
|
|
164626
164712
|
binaryName: "spur",
|
|
164627
164713
|
binaryLabel: "spur",
|
|
164628
|
-
binaryVersion: "0.4.
|
|
164714
|
+
binaryVersion: "0.4.27",
|
|
164629
164715
|
configDir: ".spur",
|
|
164630
164716
|
configFile: ".spur/config.yaml",
|
|
164631
164717
|
databaseFile: ".spur/spur.db"
|
|
@@ -172364,7 +172450,7 @@ init_dist5();
|
|
|
172364
172450
|
import { homedir as homedir17 } from "os";
|
|
172365
172451
|
import { basename as basename23, join as join95, relative as relative19, resolve as resolve37 } from "path";
|
|
172366
172452
|
// ../server/package.json
|
|
172367
|
-
var version4 = "0.4.
|
|
172453
|
+
var version4 = "0.4.27";
|
|
172368
172454
|
|
|
172369
172455
|
// ../server/src/modules/health/index.ts
|
|
172370
172456
|
var startedAt = Date.now();
|
|
@@ -178537,8 +178623,10 @@ function formatWorkflowProgress(projection) {
|
|
|
178537
178623
|
}
|
|
178538
178624
|
function registerWorkflowProgressCommand(workflow, context) {
|
|
178539
178625
|
workflow.command("progress").description("Project the structured progress of a workflow run: current state, per-action attempts, and candidate next transitions.").argument("<run-id>", "Run id to project").option(...SHARED_OPTIONS.jsonSupported).option(...SHARED_OPTIONS.jsonEnvelope).action(async (runId, options) => {
|
|
178626
|
+
const db = await context.getDb();
|
|
178627
|
+
await reapIfWorkerLost(db, context.cwd, runId);
|
|
178540
178628
|
const projection = await projectWorkflowProgress(runId, {
|
|
178541
|
-
db
|
|
178629
|
+
db,
|
|
178542
178630
|
projectRoot: context.cwd
|
|
178543
178631
|
});
|
|
178544
178632
|
if (projection.diagnostics.some((diagnostic) => diagnostic.code === "orphan-row")) {
|
|
@@ -179238,6 +179326,14 @@ function formatTimelineEvent(event) {
|
|
|
179238
179326
|
function followDeadlineLine(runId, elapsedMs, status) {
|
|
179239
179327
|
return `Follow timed out after ${elapsedMs}ms: run ${runId} status=${status} \u2014 run continues; resume with spur workflow trace ${runId} --follow`;
|
|
179240
179328
|
}
|
|
179329
|
+
function followFinalizedLine(timeline) {
|
|
179330
|
+
const run = timeline.run;
|
|
179331
|
+
const reason = run.failureReason ? ` \u2014 ${run.failureReason}` : "";
|
|
179332
|
+
const duration = run.durationMs === null ? "unavailable" : `${run.durationMs}ms`;
|
|
179333
|
+
const next = run.nextAction === undefined ? "" : `
|
|
179334
|
+
Next: ${run.nextAction.label} \u2014 ${run.nextAction.value}`;
|
|
179335
|
+
return `Run finalized: ${run.status}${reason} \u2014 outcome=${run.outcome} duration=${duration}${next}`;
|
|
179336
|
+
}
|
|
179241
179337
|
async function followTrace(service, runId, pollMs, write, wait = (ms) => sleep7(ms), now = Date.now, deadlineMs) {
|
|
179242
179338
|
const start = now();
|
|
179243
179339
|
const deadline = deadlineMs === undefined ? Number.POSITIVE_INFINITY : start + deadlineMs;
|
|
@@ -179260,8 +179356,10 @@ async function followTrace(service, runId, pollMs, write, wait = (ms) => sleep7(
|
|
|
179260
179356
|
}
|
|
179261
179357
|
write(formatTraceTimeline(timeline));
|
|
179262
179358
|
const seen = new Set(timeline.events.map((event) => JSON.stringify(event)));
|
|
179263
|
-
if (isTerminalTraceStatus(timeline.run.status))
|
|
179359
|
+
if (isTerminalTraceStatus(timeline.run.status)) {
|
|
179360
|
+
write(followFinalizedLine(timeline));
|
|
179264
179361
|
return false;
|
|
179362
|
+
}
|
|
179265
179363
|
while (true) {
|
|
179266
179364
|
await wait(pollMs);
|
|
179267
179365
|
const next = await service.trace(runId);
|
|
@@ -179273,12 +179371,7 @@ async function followTrace(service, runId, pollMs, write, wait = (ms) => sleep7(
|
|
|
179273
179371
|
write(formatTimelineEvent(event));
|
|
179274
179372
|
}
|
|
179275
179373
|
if (isTerminalTraceStatus(next.run.status)) {
|
|
179276
|
-
|
|
179277
|
-
const duration = next.run.durationMs === null ? "unavailable" : `${next.run.durationMs}ms`;
|
|
179278
|
-
write(`Run finalized: ${next.run.status}${reason} \u2014 outcome=${next.run.outcome} duration=${duration}`);
|
|
179279
|
-
if (next.run.nextAction !== undefined) {
|
|
179280
|
-
write(`Next: ${next.run.nextAction.label} \u2014 ${next.run.nextAction.value}`);
|
|
179281
|
-
}
|
|
179374
|
+
write(followFinalizedLine(next));
|
|
179282
179375
|
return false;
|
|
179283
179376
|
}
|
|
179284
179377
|
const at = now();
|