pi-squire 0.9.4 → 0.10.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/CHANGELOG.md CHANGED
@@ -6,6 +6,100 @@ follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.0] - 2026-10-24
10
+
11
+ ### Added
12
+
13
+ - `/delegate --worktree-source <dir>` and the `worktree_source` input select another existing Git checkout as the frozen base and trust identity for managed worktrees. Unresolved protected resources in that planned checkout require the parent's decision before creation.
14
+
15
+ - `/delegate --worktree-base <commit-ish>` and the `worktree_base` tool input create managed branches from an explicitly resolved local commit. Resolution is frozen, never fetches, and supports valid explicit commits from an unborn source checkout.
16
+
17
+ - Managed worktree placement can be configured per call in
18
+ `<agent dir>/squire/settings.json` with a global `worktreeRoot` template and
19
+ canonical-main-repository `worktreeRootByRepo` overrides. Placement settings
20
+ affect only worktree creation; inherited and `cwd` launches never read them.
21
+ - `/delegate --worktree <branch>` and the `delegate` tool's `worktree` input
22
+ create a managed worktree from the source checkout's frozen committed HEAD,
23
+ in the built-in sibling `{repo}-worktrees/{branch}` layout. Source dirty
24
+ paths are summarized but never copied. Launch attempts are journaled before
25
+ creation; known pre-window failures roll back only a proven pristine checkout
26
+ and branch, while uncertain or changed resources are retained with recovery
27
+ facts. Once a window opens, the workspace is retained. Managed workspace
28
+ facts appear in results, status and expanded rendering.
29
+ - The `session:launch` payload gains a read-only `cwd`, the directory the
30
+ delegate's window opens in. The window opens there whatever a listener
31
+ assigns; appending args and adding env keys work as before. The README
32
+ example and its test read it.
33
+ - Each delegation records its working directory and session-storage
34
+ directory. The `delegate` tool's structured result carries `cwd` and
35
+ `sessionDir`; `delegation_status`, result and closed-without-a-result
36
+ messages and their expanded views show the working directory, and older
37
+ records show it as `unknown (not recorded)`.
38
+
39
+ - `--cwd <dir>` on `/delegate` and `cwd` on the `delegate` tool run a
40
+ delegate in an existing directory: relative to the parent's, `~`/`~/`
41
+ expanded, symlinks resolved, refused before anything starts when it is
42
+ missing, a file or cannot be entered. The directory's branch, index and
43
+ files are never touched or cleaned up. Placeholder values (`null`, `""`,
44
+ `"null"`, `"none"`, `"false"`, `"undefined"`) mean the parent's directory;
45
+ other non-string values are refused. The delegate's prompt names the
46
+ selected directory, and the start message names it when it differs.
47
+ - Project trust for a `--cwd` directory is decided in the parent: the parent's
48
+ current trust inside its own repository (by Git common directory), else the
49
+ saved decision for a linked worktree's main repository or for the directory,
50
+ else `--no-approve` when it has no protected resources, else a parent
51
+ dialog with Pi's five choices. Without a UI, after a cancel or an abort, or
52
+ when the trust store cannot be read or written, nothing starts. Dialogs are
53
+ asked one at a time.
54
+ - Each new delegation records a `workspace` (mode, cwd and Git checkout at
55
+ launch: identity, branch or detached, launch commit, or non-Git, or unknown
56
+ with a reason). The `delegate` tool's structured result, `delegation_status`,
57
+ result and closed-without-a-result messages and their expanded views carry
58
+ it; result and closed text gain a `Workspace:` line, and status text a
59
+ `workspace:` line. The pinned record, result-text and completion tests
60
+ change with it.
61
+
62
+ - Results and `delegation_status` report a snapshot of the delegation's
63
+ checkout (`workspaceSnapshot`): branch or detached HEAD, head, launch
64
+ commit, commits since launch, committed paths since launch, and staged,
65
+ unstaged, untracked and conflicted paths, or non-Git, or unknown with a
66
+ reason. A result keeps the snapshot taken when its reply arrived; status
67
+ takes a fresh one each call. Lists are capped at 100 paths and 16 KiB with
68
+ totals; Git gets 2 s per command, 5 s and 4 MiB per checkout, runs without
69
+ optional index writes, and is asked only about the recorded checkout. A
70
+ snapshot is an observation of the Git state at the recorded location, not
71
+ proof of the same repository on disk, attribution or merge advice. Result text
72
+ gains `Checkout when the result arrived:` lines and status text
73
+ `checkout now:` lines; the pinned result entry, details and result-text
74
+ tests change with it.
75
+
76
+ ### Changed
77
+
78
+ - `/delegate` flag values may be double-quoted, with `\"` and `\\` escapes,
79
+ so a path with spaces works; the task after the flags is kept byte for
80
+ byte. A missing closing quote is refused before anything starts.
81
+ - Every delegate is launched with an explicit `--approve` or `--no-approve`
82
+ from the parent's current project trust, so a global
83
+ `defaultProjectTrust` cannot trust a project the parent does not.
84
+ - Every delegate is launched with `--session-dir` set to the parent's absolute
85
+ session-storage directory. Session lookups for running, closed and late
86
+ results use the directory recorded at launch, so they survive a parent that
87
+ resumes with another storage directory, and a found session path is kept
88
+ in the closed-without-a-result record. A parent whose session directory is
89
+ not absolute refuses to launch before sending the task. The launch argv and
90
+ record tests change with it.
91
+
92
+ ## [0.9.5] - 2026-10-23
93
+
94
+ ### Changed
95
+
96
+ - A delegation is drawn as one tool call that changes state, in the style of
97
+ pi-interactive-subagents, replacing the 0.9.2 to 0.9.4 look: the `delegate`
98
+ row shows `○` with the agent, label and task while the call runs, and one
99
+ header, `● <name> (<model>) — started`, once the delegate starts; the result arrives as a
100
+ finished tool row on Pi's tool success or error background, `✓ <name> (<model>) — done` or `✗` with the status,
101
+ with a 5-line preview. The renderer tests change with it.
102
+
9
103
  ## [0.9.4] - 2026-10-23
