@edgehero/pi-dispatch-admin 1.3.0 → 1.5.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 +8 -6
- package/dist/index.mjs +1347 -497
- package/package.json +1 -1
- package/skills/operate-pi-dispatch/SKILL.md +67 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edgehero/pi-dispatch-admin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Operator console (a pi extension) + skill for pi-dispatch: run the pi coding agent as a self-hosted service. A /dispatch TUI for the queue, spend caps, run history, editable GitHub, GitLab, Forgejo and Azure DevOps triggers (cron/label/comment/PR/MR/work item), and scheduled pause windows — plus AI-operable, human-confirmed controls. Runs against a live pi-dispatch deployment.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -41,6 +41,12 @@ dialog before it takes effect**:
|
|
|
41
41
|
(per folder/repo "quiet hours": runs for a scope are deferred between certain times and auto-resume after;
|
|
42
42
|
`dispatch_pauses` lists them with their index). `dispatch_pause_edit` is a partial change — pass the index
|
|
43
43
|
plus only the fields to alter. Deferring never drops a job and costs no budget.
|
|
44
|
+
- `dispatch_limit_add` / `dispatch_limit_edit` / `dispatch_limit_delete` — manage scoped limits (per
|
|
45
|
+
repo/folder budget caps and concurrency: `day`/`week`/`month` job-count caps refuse a job pre-spend with
|
|
46
|
+
reason `scope-cap`, never retried; `concurrent` defers the excess, never drops it; `dispatch_limits`
|
|
47
|
+
lists them with their index and used counts). `dispatch_limit_edit` is a partial change — pass the index
|
|
48
|
+
plus only the fields to alter. Scopes match exactly (a repo `owner/name` or an ABSOLUTE folder path, no
|
|
49
|
+
globs).
|
|
44
50
|
|
|
45
51
|
Use them like this:
|
|
46
52
|
|
|
@@ -159,11 +165,14 @@ about the trigger is the same — the `on.type`, the `{any, all, none}` label pr
|
|
|
159
165
|
Three things are NOT the same, and all of them refuse at load rather than misbehaving quietly:
|
|
160
166
|
|
|
161
167
|
- **`pull_request` actions are the forge's own words.** GitHub takes
|
|
162
|
-
`labeled | opened | synchronize | reopened | review_submitted`; GitLab takes
|
|
163
|
-
`open | update | reopen | approved`; Forgejo takes
|
|
164
|
-
`label_updated | opened | synchronized | reopened`; Azure takes `created | updated
|
|
168
|
+
`labeled | opened | synchronize | reopened | review_submitted | closed`; GitLab takes
|
|
169
|
+
`open | update | reopen | approved | close`; Forgejo takes
|
|
170
|
+
`label_updated | opened | synchronized | reopened | closed`; Azure takes `created | updated` and has
|
|
171
|
+
no close word (a close trigger on azure is refused at load — not yet covered, not declined). A word
|
|
165
172
|
from the wrong forge is refused when the file is written. It would not break anything otherwise — it
|
|
166
173
|
would simply never match an event, and the trigger would look configured while doing nothing.
|
|
174
|
+
The close word rides **alone**: a rule mixing it with other actions is refused, because a close is
|
|
175
|
+
gated on the actor who closed the item where every other action gates on the author or a label.
|
|
167
176
|
`review_submitted` is GitHub's `pull_request_review` event: it fires on **every** submitted review, so
|
|
168
177
|
add `reviewState` (`approved | changes_requested | commented`) to narrow which verdicts are worth paying
|
|
169
178
|
for. That field is github-only and legal only beside `review_submitted`; anywhere else it refuses at
|
|
@@ -176,6 +185,32 @@ Three things are NOT the same, and all of them refuse at load rather than misbeh
|
|
|
176
185
|
requests, so `any`/`all`/`none` could never match and a rule that loads clean and never fires reads as a
|
|
177
186
|
broken harness.
|
|
178
187
|
|
|
188
|
+
## Close triggers and one-shots — `on.once`, and the re-arm
|
|
189
|
+
|
|
190
|
+
Two shapes fire on a close: the `issue` trigger type (an issue closing, `on.action` in the forge's close
|
|
191
|
+
word), and a `pull_request` rule whose only action is the close word (on GitHub and Forgejo a merged PR
|
|
192
|
+
counts as closed; on GitLab only an explicit close fires it). Both
|
|
193
|
+
may carry `on.number` to pin one specific item, and `once: true` (which requires `number`) makes the rule
|
|
194
|
+
a **one-shot**: after one run the worker marks the entry spent by adding
|
|
195
|
+
`on.disarmed: { at, jobId }` to it in `triggers.json`. The entry is never deleted — run history
|
|
196
|
+
attributes by array position — so `dispatch_triggers` and the panel keep showing it, marked spent,
|
|
197
|
+
matching nothing.
|
|
198
|
+
|
|
199
|
+
Four things to say when an operator asks:
|
|
200
|
+
|
|
201
|
+
- **A failed run still spends it.** "Fired" means "produced a run record", so a one-shot whose job failed
|
|
202
|
+
is spent too. The fix is the re-arm below, after they have read why it failed.
|
|
203
|
+
- **Re-arming is deleting `on.disarmed` from the entry** in `triggers.json`, nothing else. It is an
|
|
204
|
+
operator file edit: no tool and no panel key writes or removes that mark, so say so plainly rather than
|
|
205
|
+
reaching for `dispatch_trigger_edit` (which changes the flow only).
|
|
206
|
+
- **Authoring one**: `dispatch_trigger_add` takes `kind: issue` (plus `number` and `once`), behind the
|
|
207
|
+
same confirm dialog as every trigger write, and a close-only `pull_request` rule accepts the same two
|
|
208
|
+
fields. The shared validator refuses anything malformed, and `once` requires `number`.
|
|
209
|
+
- **Both services must read the same file.** The disarm is a worker write, so a receiver pointed at a
|
|
210
|
+
different `triggers.json` stays armed; `PI_TRIGGERS_FILE` set to one absolute path is the fix, and
|
|
211
|
+
`pi-dispatch doctor` warns about it. In the compose topology the receiver's read-only mount lags a
|
|
212
|
+
disarm until restart, and the worker's own pre-spend check is what prevents a second run meanwhile.
|
|
213
|
+
|
|
179
214
|
## Racing two agents on one event — `run.replicas`
|
|
180
215
|
|
|
181
216
|
`"replicas": 2` on a `label`, `comment` or `pull_request` trigger turns one delivery into **two independent
|
|
@@ -190,9 +225,10 @@ Three things to say when an operator asks about it:
|
|
|
190
225
|
- **You cannot set it from here.** There is no `replicas` parameter on `dispatch_trigger_add` or
|
|
191
226
|
`_edit`, and no panel key. It is a reviewed edit to `triggers.json`, deliberately: a spend multiplier is
|
|
192
227
|
plainly a capability a model should not gain. Say so rather than looking for a way around it.
|
|
193
|
-
- **It refuses on cron, and beside `
|
|
194
|
-
replicas would edit one working tree with no gate and no undo.
|
|
195
|
-
where replicas exist to fork it.
|
|
228
|
+
- **It refuses on cron, beside `resume`, and beside `once: true`.** A local job's `/workspace` IS the
|
|
229
|
+
operator's folder, so two replicas would edit one working tree with no gate and no undo. A resumed run
|
|
230
|
+
continues one lineage where replicas exist to fork it. And a one-shot promises exactly one run, which N
|
|
231
|
+
racing sandboxes contradict.
|
|
196
232
|
|
|
197
233
|
`dispatch_trigger_add` takes an optional `forge` parameter, defaulting to `github`. Unlike `image`, this
|
|
198
234
|
one IS offered to the model — a model that can already add a GitHub trigger can already arm a paid run,
|
|
@@ -205,3 +241,28 @@ If you are asked why a GitLab trigger did not fire, the usual answers in order:
|
|
|
205
241
|
2. **No label was added by that event.** GitLab has no `labeled` action; the trigger fires on the labels
|
|
206
242
|
an event *added*, so editing an already-labelled issue does nothing. This is deliberate.
|
|
207
243
|
3. **The action word belongs to the other forge.** See above.
|
|
244
|
+
|
|
245
|
+
## Scoped limits — and the folder mutex you cannot turn off
|
|
246
|
+
|
|
247
|
+
`scoped-limits.json` holds per-scope bounds beside the deployment-global ones: `day`/`week`/`month` cap
|
|
248
|
+
how many jobs a repo or folder may run per window (refused pre-spend, reason `scope-cap`, never retried,
|
|
249
|
+
the scope's own counter still counts the refusal), and `concurrent` caps how many run at once (the excess
|
|
250
|
+
is deferred to the delayed set and runs when a slot frees — never dropped, no budget spent while waiting).
|
|
251
|
+
Edits apply live: the worker hot-reloads the file and keeps the last good version on a bad edit.
|
|
252
|
+
|
|
253
|
+
Separate from all of that, **local jobs carry a built-in one-job-per-folder mutex: at most one job per
|
|
254
|
+
folder at a time, always on, with NO configuration, NO tool, and NO panel key.** If an operator asks to
|
|
255
|
+
disable it, say plainly that there is no switch, deliberately: two agents editing one working tree race
|
|
256
|
+
each other with no gate and no undo, and an off switch's only use is re-opening that race. A `concurrent`
|
|
257
|
+
value on a folder scope can never raise the mutex's one-at-a-time (the lower bound always wins).
|
|
258
|
+
|
|
259
|
+
Three more things to say when asked:
|
|
260
|
+
|
|
261
|
+
- **A `scope-cap` refusal is final for that window.** The counter is not resettable from any tool; the
|
|
262
|
+
window rolls over on its own (day/week/month, UTC). Raising the cap via `dispatch_limit_edit` takes
|
|
263
|
+
effect at the next job.
|
|
264
|
+
- **Deferrals are visible only as the queue's delayed count** (the panel's status line shows it when it
|
|
265
|
+
is nonzero). That count also includes cron next-occurrences and retry backoff — a nonzero number is
|
|
266
|
+
normal on any deployment with schedules.
|
|
267
|
+
- **Per-scope in-flight is not displayed anywhere.** The panel and `dispatch_limits` show `concurrent` as
|
|
268
|
+
configuration only; the live count lives inside the worker process and no reader can see it.
|