pi-herdr-agents 3.0.2 → 3.1.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/AGENTS.md +2 -0
- package/CHANGELOG.md +42 -1
- package/CONTEXT.md +25 -4
- package/README.md +126 -13
- package/RELEASING.md +2 -0
- package/docs/README.md +2 -1
- package/docs/adr/0011-explicit-worktree-cleanup.md +1 -1
- package/docs/adr/0015-report-opened-primary-workspace.md +121 -0
- package/docs/worktree-subagents.md +5 -3
- package/maestro/adapters/pi/activity-file.ts +11 -1
- package/maestro/adapters/pi/launch.ts +29 -15
- package/maestro/adapters/pi/process-identity.ts +38 -0
- package/maestro/core/activity.ts +1 -1
- package/maestro/core/config/model-config.ts +83 -10
- package/maestro/core/config/task-model-init.ts +208 -13
- package/maestro/core/config/task-model-types.ts +19 -1
- package/maestro/core/config/type-guards.ts +5 -0
- package/maestro/core/opened-primary-workspace.ts +108 -0
- package/maestro/core/supervision.ts +38 -13
- package/maestro/core/surface-provider.ts +22 -0
- package/maestro/core/wake.ts +37 -0
- package/maestro/core/worktree-cleanup.ts +52 -1
- package/maestro/core/worktree.ts +2 -0
- package/maestro/runtime/index.ts +2 -1
- package/maestro/runtime/task-model-init.ts +106 -9
- package/maestro/runtime/worktree-operations.ts +2 -0
- package/maestro/surfaces/fake/fake-surface-provider.ts +8 -0
- package/maestro/surfaces/herdr/herdr-surface-provider.ts +12 -1
- package/maestro/surfaces/herdr/herdr.ts +345 -49
- package/maestro/surfaces/herdr/terminal.ts +0 -10
- package/package.json +1 -1
- package/pi-extension/subagents/index.ts +170 -16
- package/skills/pi-herdr-agents/SKILL.md +16 -4
package/AGENTS.md
CHANGED
|
@@ -96,6 +96,8 @@ npm pack --dry-run
|
|
|
96
96
|
git diff --check
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
`npm run lint` needs Node.js 22.18+ (or 22.6–22.17 with `NODE_OPTIONS=--experimental-strip-types`) because oxlint imports `oxlint.config.ts` and its `tools/oxlint/anti-slop/index.ts` plugin as TypeScript.
|
|
100
|
+
|
|
99
101
|
Run LSP diagnostics on every changed TypeScript file; lint and tests do not catch every TypeScript error.
|
|
100
102
|
|
|
101
103
|
For Herdr or lifecycle changes, run the deterministic suite from inside Herdr. Run only one integration suite at a time on a Herdr instance; concurrent suites compete for terminal focus and process capacity and can cause false timeouts or leaked test resources.
|
package/CHANGELOG.md
CHANGED
|
@@ -7,12 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
9
9
|
|
|
10
|
-
## [v3.0
|
|
10
|
+
## [v3.1.0](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.3...v3.1.0)
|
|
11
|
+
|
|
12
|
+
### Commits
|
|
13
|
+
|
|
14
|
+
- feat: route task-model init through pack approval [`1b6bacd`](https://github.com/giuseppecrj/pi-herdr-agents/commit/1b6bacd0b5eecb6f83ffba1d5a5ee57a8b0fa944)
|
|
15
|
+
- test: dispatch all Pi lifecycle handlers in placement fixtures [`f96e59d`](https://github.com/giuseppecrj/pi-herdr-agents/commit/f96e59de8890d39ce6c79be8de245d287fc2485e)
|
|
16
|
+
|
|
17
|
+
## [v3.0.3](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.2...v3.0.3) - 2026-10-08
|
|
18
|
+
|
|
19
|
+
### Merged
|
|
20
|
+
|
|
21
|
+
- Release 3.0.3 [`#82`](https://github.com/giuseppecrj/pi-herdr-agents/pull/82)
|
|
22
|
+
- test(integration): re-read worktree row before asserting its open workspace [`#81`](https://github.com/giuseppecrj/pi-herdr-agents/pull/81)
|
|
23
|
+
- Fix Pi readiness check when Pi rewrites argv [`#80`](https://github.com/giuseppecrj/pi-herdr-agents/pull/80)
|
|
24
|
+
- Fix tsc errors and git-version-sensitive index test; document lint Node floor [`#79`](https://github.com/giuseppecrj/pi-herdr-agents/pull/79)
|
|
25
|
+
- fix(worktrees): report the primary workspace creation opened [`#78`](https://github.com/giuseppecrj/pi-herdr-agents/pull/78)
|
|
26
|
+
- fix: keep parked supervision wakes alive until they settle [`#77`](https://github.com/giuseppecrj/pi-herdr-agents/pull/77)
|
|
27
|
+
|
|
28
|
+
### Commits
|
|
29
|
+
|
|
30
|
+
- remove openspec [`5e31900`](https://github.com/giuseppecrj/pi-herdr-agents/commit/5e31900ea96fdaab6fcf26fc9a335497dcbc528d)
|
|
31
|
+
- fix(worktrees): bound the primary workspace report and hedge its note [`44a18a8`](https://github.com/giuseppecrj/pi-herdr-agents/commit/44a18a8145549801bd17007f05beb8a8cddf41b9)
|
|
32
|
+
- Confirm Pi startup by launch environment, not rewritten argv [`bbfe7e7`](https://github.com/giuseppecrj/pi-herdr-agents/commit/bbfe7e7093bd76d8803562ddb6263c27cfe4a982)
|
|
33
|
+
- fix: settle stranded supervision waits and test wake retain accounting [`d18df1c`](https://github.com/giuseppecrj/pi-herdr-agents/commit/d18df1cb83bedf86951d2f9702a4a6185b44a8e8)
|
|
34
|
+
- test: bound the identity-capture loop anchor and close supervision test gaps [`4a0e71a`](https://github.com/giuseppecrj/pi-herdr-agents/commit/4a0e71a97ebc9ba3f21a5377504f24c3d837d9a2)
|
|
35
|
+
- Treat an unreadable Pi environment as unproven, not fatal [`b69151a`](https://github.com/giuseppecrj/pi-herdr-agents/commit/b69151ae85bd13c45bdf3e980c0b36e315c7c863)
|
|
36
|
+
- chore: release v3.0.3 [`1c75adb`](https://github.com/giuseppecrj/pi-herdr-agents/commit/1c75adb0864af9b922d1471734b086943435f238)
|
|
37
|
+
- test(worktree-cleanup): make the index-restore test hermetic and deterministic [`16840f6`](https://github.com/giuseppecrj/pi-herdr-agents/commit/16840f64b9481983cf44fc928061fd4e6822434d)
|
|
38
|
+
- test(worktree-cleanup): clear inherited git repository variables in the fixture [`323e1fb`](https://github.com/giuseppecrj/pi-herdr-agents/commit/323e1fbf6e6240eb5e7000d63fc84e23b391259a)
|
|
39
|
+
- test(worktrees): expect launch snapshots in scoped inventory [`f733040`](https://github.com/giuseppecrj/pi-herdr-agents/commit/f73304088c8a781b1ed7333a5a09175ec7c896e9)
|
|
40
|
+
- fix(types): resolve strict tsc errors in activity scope guard and recorder [`26bc9ef`](https://github.com/giuseppecrj/pi-herdr-agents/commit/26bc9ef2e8a2afc23901641efdd19661fcd3f756)
|
|
41
|
+
- docs: limit the lint type-stripping fallback to Node 22.6-22.17 [`ccab597`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ccab597cfaf2bcf6e37889ae3103dd6e9c78c6ef)
|
|
42
|
+
- docs: note that npm run lint needs Node.js 22.18 or newer [`b8ffa8d`](https://github.com/giuseppecrj/pi-herdr-agents/commit/b8ffa8d1d83419588db8d4aed506d5d236360673)
|
|
43
|
+
- test(integration): check cancel manifest workspace id; clarify Herdr Node note [`aac18d9`](https://github.com/giuseppecrj/pi-herdr-agents/commit/aac18d926385582ccc36783fd8a9475db7df7e3d)
|
|
44
|
+
- test(worktree-cleanup): snapshot the index after git status [`aa869b2`](https://github.com/giuseppecrj/pi-herdr-agents/commit/aa869b2a62c8868d377b134d3f05b029692f2eb0)
|
|
45
|
+
- docs: integration tests need the Herdr server on Node.js 22.19+ [`bdd791b`](https://github.com/giuseppecrj/pi-herdr-agents/commit/bdd791bd934ea3174db32e1fcf1353353506224b)
|
|
46
|
+
|
|
47
|
+
## [v3.0.2](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.1...v3.0.2) - 2026-10-06
|
|
11
48
|
|
|
12
49
|
### Merged
|
|
13
50
|
|
|
14
51
|
- fix(subagents): resolve submodule sources and refresh fallback context [`#76`](https://github.com/giuseppecrj/pi-herdr-agents/pull/76)
|
|
15
52
|
|
|
53
|
+
### Commits
|
|
54
|
+
|
|
55
|
+
- chore: release v3.0.2 [`245e54e`](https://github.com/giuseppecrj/pi-herdr-agents/commit/245e54e7ccc937f69bee6093906d26b03b8a92c9)
|
|
56
|
+
|
|
16
57
|
## [v3.0.1](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.0...v3.0.1) - 2026-10-06
|
|
17
58
|
|
|
18
59
|
### Merged
|
package/CONTEXT.md
CHANGED
|
@@ -71,6 +71,13 @@ verified by the parent against the Herdr pane. A worktree cancel signals and
|
|
|
71
71
|
judges exit only by it.
|
|
72
72
|
_Avoid_: argv match, foreground process, shell visibility
|
|
73
73
|
|
|
74
|
+
**Pi startup confirmation**:
|
|
75
|
+
The bounded check that a `/worktree` handoff's Pi is running before its
|
|
76
|
+
workspace is focused: a Pi process in the root pane with the worktree cwd whose
|
|
77
|
+
launch-time environment names the launched session in
|
|
78
|
+
`PI_HERDR_AGENTS_SESSION`. Nothing inside Pi reads that marker.
|
|
79
|
+
_Avoid_: argv match, `--session` visibility, child-context hint
|
|
80
|
+
|
|
74
81
|
**No-progress advisory**:
|
|
75
82
|
An internal warning that an active child shows no durable progress in its session
|
|
76
83
|
JSONL or activity snapshot. It is advisory only and never changes the child's
|
|
@@ -182,9 +189,13 @@ global enumeration and unsupported platforms remain blockers.
|
|
|
182
189
|
_Avoid_: Proven unrelated, machine-wide inactivity, bypass permission
|
|
183
190
|
|
|
184
191
|
**Explicit worktree removal**:
|
|
185
|
-
A parent-requested removal of one named managed checkout and its open
|
|
186
|
-
with absence verification and retained branch history. Never automatic
|
|
187
|
-
|
|
192
|
+
A parent-requested removal of one named managed checkout and its open worktree
|
|
193
|
+
workspace, with absence verification and retained branch history. Never automatic
|
|
194
|
+
reaping. It never closes the source repository's primary workspace. When this
|
|
195
|
+
process recorded that worktree creation appeared to open that workspace and a
|
|
196
|
+
read taken just before the report still shows it untouched, removal suggests
|
|
197
|
+
the `herdr workspace close` command, to use only if the workspace is unused.
|
|
198
|
+
_Avoid_: Branch deletion, completion cleanup, automatic close of a workspace the user may have used
|
|
188
199
|
|
|
189
200
|
**Dirty-state preservation**:
|
|
190
201
|
Explicit opt-in staging and WIP commitment of a worktree's uncommitted and
|
|
@@ -199,7 +210,9 @@ architecture to planning and diagnosis, coding to workers, review to reviewers,
|
|
|
199
210
|
QA to software and test runners, and docs to documentation workers. Categories
|
|
200
211
|
describe work, not complexity. `/subagents-init [preferences]` drafts them from
|
|
201
212
|
the active extension-loaded registry's synchronous snapshot and existing saved
|
|
202
|
-
choices, with source-based research when available. A
|
|
213
|
+
choices, with source-based research when available. A single loaded extension
|
|
214
|
+
that offers on the task-model init approval event owns approval and the write
|
|
215
|
+
instead of the host writer. A dynamic provider awaiting
|
|
203
216
|
its initial catalog refresh might be absent. `task:<category>` is a subagent
|
|
204
217
|
model selector, not a command or parent model change. Ordered authenticated
|
|
205
218
|
candidate plans resolve before launch; ordinary nonpersistent runs can retry
|
|
@@ -216,6 +229,14 @@ the independence boundary; project policy may separately require a
|
|
|
216
229
|
different provider.
|
|
217
230
|
_Avoid_: Generic tier, reviewer-family enforcement, per-step routing
|
|
218
231
|
|
|
232
|
+
**Ranking basis**:
|
|
233
|
+
What a task-model proposal's ranking rests on, as the proposing model submitted
|
|
234
|
+
it: `registry-only`, or `research` with the http(s) sources consulted in that
|
|
235
|
+
run, how each influenced the ranking, and the remaining uncertainty. It travels
|
|
236
|
+
with one write for review and is never saved; `tasksMeta.method` keeps its kind.
|
|
237
|
+
Code checks its shape, not that a source was read.
|
|
238
|
+
_Avoid_: Verified research, evidence store
|
|
239
|
+
|
|
219
240
|
**Loop template**:
|
|
220
241
|
A future reusable orchestration definition beside `models`, describing stages,
|
|
221
242
|
task categories, and a termination/report contract. Loop templates are not
|
package/README.md
CHANGED
|
@@ -192,7 +192,7 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
|
|
|
192
192
|
| `worktree_list` | Parent-only inspect-only inventory of managed worktrees and cleanup blockers |
|
|
193
193
|
| `worktree_remove` | Parent-only explicit removal by `target` path, branch, or workspace ID; optional `preserve: true` commits dirty state first |
|
|
194
194
|
| `subagent_resume` | Resume a previous Pi-backed sub-agent session in a new ordinary pane (async) |
|
|
195
|
-
| `subagents_write_task_models` | Parent-only internal tool that validates and atomically writes `models.tasks` preferences, optionally conditional on `expectedConfigRevision` |
|
|
195
|
+
| `subagents_write_task_models` | Parent-only internal tool that validates and atomically writes `models.tasks` preferences, optionally conditional on `expectedConfigRevision` and carrying an unsaved ranking `basis` |
|
|
196
196
|
|
|
197
197
|
| Skill | Description |
|
|
198
198
|
| ----- | ----------- |
|
|
@@ -207,7 +207,7 @@ Subagent tabs, panes, and worktree workspaces are created without stealing keybo
|
|
|
207
207
|
| -------------------------- | ------------------------------------ |
|
|
208
208
|
| `/worktree <name> [task]` | Continue this session in a new managed worktree (`/worktree list` lists them) |
|
|
209
209
|
| `/subagent <agent> <task>` | Spawn a named agent directly (`/subagent list` lists available agents) |
|
|
210
|
-
| `/subagents-init [preferences]` | Draft task-category model preferences from the live authenticated registry, with optional ranking preferences |
|
|
210
|
+
| `/subagents-init [preferences]` | Draft task-category model preferences from the live authenticated registry, with optional ranking preferences; a loaded approval extension can own the write |
|
|
211
211
|
|
|
212
212
|
### Taxonomy and discovery
|
|
213
213
|
|
|
@@ -461,7 +461,10 @@ JSON without truncating models and reports its model count and JSON character
|
|
|
461
461
|
count (not a token estimate); large catalogs still consume context. This is the
|
|
462
462
|
current synchronous snapshot: a dynamic provider whose initial catalog refresh
|
|
463
463
|
has not completed might be absent. Init does not refresh providers or probe the
|
|
464
|
-
network for availability.
|
|
464
|
+
network for availability. The brief also includes `configRevision`, the
|
|
465
|
+
revision of the exact config bytes that supplied the saved preferences. Init
|
|
466
|
+
does not start while a turn runs or messages are queued, and an empty registry
|
|
467
|
+
ends with a notice instead of a prompt. Neither case writes anything.
|
|
465
468
|
|
|
466
469
|
The draft considers current saved task, default, and per-agent preferences.
|
|
467
470
|
Optional command arguments set ranking preferences. Otherwise it favors
|
|
@@ -482,8 +485,23 @@ sources, disclose uncertainty and notable exclusions, and avoid duplicate
|
|
|
482
485
|
upstream models across routes unless deliberate redundancy is explained. Display
|
|
483
486
|
names help identify candidates but, like aliases, do not prove upstream
|
|
484
487
|
equivalence; research is still required. Price or context size alone is not
|
|
485
|
-
quality evidence.
|
|
486
|
-
|
|
488
|
+
quality evidence. No live model probes run.
|
|
489
|
+
|
|
490
|
+
The proposal carries a ranking `basis`. It is `{"kind":"registry-only"}` when
|
|
491
|
+
search is unavailable or yields no usable evidence. It is `{"kind":"research"}`
|
|
492
|
+
only with the http(s) `sources` consulted in this run, the `influence` each had
|
|
493
|
+
on the ranking, and the remaining `uncertainty`. Code checks the basis's shape,
|
|
494
|
+
not whether a source was read, so the basis is labeled as submitted and
|
|
495
|
+
unverified.
|
|
496
|
+
|
|
497
|
+
While init runs, it emits `pi-herdr-subagents:task-models:init:approval:v1`.
|
|
498
|
+
With no offer, the prompt directs the model to `subagents_write_task_models`
|
|
499
|
+
with `expectedConfigRevision`. When exactly one loaded extension offers, init
|
|
500
|
+
opens that extension's flow, and the extension's instructions replace the
|
|
501
|
+
writer instruction; the extension then owns approval and the write. Two or more
|
|
502
|
+
offers, or an invalid one, stop init before any extension opens or a prompt is
|
|
503
|
+
sent. Once an offer is recorded, a refusal or failure also stops init: it never
|
|
504
|
+
falls back to the direct writer. See [Task-model init events](#task-model-init-events).
|
|
487
505
|
|
|
488
506
|
The writer validates and atomically replaces `models.tasks` and `tasksMeta`,
|
|
489
507
|
preserving unrelated settings. Its tool schema accepts partial nonempty
|
|
@@ -491,9 +509,15 @@ categories (omitted categories are removed), rejects empty `tasks: {}` input,
|
|
|
491
509
|
and rejects exact duplicate refs within a category after trimming;
|
|
492
510
|
IDs remain case-sensitive. Its result includes normalized saved `tasks`,
|
|
493
511
|
`tasksMeta`, `configPath`, `missingCategories`, and `configRevision`. Init requests all six categories
|
|
494
|
-
and a before/after table based on that saved result, not the unsaved draft
|
|
495
|
-
must explain missing categories or changed choices
|
|
496
|
-
|
|
512
|
+
and a before/after table based on that saved result, not the unsaved draft, and
|
|
513
|
+
must explain missing categories or changed choices.
|
|
514
|
+
|
|
515
|
+
Optional `basis` must have the same kind as `tasksMeta.method`. A research basis
|
|
516
|
+
needs at least one source with an http(s) URL that has a host and a nonblank
|
|
517
|
+
`influence`, plus a nonblank `uncertainty`. The writer validates it before
|
|
518
|
+
writing, returns it in the result, and never saves it; only `tasksMeta.method`
|
|
519
|
+
persists. Calls without `basis`, including research calls, keep the earlier
|
|
520
|
+
contract.
|
|
497
521
|
|
|
498
522
|
Optional `expectedConfigRevision` makes a write conditional on the config the
|
|
499
523
|
proposal was read from. A revision is `sha256:` followed by 64 lowercase hex
|
|
@@ -549,13 +573,94 @@ rewrites the file. Other values remain configuration errors, as do unknown keys
|
|
|
549
573
|
under `roles`. Registered role packs are the entire package layer, and global and
|
|
550
574
|
project definitions keep their precedence over them.
|
|
551
575
|
|
|
576
|
+
### Task-model init events
|
|
577
|
+
|
|
578
|
+
Two versioned `pi.events` channels let another extension take part in
|
|
579
|
+
`/subagents-init` without importing this package. Both carry the invoking
|
|
580
|
+
command's live `ExtensionCommandContext`, so they are for trusted extensions in
|
|
581
|
+
the same process. The context is current only while the event is emitted.
|
|
582
|
+
|
|
583
|
+
The host emits `pi-herdr-subagents:task-models:init:approval:v1` while init
|
|
584
|
+
runs:
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
type ApprovalRequest = {
|
|
588
|
+
apiVersion: 1;
|
|
589
|
+
brief: TaskModelBrief; // frozen: operatorPreferences, categories, configRevision, current, models (never empty)
|
|
590
|
+
context: ExtensionCommandContext;
|
|
591
|
+
offer(offer: { owner: string; open(): OpenResult }): "recorded" | "closed";
|
|
592
|
+
};
|
|
593
|
+
type OpenResult =
|
|
594
|
+
| {
|
|
595
|
+
kind: "ready";
|
|
596
|
+
destination: { toolName: string; instructions: string };
|
|
597
|
+
cancel(): void; // closes the flow this open created
|
|
598
|
+
}
|
|
599
|
+
| { kind: "blocked"; reason: string };
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
- Call `offer` synchronously, before the listener's first `await`. After `emit`
|
|
603
|
+
returns, `offer` answers `"closed"` and records nothing.
|
|
604
|
+
- Offer whenever your extension owns task-model approval, even when it is busy,
|
|
605
|
+
and refuse from `open`. `owner` is one printable token without spaces.
|
|
606
|
+
- The host opens nothing until collection ends. Two offers (even with the same
|
|
607
|
+
owner) or an invalid offer stop init. With one offer, the host calls `open()`
|
|
608
|
+
once, and it must return synchronously.
|
|
609
|
+
- `ready` must leave `toolName` active. Its `instructions` replace the host's
|
|
610
|
+
writer instruction in the prompt the host sends.
|
|
611
|
+
- `ready` must include `cancel`. When the host does not hand the prompt to Pi
|
|
612
|
+
(the tool is inactive, or `pi.sendUserMessage` throws), it calls `cancel()`
|
|
613
|
+
once before reporting `not-started`. `cancel` must close only the flow that
|
|
614
|
+
this `open` created, never a later one. A `cancel` that throws is reported
|
|
615
|
+
with the outcome.
|
|
616
|
+
- A throw, a promise, an invalid result (including a missing `cancel`), or
|
|
617
|
+
`blocked` stops init with a notice and no prompt. The host cannot undo what
|
|
618
|
+
`open` changed before failing.
|
|
619
|
+
- Pi accepts the prompt asynchronously. It can still reject it after `start`
|
|
620
|
+
reports `started`, for example when the selected model has no configured
|
|
621
|
+
auth; the host refuses up front only when no model is selected. Pi delivers
|
|
622
|
+
the prompt to `input` handlers (source `"extension"`) before that check and
|
|
623
|
+
emits `before_agent_start` and `agent_start` only after it passes. An
|
|
624
|
+
extension whose flow outlives one prompt should close it when other input or
|
|
625
|
+
a run arrives before those events, so a later request cannot inherit it.
|
|
626
|
+
- Pi's event bus logs and swallows listener exceptions. A listener that throws
|
|
627
|
+
before offering looks the same as no listener, so init uses the direct
|
|
628
|
+
writer. An extension that guards the writer still refuses that write.
|
|
629
|
+
|
|
630
|
+
Another extension's command emits `pi-herdr-subagents:task-models:init:start:v1`
|
|
631
|
+
to start the same init:
|
|
632
|
+
|
|
633
|
+
```typescript
|
|
634
|
+
type StartRequest = {
|
|
635
|
+
apiVersion: 1;
|
|
636
|
+
context: ExtensionCommandContext;
|
|
637
|
+
preferences: string; // ranking preferences, passed through as typed
|
|
638
|
+
offer(offer: { owner: string; start(): InitOutcome }): "recorded" | "closed";
|
|
639
|
+
};
|
|
640
|
+
type InitOutcome =
|
|
641
|
+
| { kind: "started"; destination: string }
|
|
642
|
+
| { kind: "not-started"; reason: string };
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
The host offers synchronously as `pi-herdr-agents` and starts nothing until the
|
|
646
|
+
emitter calls `start()`. Call it only when exactly one host offered. `start`
|
|
647
|
+
runs init with the request's context and returns synchronously; the emitter
|
|
648
|
+
shows a `not-started` reason. The host does not listen in subagent sessions
|
|
649
|
+
and unsubscribes at `session_shutdown`. Unsubscribe an approval listener there
|
|
650
|
+
too, as the role-pack bridge does.
|
|
651
|
+
|
|
552
652
|
### Supervision transport
|
|
553
653
|
|
|
554
654
|
On supported local filesystems, supervision uses file wake-ups plus one shared
|
|
555
655
|
4.8-second pane reconciliation. A wake-up only prompts fresh evidence
|
|
556
|
-
collection; it never establishes a result by itself.
|
|
557
|
-
|
|
558
|
-
|
|
656
|
+
collection; it never establishes a result by itself. While any child's wait for
|
|
657
|
+
that check is parked, the coordinator's file watches stay referenced, so the
|
|
658
|
+
sidecar is still observed when the parent has no other event-loop work. The
|
|
659
|
+
reference is released when the wait settles, is aborted, or its child is
|
|
660
|
+
unregistered. With no parked wait, the watches stay unreferenced and do not
|
|
661
|
+
keep the process running. If the watcher
|
|
662
|
+
or shared pane inspection becomes unavailable, supervision quietly returns to
|
|
663
|
+
the legacy one-second polling cadence. No caller action is required.
|
|
559
664
|
|
|
560
665
|
Set `supervision.forcePolling` to `true` in the durable user `config.json` to
|
|
561
666
|
disable wake-ups and use that legacy cadence deliberately. The setting is read
|
|
@@ -866,15 +971,19 @@ is no other parent/child protocol.
|
|
|
866
971
|
|
|
867
972
|
`/worktree <worktree> [task]` creates a Herdr-managed worktree from the current committed branch and launches a new interactive Pi session there with the active conversation branch. The original session remains available. Use `/worktree list` or `worktree_list({})` to inspect managed worktrees, including cross-session orphans, whose canonical source repositories are inside the session's cwd subtree. This is a new-process handoff, not an in-place move of the existing shell or Pi process.
|
|
868
973
|
|
|
974
|
+
The destination workspace is focused only after Pi startup is confirmed. Every fresh launch, including the handoff, sets `PI_HERDR_AGENTS_SESSION` to its session file; confirmation requires a Pi process in the root pane with the worktree cwd whose launch-time environment carries that value. Pi rewrites its process title, so command-line text is never evidence. Nothing inside Pi reads the variable, and it is not a child-context hint. Confirmation reads Linux `/proc`; see [Worktree subagents](docs/worktree-subagents.md).
|
|
975
|
+
|
|
869
976
|
---
|
|
870
977
|
|
|
871
978
|
### Explicit worktree cleanup
|
|
872
979
|
|
|
873
980
|
Parent sessions can call `worktree_remove({ target: "<path|branch|workspace-id>", preserve: true })` or `/worktree remove <target> [--preserve]`. Preservation is optional and never implied: dirty work is blocked unless explicitly committed first or preserved as a WIP commit. The result reports its SHA even if removal later fails or is refused. A failed preservation commit restores the pre-preservation index and never proceeds to removal. Inventory and removal reports disclose exact ignored-file counts: enumeration is streamed rather than buffered as one listing. Ignored files do not block cleanup and are not captured by preservation; failed counting still blocks removal.
|
|
874
981
|
|
|
875
|
-
Eligibility is rechecked at removal time: canonical source-repository cwd containment, registered linked checkout, no detected process holder, known live child, or persistent lease, and clean Git state with no untracked files or conflicts. A successful launch from a linked checkout does not itself authorize its removal; cleanup uses the canonical principal/source repository under the invoking parent session's cwd, not `manifest.sourceCwd` or shared Git identity. Start the parent Pi session rooted at that principal checkout or an ancestor containing it, then use normal explicit cleanup; `cd` inside an existing Pi session does not change the session cwd. A source repository that is a Git submodule is located through the `core.worktree` setting of its shared Git directory under the superproject's `.git/modules`, not through that directory's parent. Unknown inspection, identity disagreements, detached HEAD, locked checkouts, and initialized submodules block removal. Out-of-scope repositories are never eligible. Open workspaces use Herdr removal;
|
|
982
|
+
Eligibility is rechecked at removal time: canonical source-repository cwd containment, registered linked checkout, no detected process holder, known live child, or persistent lease, and clean Git state with no untracked files or conflicts. A successful launch from a linked checkout does not itself authorize its removal; cleanup uses the canonical principal/source repository under the invoking parent session's cwd, not `manifest.sourceCwd` or shared Git identity. Start the parent Pi session rooted at that principal checkout or an ancestor containing it, then use normal explicit cleanup; `cd` inside an existing Pi session does not change the session cwd. A source repository that is a Git submodule is located through the `core.worktree` setting of its shared Git directory under the superproject's `.git/modules`, not through that directory's parent. Unknown inspection, identity disagreements, detached HEAD, locked checkouts, and initialized submodules block removal. Out-of-scope repositories are never eligible. Open worktree workspaces use Herdr removal. The source repository's primary workspace is never closed automatically. If this process recorded that worktree creation appeared to open it and a read taken just before the report still shows it untouched, removal adds `wX appears to have been opened by worktree creation; if you haven't used it, close it with herdr workspace close wX`; a missing or wrong-typed field, another open workspace for the repository, another session, or a restarted process reports nothing. Orphans use Git removal and registration pruning after checkout absence is verified. Owned reachable manifests are marked `removed`; manifests from other sessions are not required or rewritten. Stale removed manifests never govern a recreated checkout. Missing or dangling manifest paths do not affect unrelated checkouts; undecidable or conflicting manifest identity still blocks removal. A failed manifest update after removal is reported as a warning. Symlinked ancestors are supported; checkout symlinks escaping the canonical managed root are blocked. Process inspection covers observable same-user processes across sessions, regardless of runtime name, exempting only Herdr-confirmed idle retained shells, never runtimes at the same PID. Unreadable individual process details produce non-blocking warnings without an override flag; scanning continues so another observable holder still blocks removal. Human and structured inventory/removal results disclose incomplete coverage, including warnings seen before a later recheck or failure. Same-user inspection is permission-limited, and other-user processes are not inspected: a protected process could hold the checkout undetected. Warnings aggregate counts and bounded PID samples, not commands or environments. Linux uses `/proc`; macOS uses same-user `lsof` cwd records and warns for unreadable individual records. Unsupported platforms, failed global enumeration (including a failed `lsof` with partial output), and other unverifiable eligibility evidence still block removal. Cleanup Git and Herdr calls have 30-second timeouts.
|
|
983
|
+
|
|
984
|
+
A worktree launch may add three synchronous Herdr snapshots (`worktree list` before create, `worktree list` after create, and `pane list` for the opened primary), each bounded at 3 seconds, 9 seconds if all three run to their timeouts. A failed snapshot claims nothing and is returned as a launch diagnostic, shown in the `subagent` acknowledgement and the `/worktree` notice; it is not written to the console. Claims are kept only in this process and are not written to the manifest. Ordinary non-worktree launches add no Herdr calls. Explicit removal adds a primary-workspace report only when this process holds a claim for the removed source repository: `worktree list`, `workspace get`, and `pane list`, each asynchronous and bounded at 3 seconds, taken after the checkout is removed. That is at most three calls and 9 seconds of wall time, without blocking the event loop. These report calls sit on top of the existing cleanup Git and Herdr calls, which stay at 30 seconds each.
|
|
876
985
|
|
|
877
|
-
Cleanup never deletes or rewrites branches, uses force flags, or runs automatically. Session startup does not scan worktree inventory, avoiding blocking Pi initialization; use `/worktree list` or `worktree_list` for an explicit inventory. Child sessions retain `/worktree list` and `/worktree <name>`, but receive neither cleanup tools nor the remove subcommand. Their repository-local listing labels detached entries `(detached HEAD)`; a detached sibling does not prevent inspection of named branches. See [cleanup and recovery](docs/worktree-subagents.md#cleanup) for details.
|
|
986
|
+
Cleanup never deletes or rewrites branches, uses force flags, or runs automatically. It never closes the source repository's primary workspace. Session startup does not scan worktree inventory, avoiding blocking Pi initialization; use `/worktree list` or `worktree_list` for an explicit inventory. Child sessions retain `/worktree list` and `/worktree <name>`, but receive neither cleanup tools nor the remove subcommand. Their repository-local listing labels detached entries `(detached HEAD)`; a detached sibling does not prevent inspection of named branches. See [cleanup and recovery](docs/worktree-subagents.md#cleanup) for details.
|
|
878
987
|
|
|
879
988
|
## Custom Agents
|
|
880
989
|
|
|
@@ -1258,12 +1367,16 @@ npm run lint
|
|
|
1258
1367
|
npm pack --dry-run
|
|
1259
1368
|
```
|
|
1260
1369
|
|
|
1370
|
+
`npm run lint` needs Node.js 22.18+ (or 22.6–22.17 with `NODE_OPTIONS=--experimental-strip-types`) because oxlint imports TypeScript directly: its configuration, `oxlint.config.ts`, and the JavaScript plugin it loads, `tools/oxlint/anti-slop/index.ts`. Without type stripping, oxlint fails before linting with `Unknown file extension ".ts"`. This is a contributor tooling requirement, not a runtime requirement for package users.
|
|
1371
|
+
|
|
1261
1372
|
Run the required end-to-end suite from inside Herdr:
|
|
1262
1373
|
|
|
1263
1374
|
```bash
|
|
1264
1375
|
npm run test:integration
|
|
1265
1376
|
```
|
|
1266
1377
|
|
|
1378
|
+
Start the Herdr server with Node.js 22.19+ on its `PATH`, because test panes inherit the server's environment, not the test runner's: Pi 1.0.0 needs Node.js 22.19+ and crashes at startup on versions older than 22.15.
|
|
1379
|
+
|
|
1267
1380
|
The deterministic suite launches real Pi sessions, Herdr panes, and worktrees without provider credentials. The optional live-provider smoke test is not a merge gate:
|
|
1268
1381
|
|
|
1269
1382
|
```bash
|
package/RELEASING.md
CHANGED
|
@@ -10,6 +10,8 @@ The published version must be unique on npm.
|
|
|
10
10
|
|
|
11
11
|
`3.0.0` is the pack-neutral baseline. It is a major release because it removes the bundled roles, `/plan`, `/skill:orchestrate`, `/iterate`, `/btw`, and `/btw-close`, and makes `roles.bundled` a deprecated no-op. Its hand-written breaking changes and migration notes are in the README [Release notes](README.md#release-notes) section, because `npm version` regenerates `CHANGELOG.md` and would discard hand edits there. Record later breaking-release notes in the same section.
|
|
12
12
|
|
|
13
|
+
`3.1.0` is a compatible minor release. It adds the request-scoped task-model init events that let a loaded pack approve `/subagents-init` proposals; with no pack offering, the direct writer is unchanged. It was prepared from feature commit `1b6bacd0b5eecb6f83ffba1d5a5ee57a8b0fa944`. It is not published until the release workflow succeeds.
|
|
14
|
+
|
|
13
15
|
Do not design a release that creates a GitHub Release without a successful npm publish for a new version. The workflow publishes first, then tags and creates the GitHub Release.
|
|
14
16
|
|
|
15
17
|
## Prerequisites
|
package/docs/README.md
CHANGED
|
@@ -60,10 +60,11 @@ real implementations; in-memory fakes are conformance fixtures only.
|
|
|
60
60
|
| [`0008`](adr/0008-adopt-pi-only-subagent-execution.md) | Partially superseded by 0012 | Keep Pi-only execution; 0012 supersedes only the adapter-seam restriction. |
|
|
61
61
|
| [`0009`](adr/0009-remove-workflow-subsystem.md) | Accepted; skill ownership moved by 0013 | Remove the workflow subsystem; use public subagent fan-out and parent synthesis. |
|
|
62
62
|
| [`0010`](adr/0010-persistent-specialists-as-session-generations.md) | Accepted | Define persistent specialists as logical identities with policy-bound session generations. |
|
|
63
|
-
| [`0011`](adr/0011-explicit-worktree-cleanup.md) | Accepted | Authorize explicit worktree cleanup by cwd containment; retain branches and reject automatic reaping. |
|
|
63
|
+
| [`0011`](adr/0011-explicit-worktree-cleanup.md) | Accepted; amended by 0015 | Authorize explicit worktree cleanup by cwd containment; retain branches and reject automatic reaping. |
|
|
64
64
|
| [`0012`](adr/0012-adopt-maestro-seams-in-repo.md) | Accepted | Adopt in-repo seams and conformance fakes; keep one package, Pi-only execution, and Herdr-only surfaces. |
|
|
65
65
|
| [`0013`](adr/0013-pack-neutral-execution-host.md) | Accepted | Ship a pack-neutral execution host; roles and workflows move to optional packs, and `/iterate` and `/btw` are removed. |
|
|
66
66
|
| [`0014`](adr/0014-operator-cancel-terminal-intent.md) | Accepted | Operator cancel records terminal intent before owned termination: no fallback, one confirmed cancelled result, unconfirmed runs stay live. |
|
|
67
|
+
| [`0015`](adr/0015-report-opened-primary-workspace.md) | Accepted | Report, and never automatically close, a primary workspace that worktree creation opened. |
|
|
67
68
|
|
|
68
69
|
## Historical material
|
|
69
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ADR-0011: Authorize explicit worktree cleanup by cwd containment
|
|
2
2
|
|
|
3
|
-
- **Status:** Accepted
|
|
3
|
+
- **Status:** Accepted; amended by [ADR-0015](0015-report-opened-primary-workspace.md)
|
|
4
4
|
- **Date:** 2026-09-17
|
|
5
5
|
- **Scope:** Managed subagent worktree cleanup (issue #45)
|
|
6
6
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# ADR-0015: Report the primary workspace worktree creation opened
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-10-07
|
|
5
|
+
- **Amends:** [ADR-0011](0011-explicit-worktree-cleanup.md)
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
`herdr worktree create` groups a new worktree with the source repository's
|
|
10
|
+
primary workspace and opens that workspace when none is open. `herdr worktree
|
|
11
|
+
remove` removes only the linked checkout, so the first worktree of an idle
|
|
12
|
+
repository leaves an empty shell workspace behind.
|
|
13
|
+
|
|
14
|
+
Herdr's `WorktreeCreated` result does not include `created_parent`, so this
|
|
15
|
+
extension cannot ask Herdr which workspace the create call opened. The only
|
|
16
|
+
signal is a `herdr worktree list --cwd` snapshot before create and another
|
|
17
|
+
after it. A workspace the user or another session opens during a long checkout
|
|
18
|
+
can be attributed to this create. That race is inherent in the snapshots.
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
Explicit worktree removal never closes the source repository's primary
|
|
23
|
+
workspace.
|
|
24
|
+
|
|
25
|
+
On a worktree launch, this process may record one claim: workspace id, the
|
|
26
|
+
source `repo_key`, the only pane's `terminal_id`, and the checkout path the
|
|
27
|
+
workspace was opened at. A workspace that was already open is not claimed. A
|
|
28
|
+
snapshot that fails, times out, or lacks a required field claims nothing. The
|
|
29
|
+
failure is returned as a launch diagnostic, shown in the `subagent`
|
|
30
|
+
acknowledgement and the `/worktree` notice, and never written to the console.
|
|
31
|
+
Ordinary non-worktree launches do not take these snapshots.
|
|
32
|
+
|
|
33
|
+
The claim lives only in the creating process and is not written to the
|
|
34
|
+
manifest. Removal from another session, or after this process restarts,
|
|
35
|
+
reports nothing. Extension reload inside the same process keeps the in-memory
|
|
36
|
+
claim.
|
|
37
|
+
|
|
38
|
+
On explicit removal, core checks whether this process holds a claim whose
|
|
39
|
+
checkout is the removed source repository. If not, removal makes no report
|
|
40
|
+
calls at all. Otherwise the Herdr surface reads the source once more, after
|
|
41
|
+
the checkout is removed and immediately before any sentence. It may tell the
|
|
42
|
+
parent:
|
|
43
|
+
|
|
44
|
+
`wX appears to have been opened by worktree creation; if you haven't used it, close it with herdr workspace close wX`
|
|
45
|
+
|
|
46
|
+
only when that read shows the workspace untouched: the default label equal to
|
|
47
|
+
the repository name (both present), not focused, one tab, not a linked
|
|
48
|
+
worktree, the claimed `repo_key`, one pane, the recorded terminal, and `cwd`
|
|
49
|
+
and `foreground_cwd` at the recorded checkout. No other workspace may be open
|
|
50
|
+
for the repository; a listed row with an open workspace and no boolean
|
|
51
|
+
`is_linked_worktree` blocks the sentence too. Any missing or wrong-typed field
|
|
52
|
+
a check uses means the workspace is not untouched, so removal says nothing
|
|
53
|
+
about it. Shell idleness and child processes are not checked: the sentence is
|
|
54
|
+
advisory and hedged, and the extra reads would double the removal cost
|
|
55
|
+
without making a close safe.
|
|
56
|
+
|
|
57
|
+
The surface method is `SurfaceProvider.reportOpenedPrimaryWorkspace`. Herdr
|
|
58
|
+
implements it. Other surfaces return no report. Core does not import Herdr.
|
|
59
|
+
|
|
60
|
+
A failed report is a warning. The checkout removal still succeeds. Nothing in
|
|
61
|
+
this path polls, retries, or sleeps.
|
|
62
|
+
|
|
63
|
+
### Blocking time
|
|
64
|
+
|
|
65
|
+
A worktree launch adds at most three synchronous Herdr calls, each bounded at
|
|
66
|
+
3 seconds:
|
|
67
|
+
|
|
68
|
+
1. `herdr worktree list --cwd` before create
|
|
69
|
+
2. `herdr worktree list --cwd` after create, only when the before snapshot
|
|
70
|
+
succeeded and showed no primary workspace
|
|
71
|
+
3. `herdr pane list --workspace` for that workspace's terminal, only when the
|
|
72
|
+
after snapshot shows a new primary workspace with a repo key and checkout
|
|
73
|
+
path
|
|
74
|
+
|
|
75
|
+
If each runs to its timeout, the extra blocking time is 9 seconds. A failed
|
|
76
|
+
before-snapshot skips the later two. `herdr worktree create` itself is an
|
|
77
|
+
existing call and is not given a new timeout here.
|
|
78
|
+
|
|
79
|
+
Explicit removal adds at most three asynchronous calls, each bounded at
|
|
80
|
+
3 seconds, and only when this process holds a claim for the removed source
|
|
81
|
+
repository:
|
|
82
|
+
|
|
83
|
+
1. `herdr worktree list --cwd`
|
|
84
|
+
2. `herdr workspace get`
|
|
85
|
+
3. `herdr pane list --workspace`
|
|
86
|
+
|
|
87
|
+
If each runs to its timeout, removal takes up to 9 more seconds of wall time.
|
|
88
|
+
The calls do not block the event loop. A failed or decisive call stops the
|
|
89
|
+
rest. These calls are in addition to the existing cleanup Git and Herdr calls,
|
|
90
|
+
which stay at 30 seconds each.
|
|
91
|
+
|
|
92
|
+
## Rejected alternatives
|
|
93
|
+
|
|
94
|
+
- **Automatic close from the untouched-workspace heuristic.** One tab, one
|
|
95
|
+
pane, the same terminal id, cwd at the checkout, and the default label, even
|
|
96
|
+
with an idle shell and no children, cannot tell whether the user worked in
|
|
97
|
+
the workspace. `git
|
|
98
|
+
log`, a test run, and `cd` back to the checkout leave that same picture, and
|
|
99
|
+
closing it would destroy the scrollback.
|
|
100
|
+
- **Pane fingerprinting.** Reading `herdr pane read --source recent` at claim
|
|
101
|
+
time and requiring the same text before a close would catch some commands
|
|
102
|
+
the heuristic misses. It is not clearly better than reporting. Prompt
|
|
103
|
+
redraws, hooks, and Herdr's own startup change the text without a user
|
|
104
|
+
command; a cleared screen looks untouched; a read can fail; and the gap
|
|
105
|
+
between the confirming read and a close is still a race. A mistaken close
|
|
106
|
+
is destructive. A mistaken report is a command the user can ignore.
|
|
107
|
+
- **Trusting the manifest after restart.** The creating process is the only
|
|
108
|
+
observer of the before/after snapshots. A later process would be acting on
|
|
109
|
+
a claim it did not make.
|
|
110
|
+
|
|
111
|
+
## Consequences
|
|
112
|
+
|
|
113
|
+
Cwd containment, branch retention, and the rejection of automatic reaping in
|
|
114
|
+
ADR-0011 are unchanged. A misattributed claim can name a workspace this create
|
|
115
|
+
did not open, so the sentence says the workspace "appears" to have been opened
|
|
116
|
+
and asks the user to close it only if they have not used it. The message does
|
|
117
|
+
not close it. There is no second re-check: the single set of reads is taken
|
|
118
|
+
immediately before the sentence, and a workspace that changes after those
|
|
119
|
+
reads can still be named. The sentence is still only a suggestion.
|
|
120
|
+
|
|
121
|
+
See the [operating guide](../worktree-subagents.md#cleanup).
|
|
@@ -48,11 +48,12 @@ For a worktree launch:
|
|
|
48
48
|
- `worktree.branch` is a new, unique branch name. Git/Herdr rejects a branch that cannot be created or is already checked out elsewhere.
|
|
49
49
|
- `worktree.base` may be any revision that resolves to a commit in the source repository. It defaults to committed `HEAD`.
|
|
50
50
|
- The extension resolves `base` to an exact SHA, writes an ownership manifest, then calls `herdr worktree create --no-focus`.
|
|
51
|
+
- Herdr groups the new worktree with the source repository's primary workspace and opens one when none is open. The extension reads `herdr worktree list --cwd` before and after creation, and `herdr pane list` for the opened workspace's terminal, only to record a claim in this process. Each of those reads has a 3-second timeout. A worktree launch runs at most three of them; if each runs to its timeout, that is 9 seconds on top of create. A workspace you already had open is not claimed. An unreadable, timed-out, or incomplete snapshot claims nothing and is reported as a launch diagnostic in the `subagent` acknowledgement or `/worktree` notice, never on the console. A failed before-snapshot skips the later reads. Ordinary pane launches do not make these calls. The claim is not written to the manifest and is not kept after this process exits.
|
|
51
52
|
- The child starts at the root of the returned worktree, in that workspace's retained root pane and newly owned `Agents` tab. Finishing Pi returns to the interactive checkout shell; it does not close the root pane. A successful launch from a linked checkout does not itself authorize cleanup there: cleanup checks the canonical principal/source repository under the invoking parent session's cwd, not `manifest.sourceCwd` or shared Git identity. Start the parent Pi session rooted at the principal checkout or an ancestor containing it, then use normal explicit cleanup; changing directories inside an existing Pi session does not change its session cwd.
|
|
52
53
|
- Uncommitted and untracked files from the parent checkout are not copied. Commit anything the child must see before spawning it, or pass the needed context in the task.
|
|
53
54
|
- Worktree creation does not steal terminal focus.
|
|
54
55
|
|
|
55
|
-
For an explicit interactive handoff, use `/worktree <worktree> [task]`. It creates the worktree from the current committed branch, forks the active conversation branch into the target-cwd session, launches a normal long-lived Pi process in the returned root pane, and focuses the destination workspace only after
|
|
56
|
+
For an explicit interactive handoff, use `/worktree <worktree> [task]`. It creates the worktree from the current committed branch, forks the active conversation branch into the target-cwd session, launches a normal long-lived Pi process in the returned root pane, and focuses the destination workspace only after Pi startup is confirmed: a Pi process in the root pane's Herdr foreground list must have the worktree cwd and, in its launch-time environment (`/proc/<pid>/environ`), `PI_HERDR_AGENTS_SESSION` naming the forked session file. An unreadable environment (for example, a process that is exiting) never confirms startup. Command-line text is never evidence, because Pi rewrites its process title and Herdr then reports only `pi`. The confirmation reads Linux `/proc`; on other hosts it fails within its bounded wait and the launch-failure retention below applies. Use `/worktree list` to inspect managed worktrees whose source repositories are inside the current cwd subtree, including cross-session orphans. The original process and session remain intact; pane movement is not used to change a running shell's cwd.
|
|
56
57
|
|
|
57
58
|
`worktree` cannot be set in agent frontmatter and is not exposed by the `/subagent <agent> <task>` shorthand. It is selected per call to the `subagent` tool. Ordered model fallback lists are not supported for worktree subagents: a failed attempt retains its worktree and branch for review, so a retry cannot safely reuse the requested branch. A persistent specialist either holds one worktree lease for its full lifetime or runs read-only in an ordinary pane; it cannot be re-bound.
|
|
58
59
|
|
|
@@ -235,7 +236,7 @@ Dirty work requires an explicit `--preserve` or `preserve: true`. This stages al
|
|
|
235
236
|
|
|
236
237
|
Ignored files do not block cleanup. Inventory shows their exact file count, and a successful removal reports how many were deleted. Counting streams the NUL-delimited Git listing rather than buffering all paths; errors and the 30-second timeout still block removal, never report a guessed zero. Preservation does not capture ignored files; attempted and successful preservation reports disclose that exclusion when ignored files are present.
|
|
237
238
|
|
|
238
|
-
Open workspaces are removed through Herdr. Git-only orphans use `git worktree remove`, verify checkout absence, and then prune stale Git registrations. Failed removal is reported, never forced. A reachable owned manifest is merge-updated to `removed` with `workspaceRemovedAt` only after success; absent cross-session manifests are reported and are not rewritten. A manifest update failure after removal is a removed-with-warning result, not a failed removal. Removed manifests provide already-removed no-op evidence only; a new checkout at the same path is classified independently. Missing and dangling manifest paths are non-matches. Permission errors, symlink loops, and other undecidable manifest identities remain blockers, as do genuine identity disagreements. Cleanup Git and Herdr calls have 30-second timeouts; a timed-out operation fails closed rather than proceeding with removal.
|
|
239
|
+
Open worktree workspaces are removed through Herdr. That removal never closes the source repository's primary workspace. If this process recorded that worktree creation opened it, removal reads the workspace after the checkout is removed and may report `wX appears to have been opened by worktree creation; if you haven't used it, close it with herdr workspace close wX` only when those reads, taken immediately before that sentence, show the workspace untouched: the default label equal to the repository name, not focused, one tab, not a linked worktree, the claimed `repo_key`, one pane, the recorded terminal, and both `cwd` and `foreground_cwd` at the recorded checkout, with no other workspace open for the repository. A listed row with an open workspace but no boolean `is_linked_worktree` blocks the sentence. A missing or wrong-typed `label`, `repo_name`, `focused`, `tab_count`, `foreground_cwd`, `terminal_id`, or any other field a check uses means the workspace is not untouched, so removal says nothing about it. Shell idleness and child processes are not checked; the sentence is a hedged suggestion, not a close. The before/after list can attribute a workspace that you or another session opened during a long checkout, because Herdr does not say which workspace create opened; the message does not close anything, so a wrong id is only a suggestion. Claims live only in the creating process: removal from another session, or after a restart, reports nothing. The report's longest path is three asynchronous calls — `worktree list`, `workspace get`, and `pane list`, 3 seconds each — 9 seconds of wall time if every call runs to its timeout, without blocking the event loop. A failed call stops the rest. With no claim for the removed source repository, removal adds none of these calls. Git-only orphans use `git worktree remove`, verify checkout absence, and then prune stale Git registrations. Failed removal is reported, never forced. A reachable owned manifest is merge-updated to `removed` with `workspaceRemovedAt` only after success; absent cross-session manifests are reported and are not rewritten. A manifest update failure after removal is a removed-with-warning result, not a failed removal. Removed manifests provide already-removed no-op evidence only; a new checkout at the same path is classified independently. Missing and dangling manifest paths are non-matches. Permission errors, symlink loops, and other undecidable manifest identities remain blockers, as do genuine identity disagreements. Cleanup Git and Herdr calls have 30-second timeouts; a timed-out operation fails closed rather than proceeding with removal.
|
|
239
240
|
|
|
240
241
|
Branches and their commits are always retained: cleanup never deletes, force-updates, or rewrites a branch. It never runs on completion, shutdown, or a timer. Session startup skips worktree inventory to avoid blocking Pi initialization; use `/worktree list` or `worktree_list` for an explicit inventory. Outside Herdr, explicit inventory tools remain available and report unavailable inspection as unknown. Unsupported platforms or failed global process enumeration block removal; individual visibility gaps are disclosed warnings, never proof that an orphan is idle.
|
|
241
242
|
|
|
@@ -246,7 +247,8 @@ This first version intentionally does not provide:
|
|
|
246
247
|
- automatic push, PR creation, merge, or cherry-pick
|
|
247
248
|
- automatic worktree or branch removal
|
|
248
249
|
- worktree-aware `subagent_resume`
|
|
249
|
-
- durable restart reconciliation
|
|
250
|
+
- durable restart reconciliation (an opened-primary-workspace claim does not survive process restart)
|
|
251
|
+
- automatic close of the source repository's primary workspace
|
|
250
252
|
- dependency DAG scheduling or merge queues
|
|
251
253
|
- stacked-branch management
|
|
252
254
|
- multi-repository workspaces
|
|
@@ -316,8 +316,18 @@ export function createSubagentActivityRecorder(params: {
|
|
|
316
316
|
const runningChildId = params.runningChildId?.trim();
|
|
317
317
|
const activityFile = params.activityFile?.trim();
|
|
318
318
|
if (!runningChildId || !activityFile) return createNoopRecorder();
|
|
319
|
+
return createFileActivityRecorder(
|
|
320
|
+
runningChildId,
|
|
321
|
+
activityFile,
|
|
322
|
+
params.now ?? (() => Date.now()),
|
|
323
|
+
);
|
|
324
|
+
}
|
|
319
325
|
|
|
320
|
-
|
|
326
|
+
function createFileActivityRecorder(
|
|
327
|
+
runningChildId: string,
|
|
328
|
+
activityFile: string,
|
|
329
|
+
now: () => number,
|
|
330
|
+
): SubagentActivityRecorder {
|
|
321
331
|
const createdAt = now();
|
|
322
332
|
const activity: SubagentActivityState = {
|
|
323
333
|
version: 1,
|