pi-herdr-agents 3.0.1 → 3.0.2

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/CHANGELOG.md CHANGED
@@ -7,7 +7,13 @@ 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.1](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.0...v3.0.1)
10
+ ## [v3.0.2](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.1...v3.0.2)
11
+
12
+ ### Merged
13
+
14
+ - fix(subagents): resolve submodule sources and refresh fallback context [`#76`](https://github.com/giuseppecrj/pi-herdr-agents/pull/76)
15
+
16
+ ## [v3.0.1](https://github.com/giuseppecrj/pi-herdr-agents/compare/v3.0.0...v3.0.1) - 2026-10-06
11
17
 
12
18
  ### Merged
13
19
 
@@ -16,6 +22,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
16
22
  ### Commits
17
23
 
18
24
  - 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)
25
+ - chore: release v3.0.1 [`ba349db`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ba349db4a4280a39b70a067fb62e7816f9a2061f)
19
26
  - docs: hero image uses generic agent labels instead of removed role names [`70ad6f0`](https://github.com/giuseppecrj/pi-herdr-agents/commit/70ad6f04d492f58e04062e01c1a529ddd1708431)
20
27
  - docs: address review: full write footprint, worktree cleanup before uninstall, skill table delimiter [`ababee3`](https://github.com/giuseppecrj/pi-herdr-agents/commit/ababee3a3f18d2112ec8959303986e1a08e3dc2a)
21
28
 
package/README.md CHANGED
@@ -625,10 +625,17 @@ example `provider/preferred, provider/fallback`. The tool argument also accepts
625
625
  candidate before launch, then launches later candidates only after the selected
626
626
  child settles with a provider/agent error. Pi owns any automatic transient
627
627
  retrying inside that child; the extension does not infer retry counts or
628
- permanence from the error text. A completed child result, including a negative
629
- task result, never switches models. Completion metadata reports the requested
630
- candidate, every attempted candidate, the model actually used, and each raw
631
- model failure in attempt order when fallbacks are tried.
628
+ permanence from the error text. A later candidate that launches after a parent
629
+ `/reload`, `/new`, `/resume`, or `/fork` uses the live parent session for its
630
+ artifacts and lineage, as completion delivery does. Its candidate list and
631
+ thinking level stay those of the original call. The original parent directory
632
+ and process directory remain the bases for directory resolution. Role files are
633
+ read again for each attempt, so a changed role `cwd` can redirect a fallback
634
+ when the tool call did not specify `cwd`. If no live parent context is available,
635
+ that candidate fails without launching. A completed child result, including a
636
+ negative task result, never switches models. Completion metadata reports the
637
+ requested candidate, every attempted candidate, the model actually used, and
638
+ each raw model failure in attempt order when fallbacks are tried.
632
639
 
633
640
  A catalog-listed model and configured authentication do not prove that the
634
641
  active provider account can use that model. Providers may reject an account /
@@ -865,7 +872,7 @@ is no other parent/child protocol.
865
872
 
866
873
  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
874
 
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; 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.
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; 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.
869
876
 
870
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.
871
878
 
@@ -223,6 +223,8 @@ The equivalent tools are `worktree_list({})` and `worktree_remove({ target, pres
223
223
 
224
224
  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
225
 
226
+ 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.
227
+
226
228
  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
229
 
228
230
  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.
@@ -11,7 +11,7 @@ import {
11
11
  unlinkSync,
12
12
  } from "node:fs";
13
13
  import { homedir } from "node:os";
14
- import { dirname, isAbsolute, join, relative, sep } from "node:path";
14
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
15
15
  import { isRecord, type JsonObject } from "../core/config/type-guards.ts";
16
16
  import type { SurfaceProvider } from "../core/surface-provider.ts";
17
17
  import type {
@@ -245,7 +245,19 @@ function resolveSource(path: string): string {
245
245
  "--git-common-dir",
246
246
  ]).trim(),
247
247
  );
248
- const root = realpathSync(dirname(common));
248
+ // A submodule's common directory lives under the superproject's
249
+ // .git/modules; only core.worktree, including included config, names its checkout.
250
+ const configured = git(common, [
251
+ `--git-dir=${common}`,
252
+ "config",
253
+ "--default",
254
+ "",
255
+ "--get",
256
+ "core.worktree",
257
+ ]).trim();
258
+ const root = realpathSync(
259
+ configured ? resolve(common, configured) : dirname(common),
260
+ );
249
261
  if (realpathSync(git(root, ["rev-parse", "--show-toplevel"]).trim()) !== root)
250
262
  throw new Error("Cannot prove source repository root");
251
263
  if (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-herdr-agents",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Asynchronous Pi subagents in Herdr, with optional isolated Git worktrees",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1950,10 +1950,6 @@ function startWidgetRefresh() {
1950
1950
  writeGlobalSlot(WIDGET_INTERVAL_KEY, widgetInterval);
1951
1951
  }
1952
1952
 
1953
- /**
1954
- * Normalize each actual attempt against its invocation context. Role files may
1955
- * change between attempts; the validated runtime candidates do not.
1956
- */
1957
1953
  function normalizePiAttempt(
1958
1954
  params: typeof SubagentParams.static,
1959
1955
  ctx: {
@@ -1975,6 +1971,7 @@ function normalizePiAttempt(
1975
1971
  options: {
1976
1972
  id: string;
1977
1973
  controlTaskId: string;
1974
+ origin: Pick<PiLaunchSnapshot["parent"], "cwd" | "invocationCwd">;
1978
1975
  },
1979
1976
  ): PiAttemptSnapshot {
1980
1977
  const agentDefs = params.agent
@@ -1999,15 +1996,19 @@ function normalizePiAttempt(
1999
1996
  const taskId = randomUUID();
2000
1997
  const parentSessionFile = ctx.sessionManager.getSessionFile();
2001
1998
  if (!parentSessionFile) throw new Error("No session file");
1999
+ const snapshot = launchSnapshot(ctx, parentThinking);
2002
2000
 
2003
2001
  return {
2004
- snapshot: launchSnapshot(ctx, parentThinking),
2002
+ snapshot: {
2003
+ ...snapshot,
2004
+ parent: { ...snapshot.parent, ...options.origin },
2005
+ },
2005
2006
  task: {
2006
2007
  id: options.controlTaskId,
2007
2008
  name: params.name,
2008
2009
  prompt: params.task,
2009
2010
  role: params.agent ?? "",
2010
- cwd: ctx.cwd,
2011
+ cwd: options.origin.cwd,
2011
2012
  worktree: params.worktree ?? undefined,
2012
2013
  session: { mode: resolveEffectiveSessionMode(params, agentDefs) },
2013
2014
  behavior: {
@@ -2792,20 +2793,24 @@ export default function subagentsExtension(
2792
2793
  );
2793
2794
  runtime.latestCtx = ctx;
2794
2795
  const id = randomUUID();
2795
- const first = normalizePiAttempt(params, ctx, parentThinking, {
2796
+ const attempt = {
2796
2797
  id,
2797
2798
  controlTaskId: id,
2798
- });
2799
+ origin: { cwd: ctx.cwd, invocationCwd: process.cwd() },
2800
+ };
2801
+ const first = normalizePiAttempt(params, ctx, parentThinking, attempt);
2799
2802
  const [plan, ...fallbacks] = runtimePlans;
2800
2803
  if (!plan) throw new Error("No resolved runtime plans");
2801
2804
  const input: PiLaunchInput = {
2802
2805
  ...first,
2803
2806
  plans: [plan, ...fallbacks],
2804
- prepareAttempt: () =>
2805
- normalizePiAttempt(params, ctx, parentThinking, {
2806
- id,
2807
- controlTaskId: id,
2808
- }),
2807
+ // Fallbacks outlive this tool call; session replacement invalidates ctx.
2808
+ prepareAttempt: () => {
2809
+ const live = runtime.latestCtx;
2810
+ if (!live)
2811
+ throw new Error("No live parent context for the fallback launch");
2812
+ return normalizePiAttempt(params, live, parentThinking, attempt);
2813
+ },
2809
2814
  };
2810
2815
  const session = runtime.session!;
2811
2816
  const handle = await session.spawnPi(input);