omp-conductor 0.16.1 → 0.17.0
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/README.md +63 -4
- package/REFERENCE.md +20 -11
- package/package.json +3 -1
- package/src/briefs/orchestrator.md +21 -6
- package/src/briefs/policy.md +13 -5
- package/src/clack-ui.ts +83 -0
- package/src/cli.ts +110 -381
- package/src/command-help.ts +220 -0
- package/src/command-manifest.ts +480 -0
- package/src/commands/arm.ts +7 -2
- package/src/commands/complete.ts +93 -0
- package/src/commands/context.ts +1 -0
- package/src/commands/decision.ts +17 -7
- package/src/commands/doctor.ts +18 -1
- package/src/commands/hold.ts +9 -7
- package/src/commands/ledger.ts +25 -4
- package/src/commands/setup.ts +128 -11
- package/src/commands/stats.ts +9 -5
- package/src/commands/status.ts +32 -5
- package/src/commands/tail.ts +13 -1
- package/src/commands/watch.ts +16 -7
- package/src/fleet.ts +55 -15
- package/src/orchestrator-tick.ts +46 -7
- package/src/privileged.ts +3 -0
- package/src/setup-answers.ts +135 -0
- package/src/setup-host.ts +29 -11
- package/src/setup-install.ts +2 -0
- package/src/setup-probe.ts +1 -0
- package/src/setup-wizard.ts +130 -38
- package/src/ui/progress.ts +32 -0
- package/src/ui/style.ts +11 -0
- package/src/upgrade-verify.ts +25 -3
- package/src/upgrade.ts +61 -2
- package/src/wizard-ui.ts +14 -5
- package/systemd/omp-conductor-recover.sh +1 -1
- package/systemd/recover-unit-test.sh +2 -2
package/README.md
CHANGED
|
@@ -114,6 +114,31 @@ For what the host needs before anything runs — `bun`, an authenticated `gh`,
|
|
|
114
114
|
`omp-telegram` for the escalation channel — see the
|
|
115
115
|
[Install prerequisites](https://github.com/TerrifiedBug/conductor/blob/main/omp/REFERENCE.md#install-prerequisites) in the reference.
|
|
116
116
|
|
|
117
|
+
### Shell completions
|
|
118
|
+
|
|
119
|
+
`omp-conductor complete` generates completions for zsh, bash, fish, and
|
|
120
|
+
PowerShell. Load them for the current shell:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
source <(omp-conductor complete zsh)
|
|
124
|
+
# or
|
|
125
|
+
source <(omp-conductor complete bash)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For a persistent zsh install:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
mkdir -p ~/.omp/conductor
|
|
132
|
+
omp-conductor complete zsh > ~/.omp/conductor/completions.zsh
|
|
133
|
+
echo '[ -f ~/.omp/conductor/completions.zsh ] && source ~/.omp/conductor/completions.zsh' >> ~/.zshrc
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Use `bash` and `~/.bashrc` for the equivalent bash install. In fish, run
|
|
137
|
+
`omp-conductor complete fish | source`; in PowerShell, run
|
|
138
|
+
`omp-conductor complete powershell | Out-String | Invoke-Expression`.
|
|
139
|
+
After a successful interactive setup, the wizard offers to install zsh or bash
|
|
140
|
+
completions this way and adds the rc source line only when it is absent.
|
|
141
|
+
|
|
117
142
|
## Quick start
|
|
118
143
|
|
|
119
144
|
1. Install `omp-conductor` and `omp-telegram`. Pair the Telegram bot and enable its bridge.
|
|
@@ -129,6 +154,26 @@ For what the host needs before anything runs — `bun`, an authenticated `gh`,
|
|
|
129
154
|
|
|
130
155
|
The wizard reads the tracker with the same routing code as the daemon. It shows every issue that the next tick can route.
|
|
131
156
|
|
|
157
|
+
On an interactive TTY, setup uses the styled Clack interface. Piped setup
|
|
158
|
+
keeps the stable line protocol for automation. Set
|
|
159
|
+
`OMP_CONDUCTOR_PLAIN_UI=1` to use the readline interface on a TTY and replace
|
|
160
|
+
Clack progress in other verbs with their stable plain messages.
|
|
161
|
+
|
|
162
|
+
To make an interactive run replayable, save every accepted answer by its
|
|
163
|
+
stable key:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
omp-conductor setup --save-answers ./setup-answers.json
|
|
167
|
+
omp-conductor setup --answers ./setup-answers.json
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
An answers file is a JSON object whose values are strings or booleans.
|
|
171
|
+
Confirmations require booleans, text prompts require strings, and select
|
|
172
|
+
prompts require the exact displayed label. `--answers` never falls back to
|
|
173
|
+
an interactive prompt: a missing key or wrong value fails with the key and
|
|
174
|
+
question name. Piped line input remains supported, but an exhausted pipe
|
|
175
|
+
fails with the `--answers` alternative instead of waiting indefinitely.
|
|
176
|
+
|
|
132
177
|
Nothing changes before the consent step. After consent, setup does these actions:
|
|
133
178
|
|
|
134
179
|
- creates the required labels;
|
|
@@ -356,6 +401,19 @@ fleet, and both are covered above under
|
|
|
356
401
|
paused-fleet failure hides. `tail`, `extend`, `unblock` and the `worker`
|
|
357
402
|
controls are the day-to-day levers:
|
|
358
403
|
|
|
404
|
+
For automation, `status --json` emits the same project, layer, daemon,
|
|
405
|
+
Telegram, code-graph, run, cap, report, and sibling data as the text report.
|
|
406
|
+
The other read-only ledgers have stable JSON forms too:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
omp-conductor ledger --json
|
|
410
|
+
omp-conductor decision list --json
|
|
411
|
+
omp-conductor watch list --json
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
JSON output is never styled. Empty ledgers return their normal top-level object
|
|
415
|
+
with an empty `entries`, `decisions`, or `watches` array.
|
|
416
|
+
|
|
359
417
|
### Outcomes and cost: `stats`
|
|
360
418
|
|
|
361
419
|
`omp-conductor stats [--since 7d | 30d | YYYY-MM-DD] [--project NAME] [--json]`
|
|
@@ -520,13 +578,13 @@ reference — usage strings, flags and per-command behaviour — is in the
|
|
|
520
578
|
|
|
521
579
|
| Verb | What it does |
|
|
522
580
|
| --- | --- |
|
|
523
|
-
| `setup [area]` | The wizard: interview + probes. `--no-ai`, per-area amend, `host`, `graph`. |
|
|
581
|
+
| `setup [area]` | The wizard: interview + probes. `--no-ai`, `--answers FILE`, `--save-answers FILE`, per-area amend, `host`, `graph`. |
|
|
524
582
|
| `start` / `stop` / `restart` | Run the dispatch daemon. `restart` drains first; `stop --pane` also halts the pane. |
|
|
525
583
|
| `upgrade [--to VERSION]` | Pin one published npm release across CLI, omp plugin, Herdr plugin and brief. |
|
|
526
|
-
| `status` | Layered fleet report: dispatch, ticks, pane, recovery, telegram, daemon, then the project body. |
|
|
584
|
+
| `status [--json]` | Layered fleet report: dispatch, ticks, pane, recovery, telegram, daemon, then the project body. |
|
|
527
585
|
| `stats [--since 7d|30d|YYYY-MM-DD] [--json]` | What the fleet accomplished and at what cost, from the local store only: merges, merge rate, lead time, attempts and metered cost per merged issue, failure classes, gh calls. |
|
|
528
586
|
| `doctor [--json] [--probe-telegram]` | Read-only deployment health — gh auth, exact-case labels, systemd drift, config backup, sqlite integrity, spend telemetry, timezones, Telegram. Run after install and after every upgrade; exit 0 only when nothing failed. |
|
|
529
|
-
| `ledger` | The action audit — every mediated verb, refusal and turn budget. |
|
|
587
|
+
| `ledger [--json]` | The action audit — every mediated verb, refusal and turn budget. |
|
|
530
588
|
| `board` | Live terminal kanban from Queue to Settled. |
|
|
531
589
|
| `dashboard [--port N] [--host ADDR]` | Browser UI plus bearer-authenticated `/api/projects`: every project's daemon state, port and healthz. Loopback by default; the token lives at `<state>/dashboard-token`. |
|
|
532
590
|
| `hold [--keep-ticks]` | Pause claims and disarm ticks — the soft stop. |
|
|
@@ -538,7 +596,8 @@ reference — usage strings, flags and per-command behaviour — is in the
|
|
|
538
596
|
| `verb <conductor_*>` | Run a mediated verb from the CLI (external orchestration). |
|
|
539
597
|
| `friction <kind> --detail TEXT` | Record a bounded operator judgment — an escalation that belonged in a digest, or a report that was noise/surprising — feeding the learning loop. |
|
|
540
598
|
| `event record` / `report` / `message` | Record and deliver reports and messages through the durable outbox. |
|
|
541
|
-
| `decision open/resolve/withdraw/list` | Read and answer questions in the decision ledger
|
|
599
|
+
| `decision open/resolve/withdraw/list [--json]` | Read and answer questions in the decision ledger; JSON applies to `list`. |
|
|
600
|
+
| `watch add/list [--json]` | Record or list orchestrator-only conditions and carry notes; JSON applies to `list`. |
|
|
542
601
|
| `intake "<text>"` / `intake list` / `intake dismiss <id>` | Capture a raw idea durably (it lives in the store and survives restarts), list what is still pending, dismiss what turned out to be nothing. |
|
|
543
602
|
| `daemon [--once]` | Run the loop in the foreground — systemd's entry point. |
|
|
544
603
|
| `resume` | Clear pause and any `stop --pane` recovery pin; never re-arms. |
|
package/REFERENCE.md
CHANGED
|
@@ -265,6 +265,12 @@ Skip the reading half entirely with `--no-ai`:
|
|
|
265
265
|
omp-conductor setup --no-ai
|
|
266
266
|
```
|
|
267
267
|
|
|
268
|
+
For unattended setup, pass `--answers FILE` with a JSON object keyed by the
|
|
269
|
+
stable prompt keys captured by `--save-answers`; the command exits `1` and
|
|
270
|
+
names the first missing key instead of prompting. Add `--save-answers FILE`
|
|
271
|
+
to an interactive run to capture only accepted answers for deterministic
|
|
272
|
+
replay. Unknown keys are ignored so one file can cover a larger interview.
|
|
273
|
+
|
|
268
274
|
To fill in or revise just the brief later — the two `POLICY.md` sections above — run
|
|
269
275
|
the `brief` area, which re-asks the judgment questions and re-runs the probes:
|
|
270
276
|
|
|
@@ -2202,7 +2208,7 @@ least of all on a fleet whose session lives somewhere else.
|
|
|
2202
2208
|
## CLI reference
|
|
2203
2209
|
|
|
2204
2210
|
```bash
|
|
2205
|
-
omp-conductor setup [area] [--no-ai] [--project NAME]
|
|
2211
|
+
omp-conductor setup [area] [--no-ai] [--answers FILE] [--save-answers FILE] [--project NAME]
|
|
2206
2212
|
omp-conductor setup host [NAME] [--project NAME]
|
|
2207
2213
|
omp-conductor setup graph [--no-seed] [--print] [--project NAME]
|
|
2208
2214
|
omp-conductor start [--port N] [--project NAME]
|
|
@@ -2212,10 +2218,10 @@ omp-conductor restart [--now] [--timeout SECONDS] [--port N] [--project NAME]
|
|
|
2212
2218
|
omp-conductor upgrade [--to VERSION] [--project NAME]
|
|
2213
2219
|
omp-conductor upgrade-install --to VERSION [--project NAME]
|
|
2214
2220
|
omp-conductor upgrade-rollback
|
|
2215
|
-
omp-conductor status [--project NAME]
|
|
2221
|
+
omp-conductor status [--project NAME] [--json]
|
|
2216
2222
|
omp-conductor doctor [--project NAME] [--json] [--probe-telegram]
|
|
2217
|
-
omp-conductor ledger [--issue N] [--limit N] [--project NAME]
|
|
2218
|
-
omp-conductor board [--project NAME]
|
|
2223
|
+
omp-conductor ledger [--issue N] [--limit N] [--project NAME] [--json]
|
|
2224
|
+
omp-conductor board [--project NAME] [--json]
|
|
2219
2225
|
omp-conductor hold [--keep-ticks] [--project NAME]
|
|
2220
2226
|
omp-conductor stop [--pane] [--project NAME]
|
|
2221
2227
|
omp-conductor arm [--project NAME]
|
|
@@ -2234,26 +2240,28 @@ omp-conductor message --text TEXT [--project NAME]
|
|
|
2234
2240
|
omp-conductor decision open --question TEXT [--blocks TEXT] [--resolves-when COND] [--project NAME]
|
|
2235
2241
|
omp-conductor decision resolve <id> --answer TEXT [--project NAME]
|
|
2236
2242
|
omp-conductor decision withdraw <id> [--reason TEXT] [--project NAME]
|
|
2237
|
-
omp-conductor decision list [--project NAME]
|
|
2243
|
+
omp-conductor decision list [--project NAME] [--json]
|
|
2238
2244
|
omp-conductor watch add --note TEXT [--blocks TEXT] [--resolves-when COND] [--project NAME]
|
|
2239
|
-
omp-conductor watch list [--project NAME]
|
|
2245
|
+
omp-conductor watch list [--project NAME] [--json]
|
|
2240
2246
|
omp-conductor daemon [--once] [--port N] [--project NAME]
|
|
2241
2247
|
omp-conductor resume [--project NAME]
|
|
2242
2248
|
omp-conductor brief-upgrade [--migrate|--retrofit] [--apply] [--file PATH] [--project NAME]
|
|
2249
|
+
omp-conductor complete <zsh|bash|fish|powershell>
|
|
2250
|
+
omp-conductor complete -- <args...>
|
|
2243
2251
|
omp-conductor help
|
|
2244
2252
|
```
|
|
2245
2253
|
|
|
2246
2254
|
| Command | Scope | Behaviour |
|
|
2247
2255
|
| --- | --- | --- |
|
|
2248
|
-
| `setup [area] [--no-ai] [--project NAME]` | project | The deterministic interview,
|
|
2256
|
+
| `setup [area] [--no-ai] [--answers FILE] [--save-answers FILE] [--project NAME]` | project | The deterministic interview, with styled Clack prompts on an interactive TTY and byte-stable plain output for pipes or `OMP_CONDUCTOR_PLAIN_UI=1`. `--answers` validates a JSON object of stable prompt keys before setup and replaces every prompt; a missing required key exits `1` naming the key and file instead of hanging. `--save-answers` records accepted interactive answers as replayable JSON after a successful run. Bare setup is a full first run, or — when the project already exists — a chooser of which area to amend. Naming an area positionally skips that chooser and amends only that area: `tracker`, `gates`, `caps`, `code-graph`, `authority`, `policy`, `escalation`, `reporting`, `brief`. `host` and `graph` are install subcommands rather than areas and are matched first; anything else exits `2` listing both vocabularies. Every prompt shows its current value as the default, and Enter accepts what you see; `Ctrl-C` at any prompt abandons the run and writes nothing. Setup also **reads your repos to propose answers**: the gates prompt is pre-filled from what CI actually runs, and the brief's `## Project context` and release procedure are drafted from every routing repo and shown for confirmation before anything is written. Each probe is a short session with **no shell, no editor and no verbs** in a throwaway shallow clone, and every answer is a proposal you edit or decline — a probe that cannot clone, cannot reach a model, or answers unusably costs you one warning and the shipped stub. `--no-ai` asks every question with the reading half removed. |
|
|
2249
2257
|
| `setup host [--project NAME]` | host | Re-render and stage the systemd unit, then **run** the install: `install -m 0644` into `/etc/systemd/system`, `daemon-reload`, `enable`, `restart`. Stages the fleet recovery oneshot (`omp-conductor-recover.service`) and its playbook (`/usr/local/sbin/omp-conductor-recover`) alongside, and installs them **before** the fleet units: both fleet units carry `OnFailure=` to the recovery unit, so a crash-looped daemon or herdr session now collects evidence durably, attempts one bounded recovery, and pages tier-2 instead of dying silently (#485). Every command is shown with its exact argv, one confirm covers the batch, and `sudo` asks for your password once before the first step — or is skipped entirely on a fleet that genuinely runs as root. The first failure stops the rest and prints the un-run remainder verbatim so you can finish by hand. Refuses an *escalated* invocation (`sudo`, or `sudo -i`/`su -` detected by the invoking account disagreeing with the fleet's) before writing anything, naming both accounts, because staging derives the unit's `User=`/`HOME=` from whoever ran it. On a non-Linux host the files are still staged and only the `systemctl` steps are refused. |
|
|
2250
2258
|
| `setup graph [--no-seed] [--print] [--project NAME]` | project | The code-graph install end to end, in one preview and one confirm: check the prerequisites read-only and stop before installing anything when `codebase-memory-mcp` is absent or no MCP entry mounts it (printing the entry to add); `git clone` each missing index-only checkout **as you, never through sudo**; install and enable `cbm-reindex.timer` as root; then seed one indexing run so the first fetch happens while you watch, and verify with the same probe `status` uses. A repo that does not verify is a failure with the remediation, not a success — staged-but-not-trusted is how you discover months later that no worker read an index. `--no-seed` enables the timer without the seeding run and says plainly the graph is unusable until it first fires; it never skips the prerequisite or clone steps. `--print` changes nothing. Exits `1` when no repo has [`graphProject`](#configuration). |
|
|
2251
2259
|
| `start` | host | Start `herdr-fleet.service` when that optional unit is installed, clearing a previous pane-recovery pin, then start the dispatch daemon and wait until it answers `GET /healthz`. When `omp-conductor.service` is installed, systemd is the only start path: even `start --project NAME` restores the shared unit and uses the name only to verify that `/healthz` serves the requested project. A detached daemon is allowed only when the unit is proven absent. It never clears pause or arms ticks. Refuses if a daemon is already live, naming its pid; manager refusal or unprovable ownership is an error rather than a detached fallback. |
|
|
2252
2260
|
| `stop` | fleet | Prefer `systemctl stop omp-conductor.service` when that unit's MainPID is the live daemon — systemd then owns the stop and will not schedule a restart for the exit it just requested. Otherwise `SIGTERM`, then `SIGKILL` after a 10-second grace period. Prints `not running` when there is nothing to stop, and tags the confirmation with `(via systemctl)` when the unit path was used. |
|
|
2253
2261
|
| `restart [--now] [--timeout SECONDS] [--port N] [--project NAME]` | host | Drains the fleet by default: pause new claims, wait until live workers reach `0 / N` (bounded by `--timeout SECONDS`, default 1800 = 30 min), restart, then restore the prior dispatch state. A daemon serving multiple configured projects makes restart host-wide: `--project` is rejected because draining one queue and restarting the shared process would kill another project's workers. Prefer `systemctl restart` when the unit owns the live pid so the replacement stays supervised; only a host proven not to have the installed unit may fall back to the standalone stop/start path. `--now` skips the drain and restarts immediately, orphaning any live runs (old behaviour). A drain that hits `--timeout` restarts nothing and leaves dispatch paused — `omp-conductor resume` lifts it, or re-run `restart` to keep waiting. The new process **salvages dirty live worktrees before orphaning** those rows — see [Deploying a new package onto a busy fleet](#deploying-a-new-package-onto-a-busy-fleet). |
|
|
2254
2262
|
| `upgrade [--to VERSION] [--project NAME]` | host | Deterministically update the Bun-global CLI, omp plugin, Herdr recovery plugin, and managed brief as one release. Resolves the npm version and exact `gitHead`, pauses only new claims, drains active workers, installs all surfaces, reloads Herdr and the daemon, waits for pane recovery, verifies identities and fleet health twice, then restores the original dispatch state. Host-wide by default: one daemon serves every configured project, so a bare run drains all of them and refreshes every brief. `--project` is rejected when the live daemon serves several projects — draining one queue and restarting the shared daemon would kill another's workers. A no-op when already current. Failure leaves dispatch paused. Must run outside a Herdr-managed session. The same transaction runs detached, without a human, as the fleet-installs-itself path: the orchestrator calls the `conductor_install` verb under the granted `install` shape, the daemon validates the version against npm and starts a transient systemd unit (`upgrade-install`) outside the pane and the daemon, and the first tick after the restart verifies version, `/healthz`, ticks, pane and `doctor` against the durable upgrade journal before restoring dispatch — rolling back and paging tier-2 on any gap. |
|
|
2255
|
-
| `status [--project NAME]` | fleet | Layered fleet report first: `dispatch` / `ticks` / next scheduled tick / `pane` / `recovery` / `herdr` / `telegram` / `brief` / `decisions` / optional `failure classes` and `code graph` / `daemon`, then the project body. The project body includes the latest completed dispatch timestamp, ready/routed/admitted counts, bounded hold groups, and the GitHub API budget (`graphql` / `core` remaining and reset, in the caps block); API failures are marked `DEGRADED` so queue starvation cannot look idle. Active-run lines overlay cooperative worker `paused`/`pausing` from `/healthz` without changing SQLite `running` state or the live worker count. The next tick comes from the live heartbeat process, not a guess from log timestamps. Telegram health uses `getMe` to prove API authentication without sending a message and reports inbound bridge configuration separately. Configured graphs report prerequisites, indexed repos, timer state, and refresh freshness without blocking dispatch. A `reports` block lists everything the outbox has not delivered, with its age, and prints `pending` (nobody has it) differently from `SENDING` (outcome unknown, it may already have arrived) — see [Report delivery](#report-delivery-the-outbox). The daemon block includes `rss` from `/healthz`; live workers add a busy-deploy warning. A `.conductor-stalled` marker adds an `orchestrator STALLED since …` line. |
|
|
2256
|
-
| `ledger [--issue N] [--limit N]` | project | The action audit: every [mediated-verb](#the-mediated-verbs-126) mutation and every next-attempt turn budget. Verb entries include the arguments, decision, named refusal, and resulting SHA. Turn-budget entries remain after an override is replaced or consumed. Reads (`conductor_pr_status`) are absent so polling cannot bury the signal. `--issue` narrows both histories; `--limit` defaults to 50. Recent verb refusals and pending turn overrides also appear in `status`. |
|
|
2263
|
+
| `status [--project NAME] [--json]` | fleet | Layered fleet report first: `dispatch` / `ticks` / next scheduled tick / `pane` / `recovery` / `herdr` / `telegram` / `brief` / `decisions` / optional `failure classes` and `code graph` / `daemon`, then the project body. `--json` emits the stable structured snapshot directly, without ANSI or prose, for automation. The text project body includes the latest completed dispatch timestamp, ready/routed/admitted counts, bounded hold groups, and the GitHub API budget (`graphql` / `core` remaining and reset, in the caps block); API failures are marked `DEGRADED` so queue starvation cannot look idle. Active-run lines overlay cooperative worker `paused`/`pausing` from `/healthz` without changing SQLite `running` state or the live worker count. The next tick comes from the live heartbeat process, not a guess from log timestamps. Telegram health uses `getMe` to prove API authentication without sending a message and reports inbound bridge configuration separately. Configured graphs report prerequisites, indexed repos, timer state, and refresh freshness without blocking dispatch. A `reports` block lists everything the outbox has not delivered, with its age, and prints `pending` (nobody has it) differently from `SENDING` (outcome unknown, it may already have arrived) — see [Report delivery](#report-delivery-the-outbox). The daemon block includes `rss` from `/healthz`; live workers add a busy-deploy warning. A `.conductor-stalled` marker adds an `orchestrator STALLED since …` line. |
|
|
2264
|
+
| `ledger [--issue N] [--limit N] [--json]` | project | The action audit: every [mediated-verb](#the-mediated-verbs-126) mutation and every next-attempt turn budget. Verb entries include the arguments, decision, named refusal, and resulting SHA. Turn-budget entries remain after an override is replaced or consumed. Reads (`conductor_pr_status`) are absent so polling cannot bury the signal. `--issue` narrows both histories; `--limit` defaults to 50. `--json` emits one stable object with `project`, optional `issue`, `entries`, `refused`, and `turnOverrides`. Recent verb refusals and pending turn overrides also appear in `status`. |
|
|
2257
2265
|
| `board [--project NAME]` | fleet | Live keyboard-driven kanban over the same SQLite and `/healthz` truth as `status`, plus the tracker's current labels: Queue, Claimed, Running, Green, Blocked, Failed, Orphaned, the last 24 hours of Merged and Settled, and Parked (an issue the tracker has not confirmed closed — still open, or a label read that failed — so nothing dispatches it until a human labels it). Columns are mutually exclusive and describe current state, not the newest run row, so a requeued issue is queued rather than failed and a closed issue is neither. Refreshes run/spend/turn values every second, and health plus the label read every ten seconds. `Enter` follows the selected transcript in place; `u` invokes the existing unblock workflow on a Blocked, Failed, or Orphaned card; `i` / `p` open the issue / PR; `r` refreshes health; `?` shows all keys. Requires an interactive terminal of at least 50×20. |
|
|
2258
2266
|
| `hold [--keep-ticks] [--project NAME]` | fleet | Soft stop: pause claiming **and** disarm ticks. Daemon and pane stay up. Prefer this when the intent is "stop the conductor" without killing processes. `--keep-ticks` pauses claiming but leaves the arm marker, so the heartbeat keeps reporting and `resume` alone restores the fleet — no fresh arm challenge. See [Stop the conductor](README.md#stop-the-conductor-hold--stop). |
|
|
2259
2267
|
| `stop [--pane] [--project NAME]` | fleet | Stop the conductor: pause claiming, disarm ticks, then stop the dispatch daemon (systemctl-aware). Pane stays up unless `--pane` is passed. `stop --pane` also pins herdr-conductor recovery off for the conductor agent only — it does **not** stop `herdr-fleet.service` or any other herdr session. Fail-closed: exits nonzero unless the agent is proven gone. To bounce the daemon without stopping the fleet, use `restart`. |
|
|
@@ -2270,9 +2278,9 @@ omp-conductor help
|
|
|
2270
2278
|
| `decision open --question TEXT [--blocks TEXT] [--resolves-when COND]` | project | Record a question the orchestrator has put to you, and print its id. A question that lives only in a session's context is lost at the next compaction — after which it is either asked twice or dropped silently. `--resolves-when` attaches a machine-checkable condition: `pr-merged:<https url>`, `pr-checks-green:<https url>`, `pr-mergeable:<https url>`, `issue-closed:<n>`, `npm-version:<pkg>@<version>`, or `rate-limit-reset:github`; anything else exits `2` listing the six forms. See [The decision ledger](#the-decision-ledger-136). |
|
|
2271
2279
|
| `decision resolve <id> --answer TEXT` | project | Record what you decided. Exits `1` naming the id when it is unknown or no longer open, so a second answer cannot overwrite the first. |
|
|
2272
2280
|
| `decision withdraw <id> [--reason TEXT]` | project | Close a question the session stopped needing, with why. Same guard as `resolve`. |
|
|
2273
|
-
| `decision list` | project | Open questions, oldest first: id, age, what each blocks, whether its condition is met, and the question. Prints `no open decisions` when there are none. Watches are not listed here — `watch list` shows those. |
|
|
2281
|
+
| `decision list [--json]` | project | Open questions, oldest first: id, age, what each blocks, whether its condition is met, and the question. `--json` emits `{ project, decisions }`; the empty state is an empty array. Prints `no open decisions` in text mode when there are none. Watches are not listed here — `watch list` shows those. |
|
|
2274
2282
|
| `watch add --note TEXT [--blocks TEXT] [--resolves-when COND]` | project | Record a condition or carry note the orchestrator set for itself, with no human in the loop (#459). `--resolves-when` attaches a machine-checkable condition the daemon checks for you; a met watch wakes the next tick exactly as a met question does. Renders under its own "Watches" heading, is never counted in `decisions N open`, and has no seven-day expiry. |
|
|
2275
|
-
| `watch list` | project | Open watches, oldest first: id, age, what each blocks, whether its condition is met, and the note. Prints `no watches` when there are none. |
|
|
2283
|
+
| `watch list [--json]` | project | Open watches, oldest first: id, age, what each blocks, whether its condition is met, and the note. `--json` emits `{ project, watches }`; the empty state is an empty array. Prints `no watches` in text mode when there are none. |
|
|
2276
2284
|
| `daemon` | host | Run the loop in the **foreground**, ticking every 5 minutes and serving `/healthz`. Admitted workers run in a tracked background pool, so settlement and capacity checks remain periodic while they work; shutdown drains the pool before closing the store. This is what `start` launches and what a systemd unit should call. |
|
|
2277
2285
|
| `daemon --once` | host | Run a single tick, wait for workers admitted by that tick, and exit. No HTTP server or pidfile — a drill must not register itself as the daemon, or the next reader believes it and the real daemon's in-flight runs get reconciled as orphans. |
|
|
2278
2286
|
| `--port N` | — | Accepted by `start`, `restart` and `daemon`. Both `--port 9000` and `--port=9000` work; missing or out of range exits `2` rather than falling back to the default, because probing the wrong endpoint is worse than a hard failure. |
|
|
@@ -2285,6 +2293,7 @@ omp-conductor help
|
|
|
2285
2293
|
| `--retrofit` | — | Only for `brief-upgrade`. Propose (or with `--apply`, write) a `YOURS TO EDIT` banner before the first owned-topic heading on a hand-written brief. |
|
|
2286
2294
|
| `--apply` | — | Only for `brief-upgrade`. Confirms `--migrate` / `--retrofit`. On its own it exits `2`: the legacy single-file merge was removed in 0.4.3. |
|
|
2287
2295
|
| `--file PATH` | — | Only for `brief-upgrade`. Check a brief that is not where the wizard would have put it, on a host that may have no config at all. |
|
|
2296
|
+
| `complete <zsh\|bash\|fish\|powershell>` | none | Print a shell completion script. Source it directly for a session or save it and source the file from the shell rc. Interactive setup offers the persistent zsh/bash install; fish and PowerShell remain available manually. `complete -- <args...>` is the fast shell callback protocol: static verbs, subcommands, flags and setup areas come from the command manifest, while project names load from config and fail empty on any read error. |
|
|
2288
2297
|
| `help`, `--help`, `-h` | none | Print usage. An unknown or missing verb prints it too, and exits `2`. |
|
|
2289
2298
|
|
|
2290
2299
|
Pause is a sentinel under the state directory and survives a daemon restart.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omp-conductor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A 24/7 dispatcher that takes ready GitHub issues to green, mergeable PRs using omp coding sessions, with tiered escalation first to an orchestrator session and then to a human.",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"schema": "bun run src/generate-schema.ts"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@bomb.sh/tab": "0.0.22",
|
|
37
|
+
"@clack/prompts": "1.7.0",
|
|
36
38
|
"yaml": "^2.9.0",
|
|
37
39
|
"zod": "^4"
|
|
38
40
|
},
|
|
@@ -381,12 +381,27 @@ did not arrive.
|
|
|
381
381
|
|
|
382
382
|
## Human messages
|
|
383
383
|
|
|
384
|
-
A human writing to you between ticks is not a tick
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
384
|
+
A human writing to you between ticks is not a tick, and how a reply reaches
|
|
385
|
+
them depends on which of three shapes this turn is — a shape the brief states,
|
|
386
|
+
never something a session infers from a tool's error:
|
|
387
|
+
|
|
388
|
+
- **A locally injected tick** has no inbound message and no topic to keep:
|
|
389
|
+
hand anything reportable to the outbox with `omp-conductor report`, and reach
|
|
390
|
+
the operator directly with `omp-conductor message --text "<the message>"`,
|
|
391
|
+
declaring the escalation category when you are asking. Your tick prompt names
|
|
392
|
+
this shape — it carries the delivery rule that says when a tick was injected
|
|
393
|
+
locally.
|
|
394
|
+
- **A turn that began as an inbound Telegram message** is answered with a
|
|
395
|
+
**single `telegram_send` call** — one message, the answer only, from evidence
|
|
396
|
+
you already hold or go and fetch — omitting **both** `chat_id` and `thread_id`
|
|
397
|
+
so the reply keeps the topic the message came from.
|
|
398
|
+
- **An interactive terminal session**, started by hand, is one the operator is
|
|
399
|
+
reading live: there, end-of-turn text **is** the delivery, and Telegram is
|
|
400
|
+
used only when it must demonstrably arrive — `telegram_send` resolves
|
|
401
|
+
`chat_id` from the last inbound message and refuses without one.
|
|
402
|
+
|
|
403
|
+
While handling any turn, produce no visible commentary between tool calls —
|
|
404
|
+
reasoning stays in thinking, actions stay in tools.
|
|
390
405
|
|
|
391
406
|
**Reply where the message arrived.** `thread_id` defaults to the active topic
|
|
392
407
|
*only while `chat_id` is omitted*, so a fleet whose Telegram is a forum topic
|
package/src/briefs/policy.md
CHANGED
|
@@ -85,11 +85,19 @@ authoritative. All four scopes, spelled out:
|
|
|
85
85
|
interrupt when operator availability permits; everything else waits for one
|
|
86
86
|
daily rollup.
|
|
87
87
|
|
|
88
|
-
**Delivery.**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
**Delivery.** A reply reaches the operator by the shape of the turn it answers.
|
|
89
|
+
A **locally injected tick** delivers through the outbox — `omp-conductor report`
|
|
90
|
+
for anything reportable, `omp-conductor message` to reach the operator directly —
|
|
91
|
+
because a report written as end-of-turn text on a tick reaches nobody. A turn
|
|
92
|
+
that **began as an inbound Telegram message** answers with `telegram_send`,
|
|
93
|
+
omitting `chat_id` and `thread_id` so the reply keeps its topic. An **interactive
|
|
94
|
+
terminal session** is read live by the operator, so end-of-turn text *is* the
|
|
95
|
+
delivery there; `telegram_send` resolves `chat_id` from the last inbound message
|
|
96
|
+
and refuses without one, so it is used only when the message must demonstrably
|
|
97
|
+
arrive. The provable delivery paths are `omp-conductor report` (persisted and
|
|
98
|
+
retried by the daemon) and `telegram_send` (direct messages); `telegram_ask` is
|
|
99
|
+
the decision primitive, not delivery evidence. Hand every reportable event to
|
|
100
|
+
the conductor's outbox:
|
|
93
101
|
|
|
94
102
|
```
|
|
95
103
|
omp-conductor report --text "<the whole report>" # a material event
|
package/src/clack-ui.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// @clack/prompts is ESM-only and currently imports node:process in a shape
|
|
2
|
+
// that blocks a future `bun build`; this CLI intentionally executes TS in Bun.
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import { stdin, stdout } from "node:process";
|
|
5
|
+
import type { Readable, Writable } from "node:stream";
|
|
6
|
+
|
|
7
|
+
import type { TerminalUi } from "./wizard-ui.ts";
|
|
8
|
+
|
|
9
|
+
interface ClackIo {
|
|
10
|
+
input?: Readable;
|
|
11
|
+
output?: Writable;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Interactive WizardUi driver. Callers gate this on TTY input and output. */
|
|
15
|
+
export function clackUi(io: ClackIo = {}): TerminalUi {
|
|
16
|
+
const input = io.input ?? stdin;
|
|
17
|
+
const output = io.output ?? stdout;
|
|
18
|
+
const promptIo = { input, output };
|
|
19
|
+
let closed = false;
|
|
20
|
+
let started = false;
|
|
21
|
+
const start = (): void => {
|
|
22
|
+
if (started) return;
|
|
23
|
+
started = true;
|
|
24
|
+
p.intro("omp-conductor setup", { output });
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
close: (message) => {
|
|
29
|
+
if (closed) return;
|
|
30
|
+
closed = true;
|
|
31
|
+
if (started) p.outro(message ?? "Setup finished.", { output });
|
|
32
|
+
},
|
|
33
|
+
notify: (message, type = "info") => {
|
|
34
|
+
start();
|
|
35
|
+
if (message.includes("\n")) {
|
|
36
|
+
const title = type === "warning" ? "Warning" : type === "error" ? "Error" : undefined;
|
|
37
|
+
p.note(message, title, { output });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (type === "warning") p.log.warning(message, { output });
|
|
41
|
+
else if (type === "error") p.log.error(message, { output });
|
|
42
|
+
else p.log.info(message, { output });
|
|
43
|
+
},
|
|
44
|
+
confirm: async (title, message) => {
|
|
45
|
+
start();
|
|
46
|
+
const result = await p.confirm({
|
|
47
|
+
...promptIo,
|
|
48
|
+
message: `${title}\n${message}`,
|
|
49
|
+
initialValue: false,
|
|
50
|
+
});
|
|
51
|
+
return p.isCancel(result) ? undefined : result;
|
|
52
|
+
},
|
|
53
|
+
input: async (title, placeholder) => {
|
|
54
|
+
start();
|
|
55
|
+
const result = await p.text({
|
|
56
|
+
...promptIo,
|
|
57
|
+
message: title,
|
|
58
|
+
placeholder,
|
|
59
|
+
defaultValue: placeholder,
|
|
60
|
+
});
|
|
61
|
+
return p.isCancel(result) ? undefined : result;
|
|
62
|
+
},
|
|
63
|
+
select: async (title, options, dialogOptions) => {
|
|
64
|
+
start();
|
|
65
|
+
const index = Math.max(
|
|
66
|
+
0,
|
|
67
|
+
Math.min(dialogOptions?.initialIndex ?? 0, options.length - 1),
|
|
68
|
+
);
|
|
69
|
+
const initialValue = options[index]?.label;
|
|
70
|
+
const result = await p.select({
|
|
71
|
+
...promptIo,
|
|
72
|
+
message: title,
|
|
73
|
+
options: options.map((option) => ({
|
|
74
|
+
value: option.label,
|
|
75
|
+
label: option.label,
|
|
76
|
+
hint: option.description,
|
|
77
|
+
})),
|
|
78
|
+
initialValue,
|
|
79
|
+
});
|
|
80
|
+
return p.isCancel(result) ? undefined : result;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|