10
104
 
11
105
  ### Changed
@@ -364,7 +458,9 @@ author's dotfiles; the changes below are against that copy.
364
458
 
365
459
  - The published file list includes `child.ts`, which `index.ts` imports.
366
460
 
367
- [Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.9.4...HEAD
461
+ [Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.10.0...HEAD
462
+ [0.10.0]: https://github.com/gvanderclay/pi-squire/compare/v0.9.5...v0.10.0
463
+ [0.9.5]: https://github.com/gvanderclay/pi-squire/compare/v0.9.4...v0.9.5
368
464
  [0.9.4]: https://github.com/gvanderclay/pi-squire/compare/v0.9.3...v0.9.4
369
465
  [0.9.3]: https://github.com/gvanderclay/pi-squire/compare/v0.9.2...v0.9.3
370
466
  [0.9.2]: https://github.com/gvanderclay/pi-squire/compare/v0.9.1...v0.9.2
package/README.md CHANGED
@@ -75,12 +75,13 @@ closes its own window ([auto-exit](#auto-exit)).
75
75
 
76
76
  ## How a delegation works
77
77
 
78
- `/delegate <agent> [--model <provider/id>] [--thinking <level>] [--label <name>] [--auto-exit | --no-auto-exit] <task…>`
78
+ `/delegate <agent> [--model <provider/id>] [--thinking <level>] [--label <name>] [--cwd <dir> | --worktree <branch> [--worktree-source <dir>] [--worktree-base <commit-ish>]] [--auto-exit | --no-auto-exit] <task…>`
79
79
  reads the agent from `<agent dir>/squire/agents/<name>/AGENT.md`, writes
80
80
  the task to the delegate's inbox through `message:send`, emits
81
81
  `session:launch` so other extensions can shape the launch, then opens a
82
- detached window running the parent's own Pi in the parent's working directory
83
- and agent root. The agent's
82
+ detached window running the parent's own Pi in the parent's working directory,
83
+ or the directory `--cwd` selects or the new `--worktree` checkout creates (see [Working directory](#working-directory)),
84
+ with the parent's agent root. The agent's
84
85
  prompt reaches the child as a path to a private file under the system
85
86
  temporary folder, not as text on the command line.
86
87
 
@@ -103,15 +104,230 @@ temporary folder, not as text on the command line.
103
104
  shown to the parent as one result message that quotes the task. By default
104
105
  the delegate then closes its own window ([auto-exit](#auto-exit)). With
105
106
  auto-exit off the window stays open so you can read it or keep working in it.
106
- - Delegations are recorded in the parent session without the task text.
107
+ - Delegations are recorded in the parent session without the task text. Each
108
+ record keeps the directory the window opened in and the session-storage
109
+ directory the delegate was given.
110
+ - The delegate always gets an explicit project-trust flag, `--approve` or
111
+ `--no-approve`, so a permissive global `defaultProjectTrust` cannot trust a
112
+ project the parent does not, and an unattended window never waits on a
113
+ trust question. In the parent's own directory it is the parent's current
114
+ trust (`ctx.isProjectTrusted()`); for a `--cwd` directory see
115
+ [Project trust](#project-trust).
116
+ - The delegate always gets `--session-dir` with the parent's absolute
117
+ session-storage directory, which outranks `PI_CODING_AGENT_SESSION_DIR`, the
118
+ tmux server's environment and a project's `sessionDir` setting. The parent
119
+ finds the delegate's session file there by its id, even after the parent
120
+ resumes with another storage directory selected. A parent whose session
121
+ directory is not an absolute path refuses to launch, before sending the
122
+ task.
107
123
  - Inside a delegate (`PI_DELEGATE_PARENT` set) the extension registers only
108
124
  auto-exit's side, the `/auto-exit` command and its handlers, so a delegate
109
125
  cannot start delegates of its own.
110
- - Without a UI the command and the `delegate` tool still work. They need tmux
126
+ - Without a UI the command and the `delegate` tool still work, except a
127
+ `--cwd` launch whose project trust needs a question (see
128
+ [Project trust](#project-trust)). They need tmux
111
129
  and a provider of `message:*`, and refuse with a message when either is
112
130
  missing. In print mode (`pi -p`) Pi does not show the refusal notices, so
113
131
  the refusal is silent there.
114
132
 
133
+ ## Working directory
134
+
135
+ `--cwd <dir>` (the `delegate` tool's `cwd`) runs the delegate in an existing
136
+ directory instead of the parent's: another checkout, a linked worktree, a
137
+ subdirectory or a directory outside Git.
138
+
139
+ - A relative path resolves against the parent's working directory. A leading
140
+ `~` or `~/` means your home directory. Nothing else is expanded: no
141
+ environment variables, globs or `~user`.
142
+ - The directory must exist and be readable. Its real path, with symlinks
143
+ resolved, is where the window opens and what is recorded. A missing path,
144
+ a file or a directory that cannot be entered is refused before the task is
145
+ sent or a window opens; nothing is created.
146
+ - The directory is used as it is. Its branch, index, uncommitted and
147
+ untracked files are not touched when the delegate starts, fails to start or
148
+ is closed, and pi-squire never cleans it up.
149
+ - The delegate's Pi finds context files (`AGENTS.md`), project settings,
150
+ skills and extensions from that directory and its ancestors, as Pi always
151
+ does. Context files load even without project trust, so trust is not an
152
+ instruction-safety boundary.
153
+ - The delegate's appended prompt names the selected directory. A delegate in
154
+ the parent's own directory is not told its cwd again.
155
+ - The model may leave `cwd` out, or send `null`, `""`, `"null"`, `"none"`,
156
+ `"false"` or `"undefined"`; each means the parent's directory. A boolean,
157
+ number, array or object is refused. `/delegate --cwd` treats those same
158
+ strings the same way.
159
+ - Write a value with spaces in double quotes:
160
+ `/delegate scout --cwd "~/work/my repo" Read the README`. Inside the quotes
161
+ `\"` is a quote and `\\` a backslash; there is no other escaping or shell
162
+ expansion. The task after the flags is kept exactly as typed, quotes and
163
+ backslashes included. A missing closing quote or a missing value is refused
164
+ before anything starts.
165
+
166
+ Each new delegation records a `workspace`: its `mode` (`inherited` for the
167
+ parent's directory, `existing` for `--cwd`), its `cwd` and the Git checkout at
168
+ launch. `git` is one of:
169
+
170
+ - `{ kind: "git", identity: { mainRoot, commonDir, checkoutRoot }, position, head, baseline }`,
171
+ where `position` is `{ branch, detached: false }` or
172
+ `{ branch: null, detached: true }` and `baseline` is the commit checked out
173
+ at launch (`null` in a repository with no commits yet);
174
+ - `{ kind: "non-git" }` when Git says the directory is not in a checkout;
175
+ - `{ kind: "unknown", reason }` when Git is missing, times out (5 seconds per
176
+ question) or fails. That never blocks a launch and is not a claim that the
177
+ directory is outside Git.
178
+
179
+ Git is asked read-only, without optional index locks. The start message
180
+ names the directory when it differs from the parent's; the tool's
181
+ structured result always carries `cwd` and `workspace`, and status, results
182
+ and their expanded views show them.
183
+
184
+ ### Managed worktrees
185
+
186
+ `--worktree <branch>` (or the `worktree` tool input) creates a new local branch
187
+ and worktree from this session's checkout, or from the existing checkout named
188
+ by `--worktree-source <dir>` (tool input `worktree_source`). Relative sources
189
+ resolve from this session's cwd; `~`, symlinks, linked worktrees and repository
190
+ subdirectories are accepted. The delegate starts at the created root. Its base
191
+ is the selected checkout's committed `HEAD`, or `--worktree-base <commit-ish>`
192
+ (the tool input `worktree_base`). The base resolves only against locally available branches, remote-tracking refs, tags, commits or commit-ish expressions; Squire does not fetch. The exact commit is frozen before creation. An explicit commit works even if source `HEAD` is unborn; without an override an unborn `HEAD` is refused. The default
193
+ location is a sibling directory named `{repo}-worktrees/{branch}`, with `/` in
194
+ the folder segment replaced by `-`; the branch name itself is unchanged.
195
+ `worktree_source` and `worktree_base` require `worktree`; `cwd` and `worktree` cannot be combined. An unrelated source repository uses its own saved trust decision; when undecided, protected resources in the frozen base and applicable destination ancestor skills are reviewed in the parent before creation. Branches and destinations are never
196
+ reused, reset, suffixed or force-created. Git creation uses the frozen commit,
197
+ not a later moving `HEAD`.
198
+
199
+ Dirty staged, unstaged and untracked source paths are summarized in the start
200
+ notice and recorded; none are copied. Ignored files remain in the source and
201
+ are not transferred. No setup or submodule initialization is performed.
202
+ The delegate starts at the created worktree root with factual branch and base
203
+ context.
204
+
205
+ Managed placement is selected on every creation from the optional
206
+ `<agent dir>/squire/settings.json` file (separate from Pi's `settings.json`):
207
+
208
+ ```json
209
+ {
210
+ "worktreeRoot": "~/worktrees/{repo}/{branch}",
211
+ "worktreeRootByRepo": {
212
+ "/Users/me/src/special-project": "../special-checkouts/{branch}"
213
+ }
214
+ }
215
+ ```
216
+
217
+ A matching `worktreeRootByRepo` entry wins over `worktreeRoot`, which wins
218
+ over the built-in `{repoParent}/{repo}-worktrees/{branch}` default. Repository
219
+ keys identify the canonical main checkout root, even when creation starts from
220
+ a linked worktree; keys and relative templates resolve from the settings
221
+ directory, and `~`/`~/` expands to the home directory. Templates accept
222
+ `{repoParent}` (the main checkout's parent), `{repo}` (its directory name) and
223
+ `{branch}` (the branch with `/` replaced by `-` for this path only). For a
224
+ main checkout at `/Users/me/src/special-project` and branch `feature/topic`,
225
+ the built-in path is
226
+ `/Users/me/src/special-project-worktrees/feature-topic`. With `~` set to
227
+ `/Users/me`, the global example resolves to
228
+ `/Users/me/worktrees/special-project/feature-topic`; if the settings directory
229
+ is `/Users/me/.pi/agent/squire`, the matching relative override resolves to
230
+ `/Users/me/.pi/agent/special-checkouts/feature-topic`. The Git branch remains
231
+ `feature/topic`.
232
+
233
+ The settings file is read only for managed creation. A missing file uses the
234
+ built-in default; malformed or inaccessible settings, invalid templates and
235
+ duplicate canonical repository keys refuse creation rather than silently
236
+ falling back. They do not affect inherited or `cwd` launches. Placement does
237
+ not choose a source checkout or base commit. Pi discovers `AGENTS.md`, skills
238
+ and other project context from the created directory and its ancestors, so the
239
+ chosen layout determines which instructions and skills apply. For a layout
240
+ inside another checkout, arrange any needed Git ignore rule yourself;
241
+ pi-squire never edits `.gitignore`.
242
+
243
+ A launch attempt is recorded before Git creation. If launch fails before a
244
+ window is known to have opened, pi-squire removes only a checkout and branch
245
+ it can prove it created and that remain pristine; it never uses force. Changed,
246
+ ignored, untracked or uncertain resources are retained and reported with
247
+ recovery facts. A timeout or other ambiguous tmux failure retains the worktree.
248
+ Once tmux returns a window id, the worktree and branch are kept through later
249
+ failure, completion and close; remove them manually when no longer needed.
250
+ Worktrees isolate checkouts, not delegate permissions or filesystem access.
251
+
252
+ ### Checkout snapshots
253
+
254
+ A result records one snapshot of the delegation's checkout, taken when the
255
+ reply arrives, and `delegation_status` takes a fresh one on every call. The
256
+ result's snapshot is history: a later status never changes it. Both are a
257
+ `workspaceSnapshot`, one of:
258
+
259
+ - `{ kind: "git", position, head, baseline, ahead, workingState, committed, pending }`:
260
+ the current branch or detached HEAD, the head commit, the launch commit
261
+ (`baseline`), how many commits are reachable from head but not from the
262
+ baseline, `clean` or `dirty`, the paths changed between baseline and head
263
+ (`committed`), and the `pending` paths, split into `staged`, `unstaged`,
264
+ `untracked` and `conflicted`. A clean working tree can still have commits
265
+ and committed paths since launch;
266
+ - `{ kind: "non-git" }` for a directory launched outside Git that still exists;
267
+ - `{ kind: "unknown", reason }` when the directory is gone, its checkout root
268
+ or Git common directory is no longer at the location recorded at launch, its
269
+ launch commit is missing, Git is missing,
270
+ fails or passes a limit, its Git state at launch was unknown, or the record
271
+ predates workspaces. Only the recorded checkout is inspected, never the
272
+ parent's.
273
+
274
+ Each path list is `{ paths, total, truncated }`: at most 100 whole paths and
275
+ 16 KiB of names per list, with `total` counting all of them. Paths come from
276
+ NUL-separated Git output and are shown JSON-quoted, so spaces and newlines in
277
+ a name stay data; a rename lists both its old and new path. An inspection
278
+ gives each Git command 2 seconds, the whole checkout 5 seconds and 4 MiB of
279
+ output; a command still running at its limit is killed. Past any of those
280
+ limits the snapshot is unknown, never a partial count, and stdout and stderr
281
+ count together toward the 4 MiB. Committed paths are relative to the
282
+ repository root whatever `diff.relative` says. Git runs read-only, without
283
+ optional index writes.
284
+
285
+ A snapshot observes the Git state at the recorded location, not that it is the
286
+ same repository on disk: a repository replaced there, for example by a fresh
287
+ clone, that still has the launch commit is reported normally against that
288
+ commit. It is not proof that the delegate made those changes, that the task
289
+ succeeded, or that the branch is safe to merge.
290
+ It does not say whether the branch is merged anywhere, prints no cleanup
291
+ command, and nothing acts on it.
292
+
293
+ ### Project trust
294
+
295
+ The delegate's trust flag for a `--cwd` directory is decided in the parent,
296
+ before the task is sent or a window opens. A same-repository managed worktree
297
+ uses the parent's current trust for that repository. The model has no trust input.
298
+
299
+ 1. A directory in the parent's own repository, by Git's common directory, so
300
+ including its linked worktrees, gets the parent's current trust, even a
301
+ decision the user made for this session only. It never carries to another
302
+ repository or to a directory outside Git.
303
+ 2. Otherwise a saved decision in Pi's trust store applies, found the way Pi
304
+ finds it (the closest saved directory at or above the key). The key is the
305
+ main repository root for a linked worktree of another repository, and the
306
+ directory itself for everything else, including a subdirectory of another
307
+ main checkout or a main checkout whose Git directory lives elsewhere
308
+ (`--separate-git-dir`), so a nearer saved denial there still wins. A
309
+ linked worktree whose main checkout Git cannot name, such as one of a bare
310
+ repository, has an unknown Git state and is keyed by its own directory.
311
+ 3. With no saved decision and no project resources that Pi gates behind trust
312
+ (`.pi/settings.json`, extensions, skills and the like, or an
313
+ `.agents/skills` directory above it), the delegate starts with
314
+ `--no-approve` and no question.
315
+ 4. Otherwise the parent asks, naming the directory, the main repository when
316
+ it is the key, and what each answer saves:
317
+ `Trust`, `Trust parent folder (<folder>)` (left out at the filesystem
318
+ root), `Trust (this session only)`, `Do not trust` and
319
+ `Do not trust (this session only)`. The two "this session only" answers
320
+ apply to this launch alone and save nothing. "Trust parent folder" saves
321
+ the folder and clears a decision saved for the key itself. Cancelling
322
+ starts nothing. One question is open at a time; a question that waited
323
+ reads the store again, so an answer just saved is not asked twice. Aborting
324
+ the turn or tool call stops a launch that is waiting for or answering a
325
+ question; nothing is saved, sent or opened.
326
+
327
+ Without a UI a question cannot be asked, so the launch is refused: open Pi in
328
+ that directory and answer its trust question first. A trust store that cannot
329
+ be read or written also refuses the launch; nothing is granted by default.
330
+
115
331
  ## Agents
116
332
 
117
333
  The roster is read at call time from
@@ -196,7 +412,7 @@ settings file and no environment variable of its own. `PI_DELEGATE_PARENT` and `
196
412
  for the child, never read as configuration by the parent. The window starts
197
413
  with the tmux server's environment plus only these from the parent:
198
414
  `PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR` (when the parent has it
199
- set), `PI_DELEGATE_PARENT` and `PI_DELEGATE_AUTO_EXIT`, and it always gets `PI_UNATTENDED=1`.
415
+ set; `--session-dir` outranks it), `PI_DELEGATE_PARENT` and `PI_DELEGATE_AUTO_EXIT`, and it always gets `PI_UNATTENDED=1`.
200
416
  `PI_UNATTENDED=1` is a general signal, not specific to pi-squire: no human is
201
417
  watching this session, even though it runs in a TUI. Other extensions may read
202
418
  it to skip a dialog or confirmation instead of waiting on one, and any other
@@ -217,7 +433,7 @@ session's delegation records. None of them is registered inside a delegate.
217
433
 
218
434
  | Tool | Parameters | What it does |
219
435
  | --- | --- | --- |
220
- | `delegate` | `agent`, `task`, optional `model`, `thinking`, `label` and `auto_exit` (boolean) | Validates the call the way the command does, starts the delegation without opening a dialog, and returns the delegation id at once without waiting for a result. |
436
+ | `delegate` | `agent`, `task`, optional `model`, `thinking`, `label`, `cwd` and `auto_exit` (boolean) | Validates the call the way the command does, starts the delegation without opening a dialog (except an unresolved [trust question](#project-trust) for `cwd`), and returns the delegation id at once without waiting for a result. |
221
437
  | `delegation_status` | optional `id` | Reports each delegation as running, done, closed or closed without a result, with the window, the delegate's session and the result envelope's path. |
222
438
  | `delegation_close` | `id` | Kills the window through tmux and records the close, so `delegation_status` reports it closed. |
223
439
 
@@ -253,6 +469,14 @@ nothing starts.
253
469
  name: scout-<first 8 of the id>
254
470
  state: running — the window is open and no result has arrived (last active 12 min ago)
255
471
  window: scout-<first 8 of the id> (@1)
472
+ cwd: <the directory its window opened in>
473
+ workspace: existing directory; Git checkout <main root> (linked worktree <checkout>), branch <name>, launch commit <sha>
474
+ checkout now: branch <name>, head <sha>, launch commit <sha>, 1 commit since launch, working tree dirty
475
+ committed since launch: 1 path: "src/a.ts"
476
+ staged: none
477
+ unstaged: 1 path: "README.md"
478
+ untracked: none
479
+ conflicted: none
256
480
  auto-exit: on — the delegate closes its window after a normal completion unless the user took over there
257
481
  session: <the delegate's session file, or its id when there is none yet>
258
482
 
@@ -260,6 +484,8 @@ nothing starts.
260
484
  name: researcher-<first 8 of the id>
261
485
  state: done — result status: done; the result reaches this session as a [delegate] message on its own
262
486
  window: researcher-<first 8 of the id> (@2)
487
+ cwd: unknown (not recorded)
488
+ checkout now: Git state unknown (this delegation was recorded before its launch checkout was kept)
263
489
  auto-exit: off — the window stays open after the result
264
490
  session: <session file>
265
491
  envelope: <the reply's path in the parent's cur/>
@@ -279,6 +505,19 @@ nothing starts.
279
505
  arrived before its window went. For a moment before the result is claimed
280
506
  it can read closed, since the window is already gone.
281
507
  - **Auto-exit** reads off for delegations recorded before auto-exit existed.
508
+ - **Cwd** is the directory the delegate's window opened in. A delegation
509
+ recorded before it was kept reads `unknown (not recorded)`, never the
510
+ parent's current directory.
511
+ - **Workspace** says how the directory was chosen and its Git checkout at
512
+ launch (see [Working directory](#working-directory)); older records have
513
+ none.
514
+ - **Checkout now** is a fresh [snapshot](#checkout-snapshots) of that
515
+ checkout, taken by this call; the details carry it as `workspaceSnapshot`.
516
+ A failure is unknown for that delegation alone.
517
+ - **Session** is the delegate's session file, searched for in the storage
518
+ directory recorded at launch. Once found, its path is kept, so a later
519
+ status or result uses it without searching again. Before the delegate has
520
+ written its file, the id is shown instead of an invented file name.
282
521
  - **Closed** covers a close `delegation_close` recorded and a window that is no
283
522
  longer there. A recorded close wins over a result, and a result wins over a
284
523
  window that is gone.
@@ -407,7 +646,7 @@ The parent resolves each delegation's auto-exit from the call
407
646
  - While it is on, a run that settles as a normal completion shuts the delegate
408
647
  down (`ctx.shutdown()`) on the next event-loop turn, after the settle
409
648
  reply has been sent, so its tmux window closes. The session file stays, and
410
- `pi --session <path>` reopens it.
649
+ `pi --session <path>` reopens it (see [Reopening a delegate's session](#reopening-a-delegates-session)).
411
650
  - A failed run, such as one ending on a usage limit or another provider
412
651
  error, exits like a normal completion; the parent still gets the failed
413
652
  reply and the session file stays. Only a run the user stopped never exits
@@ -434,6 +673,14 @@ the parent one message labelled delegate output:
434
673
  Status: done
435
674
  Task, quoted from <sent/ copy>:
436
675
  > the task
676
+ Working directory: <the directory its window opened in>
677
+ Workspace: <how the directory was chosen and its Git checkout at launch>
678
+ Checkout when the result arrived: <branch, head, launch commit, commits since launch, clean or dirty>
679
+ committed since launch: <paths>
680
+ staged: <paths>
681
+ unstaged: <paths>
682
+ untracked: <paths>
683
+ conflicted: <paths>
437
684
  Envelope: <cur/ path>
438
685
  Delegate session: <session file or id>
439
686
 
@@ -448,17 +695,20 @@ Delegate session: <session file or id>
448
695
  or `/model` in its window), it reads `launched → finished`, for example
449
696
  `(anthropic/claude-haiku-5-5 → openai/gpt-6-luna)`. `delegation_status`
450
697
  reports the same.
451
- - That text is what the parent model reads. The user sees it drawn instead:
452
- collapsed, a header with a status icon (✓ done, ✗ failed, ■ stopped), the
453
- delegate's name and the model, then the first 3 non-blank lines of the
454
- result with heading marks, backticks and bold stripped; expanded (Ctrl+O),
455
- the quoted task, the result as Markdown, then the ids and files, with the
456
- home directory shown as `~`.
457
- - The `delegate` tool's own row is drawn the same way, so a launch and its
458
- result match: a call line naming the agent and label, then a header with
459
- a ◐ started icon, the delegate's name and the model it started on, a preview of the task and any skipped fallback models;
460
- expanded, the whole task, then the delegation id, window, thinking and
461
- auto-exit. A refused call keeps its plain error text. The closed-without-a-result message gets the same header with ⊘.
698
+ - That text is what the parent model reads. The user sees the delegation as
699
+ one tool call that changes state, in the style of
700
+ [pi-interactive-subagents](https://github.com/amosblomqvist/pi-interactive-subagents):
701
+ - The `delegate` row shows `○` with the agent, the label, the task's first
702
+ line and its line count while the call runs. Once the delegate starts,
703
+ that header is replaced by `● <name> (<model>) — started` over the same
704
+ task line, with any skipped fallback model. A refused call keeps its
705
+ `○` header above the plain error text.
706
+ - The result arrives drawn as a finished tool row, on Pi's tool success or
707
+ error background: `✓ <name> (<model>) — done`, or `✗` with `failed`,
708
+ `stopped` or `closed without a result`. Collapsed, it previews the first
709
+ 5 non-blank lines with heading marks, backticks and bold stripped;
710
+ expanded (Ctrl+O), it shows the result as Markdown, the quoted task, then
711
+ the ids and files, with the home directory shown as `~`.
462
712
  - Stopping a delegate's run (Esc) sends no result: the provider holds the
463
713
  task, so the delegation stays running while the user steers it. The next
464
714
  run that completes sends the result, `done`, and its body opens with a note
@@ -473,9 +723,20 @@ Delegate session: <session file or id>
473
723
  - The task is quoted from the request copy the provider puts on the payload,
474
724
  capped at 2 KiB with the copy's `sent/` path. A request with no copy is
475
725
  named by id alone.
726
+ - The working directory is the one recorded at launch, or
727
+ `unknown (not recorded)` for a delegation recorded before it was kept. The
728
+ `Workspace:` line repeats the recorded [workspace](#working-directory), and
729
+ is left out for records from before it was kept; the closed-without-a-result
730
+ message carries the same two lines.
731
+ - `Checkout when the result arrived:` is the [snapshot](#checkout-snapshots)
732
+ taken as the reply was claimed, also recorded with the result and in the
733
+ message details and expanded view. The reply is claimed first, so a slow or
734
+ failing Git cannot lose or duplicate it; the snapshot is then unknown. A
735
+ non-Git directory reads `not a Git checkout`.
476
736
  - The envelope path is the claimed reply in the parent's `cur/`; the
477
- delegate's session path is found in the session directory by the delegation
478
- id, and the id alone is shown when no file exists yet.
737
+ delegate's session path is found by the delegation id in the session
738
+ directory recorded at launch (the parent's current one for older records),
739
+ and the id alone is shown when no file exists yet.
479
740
  - The body is cut at 32 KiB with the envelope path.
480
741
  - The message is sent as a `steer` with `triggerTurn`: a result starts a
481
742
  turn when the parent is idle, and when it is mid-turn arrives after the
@@ -507,6 +768,16 @@ delegation, with the delegate session path when one is found, sent as a
507
768
  still delivered and makes the delegation done. A resumed session polls again
508
769
  for every delegation it restores as running.
509
770
 
771
+ ### Reopening a delegate's session
772
+
773
+ `delegation_status` and every result name the delegate's session file by its
774
+ absolute path. Reopen it with that path: `pi --session /absolute/path/<file>.jsonl`.
775
+ Pi groups sessions by working directory: the session picker and an id given
776
+ to `--session` search the current project first, and may miss a delegate's
777
+ session or offer to fork it rather than open it when the delegate ran in
778
+ another directory or its file lives in another storage directory. The
779
+ absolute path opens that file directly.
780
+
510
781
  ## Hooks
511
782
 
512
783
  The `js` block below is the contract's worked example, and
@@ -517,24 +788,34 @@ that block.
517
788
 
518
789
  ### `session:launch`
519
790
 
520
- `pi-squire` provides this hook. It emits `{ args, env, agent }` after the task
521
- has been sent and just before it opens the delegate's window.
791
+ `pi-squire` provides this hook. It emits `{ args, env, agent, cwd }` after the
792
+ task has been sent and just before it opens the delegate's window.
522
793
 
523
794
  | Field | Meaning |
524
795
  | --- | --- |
525
796
  | `args` | the child `pi`'s complete argument list, launcher first |
526
797
  | `env` | the child's environment, as `KEY`/`value` pairs |
527
798
  | `agent` | the agent's name, as in `<agent dir>/squire/agents/<name>/AGENT.md`; read it, do not change it |
799
+ | `cwd` | the directory the window opens in, the parent's or the resolved `--cwd`; read it, do not change it |
528
800
 
529
801
  Listeners may only append to `args` and add keys to `env`; there is no veto,
530
802
  and `pi-squire` passes both to tmux unchanged. Do all work synchronously: the
531
803
  emitter reads the payload the moment `emit` returns.
532
804
 
805
+ `cwd`, the trust flag (`--approve` or `--no-approve`) and `--session-dir` are
806
+ launch invariants that `pi-squire` chooses. The window opens in its own `cwd`,
807
+ whatever a listener assigns to the payload. A listener must not add another
808
+ trust flag, `--session-dir` or a working-directory change to `args`, since
809
+ that would launch a child whose trust or session storage the parent did not
810
+ choose and cannot find; append only unrelated settings.
811
+
533
812
  ```js session:launch
534
813
  // Any extension can shape a launch: append a flag and add an environment key.
814
+ // `cwd` says where the window opens; read it, never assign it.
535
815
  pi.events.on("session:launch", (payload) => {
536
816
  payload.args.push("--auto");
537
817
  payload.env.GATE_MODE = "auto";
818
+ payload.env.GATE_ROOT = payload.cwd;
538
819
  });
539
820
  ```
540
821
 
@@ -594,6 +875,14 @@ requests, compaction, cache behavior, or model obedience. The idle-result wake
594
875
  is simulated by dispatching the next preparation; the suite does not assert
595
876
  that the runtime emits it.
596
877
 
878
+ Working-directory and trust tests use real temporary Git repositories and
879
+ Pi's real trust store under a temporary agent directory, but a fake tmux. The
880
+ existing-directory/inherited launch evidence does not verify startup for a
881
+ managed worktree or prove that a real delegate loads the selected checkout's
882
+ instructions and settings; actual Pi/tmux startup and resource loading remain
883
+ unverified. Snapshot tests use real repositories and,
884
+ for hung, failing or flooding Git, a scripted `git` on `PATH`.
885
+
597
886
  ## Compatibility
598
887
 
599
888
  While pi-squire is below 1.0, breaking any contract below bumps the minor
@@ -601,12 +890,15 @@ version and is listed under "Breaking" in [CHANGELOG.md](CHANGELOG.md).
601
890
 
602
891
  Other extensions may rely on:
603
892
 
604
- - the `session:launch` payload `{ args, env, agent }`, emitted synchronously
605
- after the task is sent and before the window opens (see [Hooks](#hooks));
893
+ - the `session:launch` payload `{ args, env, agent, cwd }`, emitted
894
+ synchronously after the task is sent and before the window opens, with
895
+ `cwd`, the trust flag and `--session-dir` chosen by `pi-squire` (see
896
+ [Hooks](#hooks));
606
897
  - the `message:send`, `message:inbound` and `message:scan` hooks, consumed as
607
898
  their sections document;
608
- - the `delegate`, `delegation_status` and `delegation_close` tool names and
609
- parameters;
899
+ - the `delegate`, `delegation_status` and `delegation_close` tool names,
900
+ parameters, the recorded `workspace` shape and the `workspaceSnapshot`
901
+ shape in results and status;
610
902
  - the `/delegate` syntax and flags;
611
903
  - the `[delegate]` result text and the closed-without-a-result text;
612
904
  - `AGENT.md`'s frontmatter keys and its location,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-squire",
3
- "version": "0.9.4",
3
+ "version": "0.10.0",
4
4
  "description": "Starts a delegate Pi session in a background tmux window, sends it the task over message:*, and shows the parent its result",
5
5
  "keywords": [
6
6
  "pi-package",