pi-herdr-agents 3.0.1 → 3.0.3
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 +40 -1
- package/CONTEXT.md +14 -3
- package/README.md +29 -9
- 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 +7 -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/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/worktree-operations.ts +16 -2
- 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 +35 -16
- package/skills/pi-herdr-agents/SKILL.md +6 -1
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,7 +7,45 @@ 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.0.3](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.2...v3.0.3)
|
|
11
|
+
|
|
12
|
+
### Merged
|
|
13
|
+
|
|
14
|
+
- test(integration): re-read worktree row before asserting its open workspace [`#81`](https://github.com/giuseppecrj/pi-herdr-agents/pull/81)
|
|
15
|
+
- Fix Pi readiness check when Pi rewrites argv [`#80`](https://github.com/giuseppecrj/pi-herdr-agents/pull/80)
|
|
16
|
+
- Fix tsc errors and git-version-sensitive index test; document lint Node floor [`#79`](https://github.com/giuseppecrj/pi-herdr-agents/pull/79)
|
|
17
|
+
- fix(worktrees): report the primary workspace creation opened [`#78`](https://github.com/giuseppecrj/pi-herdr-agents/pull/78)
|
|
18
|
+
- fix: keep parked supervision wakes alive until they settle [`#77`](https://github.com/giuseppecrj/pi-herdr-agents/pull/77)
|
|
19
|
+
|
|
20
|
+
### Commits
|
|
21
|
+
|
|
22
|
+
- remove openspec [`5e31900`](https://github.com/giuseppecrj/pi-herdr-agents/commit/5e31900ea96fdaab6fcf26fc9a335497dcbc528d)
|
|
23
|
+
- fix(worktrees): bound the primary workspace report and hedge its note [`44a18a8`](https://github.com/giuseppecrj/pi-herdr-agents/commit/44a18a8145549801bd17007f05beb8a8cddf41b9)
|
|
24
|
+
- Confirm Pi startup by launch environment, not rewritten argv [`bbfe7e7`](https://github.com/giuseppecrj/pi-herdr-agents/commit/bbfe7e7093bd76d8803562ddb6263c27cfe4a982)
|
|
25
|
+
- fix: settle stranded supervision waits and test wake retain accounting [`d18df1c`](https://github.com/giuseppecrj/pi-herdr-agents/commit/d18df1cb83bedf86951d2f9702a4a6185b44a8e8)
|
|
26
|
+
- test: bound the identity-capture loop anchor and close supervision test gaps [`4a0e71a`](https://github.com/giuseppecrj/pi-herdr-agents/commit/4a0e71a97ebc9ba3f21a5377504f24c3d837d9a2)
|
|
27
|
+
- Treat an unreadable Pi environment as unproven, not fatal [`b69151a`](https://github.com/giuseppecrj/pi-herdr-agents/commit/b69151ae85bd13c45bdf3e980c0b36e315c7c863)
|
|
28
|
+
- test(worktree-cleanup): make the index-restore test hermetic and deterministic [`16840f6`](https://github.com/giuseppecrj/pi-herdr-agents/commit/16840f64b9481983cf44fc928061fd4e6822434d)
|
|
29
|
+
- test(worktree-cleanup): clear inherited git repository variables in the fixture [`323e1fb`](https://github.com/giuseppecrj/pi-herdr-agents/commit/323e1fbf6e6240eb5e7000d63fc84e23b391259a)
|
|
30
|
+
- test(worktrees): expect launch snapshots in scoped inventory [`f733040`](https://github.com/giuseppecrj/pi-herdr-agents/commit/f73304088c8a781b1ed7333a5a09175ec7c896e9)
|
|
31
|
+
- fix(types): resolve strict tsc errors in activity scope guard and recorder [`26bc9ef`](https://github.com/giuseppecrj/pi-herdr-agents/commit/26bc9ef2e8a2afc23901641efdd19661fcd3f756)
|
|
32
|
+
- docs: limit the lint type-stripping fallback to Node 22.6-22.17 [`ccab597`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ccab597cfaf2bcf6e37889ae3103dd6e9c78c6ef)
|
|
33
|
+
- docs: note that npm run lint needs Node.js 22.18 or newer [`b8ffa8d`](https://github.com/giuseppecrj/pi-herdr-agents/commit/b8ffa8d1d83419588db8d4aed506d5d236360673)
|
|
34
|
+
- test(integration): check cancel manifest workspace id; clarify Herdr Node note [`aac18d9`](https://github.com/giuseppecrj/pi-herdr-agents/commit/aac18d926385582ccc36783fd8a9475db7df7e3d)
|
|
35
|
+
- test(worktree-cleanup): snapshot the index after git status [`aa869b2`](https://github.com/giuseppecrj/pi-herdr-agents/commit/aa869b2a62c8868d377b134d3f05b029692f2eb0)
|
|
36
|
+
- docs: integration tests need the Herdr server on Node.js 22.19+ [`bdd791b`](https://github.com/giuseppecrj/pi-herdr-agents/commit/bdd791bd934ea3174db32e1fcf1353353506224b)
|
|
37
|
+
|
|
38
|
+
## [v3.0.2](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.1...v3.0.2) - 2026-10-06
|
|
39
|
+
|
|
40
|
+
### Merged
|
|
41
|
+
|
|
42
|
+
- fix(subagents): resolve submodule sources and refresh fallback context [`#76`](https://github.com/giuseppecrj/pi-herdr-agents/pull/76)
|
|
43
|
+
|
|
44
|
+
### Commits
|
|
45
|
+
|
|
46
|
+
- chore: release v3.0.2 [`245e54e`](https://github.com/giuseppecrj/pi-herdr-agents/commit/245e54e7ccc937f69bee6093906d26b03b8a92c9)
|
|
47
|
+
|
|
48
|
+
## [v3.0.1](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.0...v3.0.1) - 2026-10-06
|
|
11
49
|
|
|
12
50
|
### Merged
|
|
13
51
|
|
|
@@ -16,6 +54,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
16
54
|
### Commits
|
|
17
55
|
|
|
18
56
|
- docs: README for 3.0.0: pstack ships poteto-mode not a role, safety and uninstall, contents, agent pointer [`7ed9969`](https://github.com/giuseppecrj/pi-herdr-agents/commit/7ed99693b1a2d25137d4ba2430f23255923ce3ea)
|
|
57
|
+
- chore: release v3.0.1 [`ba349db`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ba349db4a4280a39b70a067fb62e7816f9a2061f)
|
|
19
58
|
- docs: hero image uses generic agent labels instead of removed role names [`70ad6f0`](https://github.com/giuseppecrj/pi-herdr-agents/commit/70ad6f04d492f58e04062e01c1a529ddd1708431)
|
|
20
59
|
- docs: address review: full write footprint, worktree cleanup before uninstall, skill table delimiter [`ababee3`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ababee3a3f18d2112ec8959303986e1a08e3dc2a)
|
|
21
60
|
|
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
|
package/README.md
CHANGED
|
@@ -553,9 +553,14 @@ project definitions keep their precedence over them.
|
|
|
553
553
|
|
|
554
554
|
On supported local filesystems, supervision uses file wake-ups plus one shared
|
|
555
555
|
4.8-second pane reconciliation. A wake-up only prompts fresh evidence
|
|
556
|
-
collection; it never establishes a result by itself.
|
|
557
|
-
|
|
558
|
-
|
|
556
|
+
collection; it never establishes a result by itself. While any child's wait for
|
|
557
|
+
that check is parked, the coordinator's file watches stay referenced, so the
|
|
558
|
+
sidecar is still observed when the parent has no other event-loop work. The
|
|
559
|
+
reference is released when the wait settles, is aborted, or its child is
|
|
560
|
+
unregistered. With no parked wait, the watches stay unreferenced and do not
|
|
561
|
+
keep the process running. If the watcher
|
|
562
|
+
or shared pane inspection becomes unavailable, supervision quietly returns to
|
|
563
|
+
the legacy one-second polling cadence. No caller action is required.
|
|
559
564
|
|
|
560
565
|
Set `supervision.forcePolling` to `true` in the durable user `config.json` to
|
|
561
566
|
disable wake-ups and use that legacy cadence deliberately. The setting is read
|
|
@@ -625,10 +630,17 @@ example `provider/preferred, provider/fallback`. The tool argument also accepts
|
|
|
625
630
|
candidate before launch, then launches later candidates only after the selected
|
|
626
631
|
child settles with a provider/agent error. Pi owns any automatic transient
|
|
627
632
|
retrying inside that child; the extension does not infer retry counts or
|
|
628
|
-
permanence from the error text. A
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
633
|
+
permanence from the error text. A later candidate that launches after a parent
|
|
634
|
+
`/reload`, `/new`, `/resume`, or `/fork` uses the live parent session for its
|
|
635
|
+
artifacts and lineage, as completion delivery does. Its candidate list and
|
|
636
|
+
thinking level stay those of the original call. The original parent directory
|
|
637
|
+
and process directory remain the bases for directory resolution. Role files are
|
|
638
|
+
read again for each attempt, so a changed role `cwd` can redirect a fallback
|
|
639
|
+
when the tool call did not specify `cwd`. If no live parent context is available,
|
|
640
|
+
that candidate fails without launching. A completed child result, including a
|
|
641
|
+
negative task result, never switches models. Completion metadata reports the
|
|
642
|
+
requested candidate, every attempted candidate, the model actually used, and
|
|
643
|
+
each raw model failure in attempt order when fallbacks are tried.
|
|
632
644
|
|
|
633
645
|
A catalog-listed model and configured authentication do not prove that the
|
|
634
646
|
active provider account can use that model. Providers may reject an account /
|
|
@@ -859,15 +871,19 @@ is no other parent/child protocol.
|
|
|
859
871
|
|
|
860
872
|
`/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.
|
|
861
873
|
|
|
874
|
+
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).
|
|
875
|
+
|
|
862
876
|
---
|
|
863
877
|
|
|
864
878
|
### Explicit worktree cleanup
|
|
865
879
|
|
|
866
880
|
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.
|
|
867
881
|
|
|
868
|
-
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. 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;
|
|
882
|
+
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.
|
|
883
|
+
|
|
884
|
+
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.
|
|
869
885
|
|
|
870
|
-
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.
|
|
886
|
+
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.
|
|
871
887
|
|
|
872
888
|
## Custom Agents
|
|
873
889
|
|
|
@@ -1251,12 +1267,16 @@ npm run lint
|
|
|
1251
1267
|
npm pack --dry-run
|
|
1252
1268
|
```
|
|
1253
1269
|
|
|
1270
|
+
`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.
|
|
1271
|
+
|
|
1254
1272
|
Run the required end-to-end suite from inside Herdr:
|
|
1255
1273
|
|
|
1256
1274
|
```bash
|
|
1257
1275
|
npm run test:integration
|
|
1258
1276
|
```
|
|
1259
1277
|
|
|
1278
|
+
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.
|
|
1279
|
+
|
|
1260
1280
|
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:
|
|
1261
1281
|
|
|
1262
1282
|
```bash
|
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
|
|
|
@@ -223,6 +224,8 @@ The equivalent tools are `worktree_list({})` and `worktree_remove({ target, pres
|
|
|
223
224
|
|
|
224
225
|
Authorization uses **cwd containment**: the canonical source repository root must equal or descend from the canonical session cwd. For linked checkouts, this means the canonical principal/source repository under the invoking parent session's cwd; a successful launch does not itself authorize cleanup, and authorization does not use `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. It does not use the managed checkout's location or require a current-session manifest. Discovery scans `~/.herdr/worktrees/*/*/`, joins Git registration and Herdr workspace state, and includes orphans from ended sessions. Out-of-scope entries are never removable; unregistered residue and failed probes are unknown, not clean. Inventory includes path, branch, source, workspace, Git state, manifest presence, concrete blockers, and separate process-inspection warnings. The managed root, source, and cwd are canonicalized: symlinked ancestors work normally, but a checkout symlink escaping the managed root is blocked.
|
|
225
226
|
|
|
227
|
+
A source repository can be a Git submodule. Its shared Git directory lives under the superproject's `.git/modules`, so cleanup reads `core.worktree` through `git config`, which also follows included configuration files. Cleanup then requires the same top-level and common-directory proofs as for any other source. If the read or a proof fails, the entry is unknown and cannot be removed. Initialized submodules inside a managed checkout still block removal.
|
|
228
|
+
|
|
226
229
|
Removal rechecks containment, Git registration, no detected process holder, known live child, or persistent-specialist lease, and no uncommitted, untracked, or conflicted files. Liveness checks observable same-user processes from any session with a cwd inside the checkout, regardless of runtime name (including thread-suffixed Node names). Only Herdr-confirmed idle retained shell PIDs are exempt; an active runtime at the same PID is not. Detached HEAD, locked checkouts, initialized submodules, and conflicting identity evidence block removal. For initialized submodules, deinitialize them deliberately or use operator removal. No force option is provided.
|
|
227
230
|
|
|
228
231
|
Unreadable individual process details are **warnings, not blockers**, with no override flag. Scanning continues after each unreadable entry, so another observable holder still blocks removal. Unknown processes are not classified as unrelated. Inventory rows expose separate `blockers` and `warnings` arrays; removal results expose `warnings` and retain warnings from earlier inspections, including refusals and preservation/removal failures when available. Human-readable output includes the same warnings, with counts and at most ten sampled PIDs per inspection, not process commands or environments. Pi tool refusals and failures use thrown errors, so their warnings travel in the error message rather than structured tool details.
|
|
@@ -233,7 +236,7 @@ Dirty work requires an explicit `--preserve` or `preserve: true`. This stages al
|
|
|
233
236
|
|
|
234
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.
|
|
235
238
|
|
|
236
|
-
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.
|
|
237
240
|
|
|
238
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.
|
|
239
242
|
|
|
@@ -244,7 +247,8 @@ This first version intentionally does not provide:
|
|
|
244
247
|
- automatic push, PR creation, merge, or cherry-pick
|
|
245
248
|
- automatic worktree or branch removal
|
|
246
249
|
- worktree-aware `subagent_resume`
|
|
247
|
-
- 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
|
|
248
252
|
- dependency DAG scheduling or merge queues
|
|
249
253
|
- stacked-branch management
|
|
250
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,
|
|
@@ -24,9 +24,11 @@ import type {
|
|
|
24
24
|
import {
|
|
25
25
|
getSubagentProcessIdentityFile,
|
|
26
26
|
linuxProcessProbe,
|
|
27
|
+
readLinuxProcessEnvironment,
|
|
27
28
|
readProcessIdentityRecord,
|
|
28
29
|
verifyProcessIdentityRecord,
|
|
29
30
|
type PiProcessIdentity,
|
|
31
|
+
type ProcessEnvironmentReader,
|
|
30
32
|
type ProcessIdentityProbe,
|
|
31
33
|
} from "./process-identity.ts";
|
|
32
34
|
import {
|
|
@@ -141,11 +143,12 @@ export interface ProcessIdentityExpectation {
|
|
|
141
143
|
|
|
142
144
|
type MaybePromise<T> = T | Promise<T>;
|
|
143
145
|
|
|
144
|
-
interface WorktreeSurfaceForLaunch {
|
|
146
|
+
export interface WorktreeSurfaceForLaunch {
|
|
145
147
|
path: string;
|
|
146
148
|
branch: string;
|
|
147
149
|
workspaceId: string;
|
|
148
150
|
paneId: string;
|
|
151
|
+
diagnostics?: string[];
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
export interface PiLaunchOperations {
|
|
@@ -190,30 +193,38 @@ function placementFromPaneConfig(config: PaneConfig) {
|
|
|
190
193
|
function worktreeSurfaceForLaunch(
|
|
191
194
|
worktree: WorktreeSurface,
|
|
192
195
|
): WorktreeSurfaceForLaunch {
|
|
193
|
-
|
|
196
|
+
const surface: WorktreeSurfaceForLaunch = {
|
|
194
197
|
path: worktree.path,
|
|
195
198
|
branch: worktree.branch,
|
|
196
199
|
workspaceId: worktree.workspaceId,
|
|
197
200
|
paneId: worktree.surfaceId,
|
|
198
201
|
};
|
|
202
|
+
if (worktree.diagnostics?.length) surface.diagnostics = worktree.diagnostics;
|
|
203
|
+
return surface;
|
|
199
204
|
}
|
|
200
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Names the launched session in the Pi process's exec-time environment. Pi
|
|
208
|
+
* rewrites its command line, so `--session` is never readiness evidence.
|
|
209
|
+
* Nothing inside Pi reads it.
|
|
210
|
+
*/
|
|
211
|
+
export const PI_LAUNCH_SESSION_ENV = "PI_HERDR_AGENTS_SESSION";
|
|
212
|
+
|
|
201
213
|
export function isExpectedPiProcess(
|
|
202
214
|
process: {
|
|
215
|
+
pid: number;
|
|
203
216
|
name?: string;
|
|
204
217
|
argv0?: string;
|
|
205
|
-
argv?: string[];
|
|
206
218
|
cwd?: string;
|
|
207
219
|
},
|
|
208
220
|
sessionFile: string,
|
|
209
221
|
cwd: string,
|
|
222
|
+
readEnvironment: ProcessEnvironmentReader = readLinuxProcessEnvironment,
|
|
210
223
|
): boolean {
|
|
211
|
-
const sessionIndex = process.argv?.indexOf("--session") ?? -1;
|
|
212
224
|
return (
|
|
213
225
|
(process.name === "pi" || process.argv0?.split("/").pop() === "pi") &&
|
|
214
|
-
|
|
215
|
-
process.
|
|
216
|
-
process.cwd === cwd
|
|
226
|
+
process.cwd === cwd &&
|
|
227
|
+
readEnvironment(process.pid)?.get(PI_LAUNCH_SESSION_ENV) === sessionFile
|
|
217
228
|
);
|
|
218
229
|
}
|
|
219
230
|
|
|
@@ -232,15 +243,17 @@ async function waitForSurfacePiReady(
|
|
|
232
243
|
while (Date.now() <= deadline) {
|
|
233
244
|
try {
|
|
234
245
|
const info = await Promise.resolve(provider.getProcessInfo(surface));
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
246
|
+
// An unreadable process (one that is exiting) proves nothing, but must
|
|
247
|
+
// not hide a matching sibling.
|
|
248
|
+
for (const process of info.foregroundProcesses) {
|
|
249
|
+
try {
|
|
250
|
+
if (isExpectedPiProcess(process, sessionFile, cwd)) return;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
lastError = errorMessage(error);
|
|
253
|
+
}
|
|
241
254
|
}
|
|
242
255
|
} catch (error) {
|
|
243
|
-
lastError =
|
|
256
|
+
lastError = errorMessage(error);
|
|
244
257
|
}
|
|
245
258
|
if (Date.now() >= deadline) break;
|
|
246
259
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
@@ -636,6 +649,7 @@ async function prepareLaunchSurface(
|
|
|
636
649
|
baseSha,
|
|
637
650
|
manifestFile,
|
|
638
651
|
};
|
|
652
|
+
if (created.diagnostics?.length) worktree.diagnostics = created.diagnostics;
|
|
639
653
|
worktreeOps.writeWorktreeManifest(manifestFile, {
|
|
640
654
|
state: "provisioned",
|
|
641
655
|
...ownership,
|
|
@@ -857,7 +871,7 @@ function buildPiCommand(
|
|
|
857
871
|
}
|
|
858
872
|
}
|
|
859
873
|
|
|
860
|
-
const env
|
|
874
|
+
const env = [`${PI_LAUNCH_SESSION_ENV}=${shellQuote(artifacts.sessionFile)}`];
|
|
861
875
|
if (artifacts.localAgentDir) {
|
|
862
876
|
env.push(`PI_CODING_AGENT_DIR=${shellQuote(artifacts.localAgentDir)}`);
|
|
863
877
|
} else if (process.env.PI_CODING_AGENT_DIR) {
|
|
@@ -123,6 +123,44 @@ export function createLinuxProcessProbe(
|
|
|
123
123
|
|
|
124
124
|
export const linuxProcessProbe = createLinuxProcessProbe();
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* A process's environment as it was at exec. Unlike its command line, Pi's
|
|
128
|
+
* process-title rewrite leaves this intact. `undefined` only when no such PID
|
|
129
|
+
* exists; throws on non-Linux hosts and any other read failure.
|
|
130
|
+
*/
|
|
131
|
+
export type ProcessEnvironmentReader = (
|
|
132
|
+
pid: number,
|
|
133
|
+
) => Map<string, string> | undefined;
|
|
134
|
+
|
|
135
|
+
export function createLinuxProcessEnvironmentReader(
|
|
136
|
+
read: (path: string) => string = (path) => readFileSync(path, "utf8"),
|
|
137
|
+
): ProcessEnvironmentReader {
|
|
138
|
+
return (pid) => {
|
|
139
|
+
if (process.platform !== "linux")
|
|
140
|
+
throw new Error(
|
|
141
|
+
`process environment is unreadable on ${process.platform}; only Linux /proc is read`,
|
|
142
|
+
);
|
|
143
|
+
let text: string;
|
|
144
|
+
try {
|
|
145
|
+
text = read(`/proc/${pid}/environ`);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
const code = error instanceof Error ? errnoCode(error) : undefined;
|
|
148
|
+
if (code === "ENOENT" || code === "ESRCH") return undefined;
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
const environment = new Map<string, string>();
|
|
152
|
+
for (const entry of text.split("\0")) {
|
|
153
|
+
const separator = entry.indexOf("=");
|
|
154
|
+
if (separator > 0 && !environment.has(entry.slice(0, separator)))
|
|
155
|
+
environment.set(entry.slice(0, separator), entry.slice(separator + 1));
|
|
156
|
+
}
|
|
157
|
+
return environment;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const readLinuxProcessEnvironment =
|
|
162
|
+
createLinuxProcessEnvironmentReader();
|
|
163
|
+
|
|
126
164
|
/** The calling process's identity; throws when this host cannot establish it. */
|
|
127
165
|
export function readOwnProcessIdentity(
|
|
128
166
|
probe: ProcessIdentityProbe = linuxProcessProbe,
|
package/maestro/core/activity.ts
CHANGED