@oisin-ee/pi-dogsled 0.13.0 → 0.14.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 +58 -24
- package/package.json +1 -1
- package/pi/index.ts +12 -1
- package/plugins/dogsled/skills/dogsled/SKILL.md +10 -4
package/README.md
CHANGED
|
@@ -11,13 +11,13 @@ Install the binary on each Herdr host. Use the same release tag in each command.
|
|
|
11
11
|
Binary:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
mise use github:oisin-ee/dogsled@v0.
|
|
14
|
+
mise use github:oisin-ee/dogsled@v0.14.0
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Pi (extension, tools, and skill), from npm or as part of `@oisin-ee/oisinpi`:
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
|
-
pi install npm:@oisin-ee/pi-dogsled@0.
|
|
20
|
+
pi install npm:@oisin-ee/pi-dogsled@0.14.0
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
At session start the extension compares its version with `dogsled --version` and warns when they differ.
|
|
@@ -25,14 +25,14 @@ At session start the extension compares its version with `dogsled --version` and
|
|
|
25
25
|
Claude Code (hooks, tools, and skill):
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
|
-
claude plugin marketplace add oisin-ee/dogsled#v0.
|
|
28
|
+
claude plugin marketplace add oisin-ee/dogsled#v0.14.0
|
|
29
29
|
claude plugin install dogsled@dogsled
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
Codex (hooks, tools, and skill):
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
|
-
codex plugin marketplace add oisin-ee/dogsled --ref v0.
|
|
35
|
+
codex plugin marketplace add oisin-ee/dogsled --ref v0.14.0
|
|
36
36
|
codex plugin add dogsled@dogsled
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@ The [Codex CLI reference](https://developers.openai.com/codex/cli/reference#code
|
|
|
42
42
|
Herdr (status-board popup):
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
|
-
herdr plugin install oisin-ee/dogsled --ref v0.
|
|
45
|
+
herdr plugin install oisin-ee/dogsled --ref v0.14.0 --yes
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
Harness plugins use the binary on `PATH`. They do not install it or start host services.
|
|
@@ -156,7 +156,7 @@ dogsled close w9:p1 --host netcup --json
|
|
|
156
156
|
`close` ends a child now, for an owner who does not want to wait for the sweep. `--host` defaults to the host of the pane's open item, else local.
|
|
157
157
|
In an agent worktree (locked `agent:worktree`, as dogsled makes them), it runs the repository's `teardown` commands in the worktree (see [Repository configuration](#repository-configuration)).
|
|
158
158
|
Then it closes every pane that uses the worktree and the Herdr workspace that shows it, unlocks the worktree, and runs `git worktree remove` without force.
|
|
159
|
-
|
|
159
|
+
Close uses the same loss rule as cleanup: a clean checkout and every commit held by the default branch or freshly fetched remote refs. It deletes the branch under that same rule. Local-only work refuses close, including an explicit owner close.
|
|
160
160
|
When `place` opened the main checkout's workspace as the anchor and no linked worktree is open in Herdr any more, close closes that anchor too. It does so only while the anchor is unfocused, is not the caller's, and has one pane with no agent.
|
|
161
161
|
A pane outside a worktree, or in an unlocked worktree (someone's own checkout), is closed alone; the checkout stays.
|
|
162
162
|
A child tab in the caller's own workspace is closed. Close refuses a missing pane, the caller's own pane, a worktree the caller works in or whose workspace is the caller's, a worktree outside the repository's worktree directory, a dirty or detached worktree, and any other lock.
|
|
@@ -188,7 +188,7 @@ A reader claims records with `inbox claim`, which is a lease. A claimed record i
|
|
|
188
188
|
Readers:
|
|
189
189
|
|
|
190
190
|
- **Pi:** the extension watches its inbox file, injects each batch as one `dogsled` message (`steer` while the agent runs, a new turn when it is idle), and acknowledges it when Pi has written the message to the session.
|
|
191
|
-
- **Claude Code and Codex:** `dogsled hook inbox --harness claude|codex --event Stop|UserPromptSubmit`, registered by the plugin. The first hook run registers the reader. At `Stop`, records continue the turn as `{"decision":"block","reason":…}`; at `UserPromptSubmit`, they go beside the user's text as `additionalContext`. Records printed are acknowledged.
|
|
191
|
+
- **Claude Code and Codex:** `dogsled hook inbox --harness claude|codex --event Stop|UserPromptSubmit`, registered by the plugin. The first hook run registers the reader. At `Stop`, records continue the turn as `{"decision":"block","reason":…}`; at `UserPromptSubmit`, they go beside the user's text as `additionalContext`. Records printed are acknowledged. At `Stop`, the same response also includes a new reminder about finished children with work to land. It prints nothing when neither is pending.
|
|
192
192
|
|
|
193
193
|
A hook runs only when its harness acts, so an idle Claude Code or Codex agent sees a record at its next prompt or stop. Pi has no such delay.
|
|
194
194
|
|
|
@@ -204,7 +204,7 @@ A host that cannot be read shows `retry in 40s` on the board. `dogsled retry HOS
|
|
|
204
204
|
Run `dogsled serve` on every host. Dotfiles owns launchd and systemd user-service installation.
|
|
205
205
|
The measurement step uses no SSH. It reads CPU busy time, cores, memory, home disk space, Herdr agents, and Herdr panes.
|
|
206
206
|
When it has owned items or tickets, the service reads peers through the status cache and can launch admitted work through SSH.
|
|
207
|
-
Separate watchdog, admission,
|
|
207
|
+
Separate watchdog, admission, child-completion, inbox, and requested-cleanup workers keep slow actions independent from snapshot publication. Routine calls and lock waits have 30-second limits. Setup calls have a 10-minute limit.
|
|
208
208
|
An admission timeout records a failed ticket. The service continues publication and later actions.
|
|
209
209
|
It writes `~/.local/state/dogsled/host.json` through atomic replacement.
|
|
210
210
|
Each `probe.agent_panes` entry contains `pane`, `agent`, `state`, `name`, `transcript`, `transcript_modified`, `terminal_id`, `completion_seq`, `cwd`, the session's model and latest activity, and the worktree's branch, commits ahead, dirty state, and whether it landed.
|
|
@@ -294,18 +294,21 @@ dogsled prune targets
|
|
|
294
294
|
|
|
295
295
|
Doctor changes no candidates. It reports `worktrees`, `branches`, `workspaces`, `panes`, `jobs`, `targets`, `disk`, `processes`, and `repositories`.
|
|
296
296
|
Doctor lists linked worktrees on a branch and detached ones.
|
|
297
|
-
|
|
298
|
-
A
|
|
299
|
-
|
|
297
|
+
One loss rule in `src/loss.rs` governs worktrees, detached HEADs, branches, close, and board facts.
|
|
298
|
+
A checkout must have no tracked or untracked changes. Ignored files do not count. A ref without a checkout skips this condition.
|
|
299
|
+
Every commit must be held by the default branch or remote-tracking refs. Each cleanup pass runs `git fetch --all --prune` before remote refs count.
|
|
300
|
+
A failed fetch produces a repository warning. Only the default branch counts for that repository. Deleted upstream branches receive no special treatment.
|
|
300
301
|
A branch is integrated when it is deleted, when it is an ancestor of Git's remote default branch (local `main` is the fallback), or when commits on that branch apply it.
|
|
301
302
|
The branch's commits split into consecutive runs, and each run needs one commit that applies it. A commit applies a run when it changes every path the run changed, and merging the run into the commit's parent gives the commit's content on those paths.
|
|
302
303
|
One run is a merge, squash, or cherry-pick of the whole branch. Several runs are a stack: the branch holds an earlier branch's commits, and each branch landed as its own squash. Git alone decides this.
|
|
303
304
|
Each path merges on its own. An addition, deletion, or mode change on a path the base also changed differently is a conflict, and renames are not followed, so a doubtful run is not applied.
|
|
304
305
|
Results use the remote-tracking branch, so `git fetch` keeps them current.
|
|
305
306
|
A local branch with no worktree is removable when it is integrated or every commit is on a remote branch. Other branches are report-only. The default branch is never a candidate. Prune deletes a branch with `git branch -D` after it rechecks the planned commit.
|
|
306
|
-
|
|
307
|
-
An agent holds
|
|
308
|
-
|
|
307
|
+
Prune and the sweep share the same holds: active panes or processes, the caller, an unreadable process table, and pending preparations or launches.
|
|
308
|
+
An open agent item holds its checkout in prune and the sweep; `close` ends it and keeps the reply. Within `close`, a live owner holds the checkout until the default branch holds its work.
|
|
309
|
+
An idle shell, an idle or done agent without an open item, and a workspace alone are not holds. The processes of an idle agent pane are not holds, but a background job that a shell started is. Foreign locks still prevent removal.
|
|
310
|
+
A dirty checkout whose commits are in the default branch gets a `landed_dirty` finding when no hold applies.
|
|
311
|
+
The finding includes the repository, checkout, branch or HEAD, dirty-path count, and up to ten paths. It is never removable.
|
|
309
312
|
A registered worktree whose directory is gone is a stale registration. Prune drops the record only.
|
|
310
313
|
A shell with no foreground command in an ended job's tab is idle at once: the job log keeps its output. So is a shell whose directory no longer exists.
|
|
311
314
|
Other panes need more than 24 hours continuously idle or done, based on service observations and a fresh snapshot.
|
|
@@ -318,30 +321,39 @@ Prune removes it after it checks the tag again. A worktree that the same plan re
|
|
|
318
321
|
Processes above 100% CPU outside Herdr pane ancestry are report-only. Prune never kills processes or frees arbitrary disk paths.
|
|
319
322
|
|
|
320
323
|
Prune displays a plan and asks before removal. `--yes` skips that question, not safety checks.
|
|
321
|
-
Each removal rechecks current state. Working or blocked panes, held worktrees, dirty worktrees, and
|
|
324
|
+
Each removal rechecks current state. Working or blocked panes, held worktrees, dirty worktrees, and local-only commits remain untouched.
|
|
322
325
|
Removal rechecks Git path, branch, HEAD, and repository against the approved plan. Any identity change stops removal.
|
|
323
|
-
Worktree removal is the same as `close`: `teardown`, the panes and workspace, unlock, `git worktree remove`, the branch when
|
|
326
|
+
Worktree removal is the same as `close`: `teardown`, the panes and workspace, unlock, `git worktree remove`, the branch when its commits are held, and the idle anchor. Dogsled checks that the worktree is gone afterwards.
|
|
324
327
|
Each entry is independent. A failed or refused entry does not stop the others; prune reports each failure, then exits with status 2.
|
|
325
328
|
`--json` returns `{results,failures}`. A result names the removed path and the closed panes. A failed `teardown` command's error includes its last lines of output.
|
|
326
329
|
|
|
327
330
|
Placement records repositories in `repositories.json`, independently of `worktrees.json`. Repository registrations survive the last worktree removal.
|
|
328
331
|
Doctor also discovers repositories from the caller, live pane directories, Herdr workspaces, and `cleanup.repositories`. It never scans the whole home directory.
|
|
329
|
-
A
|
|
332
|
+
A missing or unreadable repository produces a report-only `repositories` warning. The warning names its registration or configuration, and the survey continues.
|
|
333
|
+
Repository registrations stay unchanged.
|
|
330
334
|
### The sweep in `serve`
|
|
331
335
|
|
|
332
336
|
The sweep closes finished children (see [Items and closing](#items-and-closing)), and a job closes its own tab. That closed loop ends most work.
|
|
333
337
|
The sweep catches what a broken loop leaves behind: a crashed owner, a killed process, or a worktree that something else made.
|
|
334
|
-
|
|
338
|
+
The Stop gate writes `sweep-request.json` and wakes the local service through `sweep.sock`. It never runs the survey inside the hook.
|
|
339
|
+
The service checks the durable request at startup, on notification, and on each publication iteration. No request means no survey.
|
|
340
|
+
The socket is optional. A bind failure produces a warning, and pending requests still reach the worker on the publication interval.
|
|
341
|
+
A Stop request failure produces a warning without preventing existing gate or inbox messages from reaching the agent.
|
|
342
|
+
Each requested pass builds the doctor plan in sweep scope. It applies only entries whose removal loses nothing:
|
|
335
343
|
|
|
336
344
|
- `panes` and `workspaces`: a shell at an empty prompt in an ended job's tab, or in a directory that no longer exists. A workspace closes whole when every pane in it is one of these.
|
|
337
|
-
- `worktrees
|
|
338
|
-
-
|
|
339
|
-
- Orphaned children: an open agent item whose owner pane no longer exists, whose child is `idle` or `done`, whose worktree is clean, and whose every commit is on a remote ref. The sweep closes it as `dogsled close` does: `teardown`, the workspace, the worktree, and the item. A remote child closes over SSH with `host close --pushed`.
|
|
345
|
+
- `worktrees` and `branches`: the shared loss rule and holds described above, identical to prune.
|
|
346
|
+
- Orphaned children: an idle or done child whose owner is gone and whose checkout passes the shared loss rule. Close runs teardown and removes its workspace, checkout, and item.
|
|
340
347
|
|
|
341
348
|
The sweep runs the same checks as `close` and `prune`. It never closes the service's own pane or workspace, or a worktree that the service works in. It skips a worktree it cannot check, and a failure on one entry does not stop the others.
|
|
342
|
-
|
|
349
|
+
The service keeps seven days of completed passes in `sweeps.jsonl` and the latest pass in `sweep.json`.
|
|
350
|
+
Each history entry has a `ts_ms` timestamp; each new pass removes entries older than seven days.
|
|
351
|
+
The report includes start and finish times, removals, failures, repository warnings, and landed-dirty findings.
|
|
352
|
+
`dogsled status` and `status --json` show the latest pass. Prompt context adds one summary line when that pass has errors or warnings.
|
|
353
|
+
The service also writes removals, failures, and repository warnings to stderr.
|
|
354
|
+
Child completion and orphan checks remain independent of requested doctor cleanup.
|
|
343
355
|
An orphan that is dirty, unpushed, `working`, `blocked`, or on an unreachable host stays open. Status shows `orphaned:` and the reason on its item line, the brief counts it, and every check-in on the host lists it.
|
|
344
|
-
|
|
356
|
+
Prune also handles idle panes after a day, ended jobs, stale registrations, and `targets`. Disk, process, and repository findings are report-only.
|
|
345
357
|
|
|
346
358
|
## Placement
|
|
347
359
|
|
|
@@ -446,12 +458,34 @@ The removed lead ledger and event log are not read or migrated.
|
|
|
446
458
|
|
|
447
459
|
## Prompt context and integrations
|
|
448
460
|
|
|
449
|
-
`dogsled hook context --harness claude|codex|pi`
|
|
461
|
+
`dogsled hook context --harness claude|codex|pi` leads with your own children and jobs, then shows the fleet totals from `status --brief`.
|
|
462
|
+
The owner summary separates work to land, attention, idle, done, running, and queued work.
|
|
463
|
+
The `To land:` line names finished children with unmerged commits or uncommitted changes.
|
|
450
464
|
Claude Code and Codex register `SessionStart` and `UserPromptSubmit` context hooks, and `UserPromptSubmit` and `Stop` inbox hooks (see [Inbox](#inbox)), in `plugins/dogsled`.
|
|
451
465
|
Each registration supplies `--event`. The default context event is `UserPromptSubmit`.
|
|
452
466
|
The CLI never reads stdin. Claude Code and Codex receive `hookSpecificOutput` JSON with `hookEventName` and `additionalContext`.
|
|
453
467
|
Pi receives plain text through its `before_agent_start` extension. All three use the same snapshot reader and formatter.
|
|
454
468
|
|
|
469
|
+
Before an orchestrator stops, the stop gate checks its own TO LAND children.
|
|
470
|
+
Pi calls `dogsled hook gate` from `agent_before_settle` and adds a visible message that continues the run.
|
|
471
|
+
Claude Code and Codex include the reminder in their existing `Stop` inbox response.
|
|
472
|
+
The reminder requests a review, a commit from dirty children, integration, or an explicit blocker or deferral.
|
|
473
|
+
It never commits, merges, closes, or discards work itself. Clean readers and working children do not trigger it.
|
|
474
|
+
|
|
475
|
+
The gate reports failures, repository warnings, and landed-dirty findings from the latest completed pass, without waiting for the requested pass.
|
|
476
|
+
Successful removals remain in status and the journal; they do not continue an agent turn.
|
|
477
|
+
Repository warnings name the repository and state entries, and request delegation to a dogsled agent instead of manual state edits.
|
|
478
|
+
Landed-dirty findings reach only panes in the same main repository, including linked worktrees and symlink paths.
|
|
479
|
+
The message asks the owner to read and preserve wanted changes, or obtain user approval before discarding abandoned work.
|
|
480
|
+
It forbids forced worktree removal, `git clean`, and `rm -rf` on the checkout.
|
|
481
|
+
Cleanup reminders use separate, once-per-set histories in `gate.json`, so an unchanged warning cannot cause a continuation loop.
|
|
482
|
+
|
|
483
|
+
The gate records each set of child item IDs, commit counts, and dirty states in `gate.json`.
|
|
484
|
+
It continues once per distinct set, across process restarts. Row order and idle/done transitions do not cause another reminder.
|
|
485
|
+
New states can trigger another reminder. Closing all owner items clears the history.
|
|
486
|
+
Pi skips the gate on errors, aborts, and boundaries that already request continuation.
|
|
487
|
+
This is a bounded reminder, not a permanent prohibition on stopping.
|
|
488
|
+
|
|
455
489
|
Without `HERDR_ENV`, service snapshots, or a usable binary, the hooks add nothing.
|
|
456
490
|
A hook has a five-second command budget. Hook failures exit successfully without output.
|
|
457
491
|
Plugin commands end with `|| true`. Pi also leaves the prompt unchanged if the binary fails.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oisin-ee/pi-dogsled",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Pi extension for dogsled: the board, the /dogsled panel, the inbox reader, and the dogsled tools. Needs the dogsled binary of the same version.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/pi/index.ts
CHANGED
|
@@ -240,12 +240,23 @@ export default function dogsled(pi: ExtensionAPI) {
|
|
|
240
240
|
// A delivered message is in the transcript once Pi ends it: only then is it acknowledged.
|
|
241
241
|
pi.on("message_end", async event => { await inbox.ended(event.message); });
|
|
242
242
|
pi.on("agent_settled", () => { inbox.settled(); });
|
|
243
|
+
pi.on("agent_before_settle", async event => {
|
|
244
|
+
if (!owner || event.outcome !== "completed" || !event.context.canContinue || event.continue) return;
|
|
245
|
+
// The Rust gate owns selection and durable deduplication across all harnesses.
|
|
246
|
+
const result = await pi.exec(binary, ["hook", "gate"], { timeout: 6_000 });
|
|
247
|
+
if (result.code !== 0) throw new Error(`dogsled stop gate failed: ${result.stderr.trim() || `exit ${result.code}`}`);
|
|
248
|
+
if (!result.stdout.trim()) return;
|
|
249
|
+
return {
|
|
250
|
+
entries: [...event.entries, { type: "custom_message" as const, customType: MESSAGE_TYPE, content: result.stdout.trim(), display: true }],
|
|
251
|
+
continue: true,
|
|
252
|
+
};
|
|
253
|
+
});
|
|
243
254
|
pi.on("before_agent_start", async (event) => {
|
|
244
255
|
// Context is optional, including when the binary is absent or fails.
|
|
245
256
|
try {
|
|
246
257
|
const result = await pi.exec(binary, ["hook", "context", "--harness", "pi"], { timeout: 6_000 });
|
|
247
258
|
if (result.code !== 0 || !result.stdout.trim()) return;
|
|
248
|
-
return { systemPrompt: `${event.systemPrompt}\n\n##
|
|
259
|
+
return { systemPrompt: `${event.systemPrompt}\n\n## Your work and fleet status\n${result.stdout.trim()}\n` };
|
|
249
260
|
} catch {
|
|
250
261
|
return;
|
|
251
262
|
}
|
|
@@ -18,7 +18,10 @@ dogsled read NAME_OR_PANE [--host H]
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
The board groups what you started by what you must do: TO LAND (a finished child with commits), ATTENTION (blocked, stalled, failed), RUNNING, and WAITING (queued for a host). Each agent row shows its harness and model; each job row its command.
|
|
21
|
-
Your prompt context
|
|
21
|
+
Your prompt context leads with your own children and jobs, then a `To land:` line, then fleet status.
|
|
22
|
+
Before you stop, a gate reminds you once per distinct set of finished children with unmerged commits or uncommitted changes.
|
|
23
|
+
Read each result. Ask dirty children to finish and commit. Integrate approved work, or report its blocker or deferral.
|
|
24
|
+
Never discard dirty work to satisfy the reminder. The gate does not close or merge anything. In Pi, the board widget above the editor shows host meters, the current task, and grouped agents and jobs. Alt+W or `/work` expands it in place. Work inspection stays inline, not in a popup. Todos follow the Pi branch; agents and jobs follow the owner pane. `/dogsled` retains host, profile, and inbox administration.
|
|
22
25
|
|
|
23
26
|
## Delegate
|
|
24
27
|
|
|
@@ -60,7 +63,8 @@ Blocked and working children never close, and nothing closes on a timer. To end
|
|
|
60
63
|
dogsled close PANE [--host HOST]
|
|
61
64
|
```
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
Close, prune, and the sweep use one loss rule: a clean checkout and every commit held by the default branch or freshly fetched remote refs. Ignored files do not count. A failed fetch disables remote-ref evidence for that repository.
|
|
67
|
+
Close runs `teardown`, closes the panes and workspace, removes the checkout, and ends the item. The same loss rule governs branch deletion. A child in an unlocked checkout loses only its pane.
|
|
64
68
|
It refuses, before it changes anything, a busy agent, a dirty or detached worktree, a foreign lock, your own pane, and a worktree you work in. Report a refusal; do not remove a worktree by hand.
|
|
65
69
|
|
|
66
70
|
## Run a job
|
|
@@ -97,8 +101,10 @@ dogsled doctor --host HOST --json
|
|
|
97
101
|
dogsled prune --host HOST worktrees jobs
|
|
98
102
|
```
|
|
99
103
|
|
|
100
|
-
Doctor
|
|
101
|
-
|
|
104
|
+
Doctor removes nothing, but refreshes remote refs. Prune shows candidates and asks. `--yes` skips only the question. Never close working or blocked agents through cleanup.
|
|
105
|
+
Worktrees, detached HEADs, branches, and orphan close use the loss rule in Closing. Prune and the sweep use identical worktree and branch decisions.
|
|
106
|
+
Holds protect active panes or processes, the caller, open agent items (only `close` ends them; a live owner also holds until landing), and pending preparations or launches. An unreadable process table also holds worktrees. An idle shell, an idle agent's own processes, or a workspace alone does not.
|
|
107
|
+
Doctor reports unheld dirty checkouts whose work is in the default branch as `landed_dirty`, with up to ten dirty paths. Missing repositories produce warnings, and the survey continues.
|
|
102
108
|
|
|
103
109
|
## Hosts
|
|
104
110
|
|