pi-squire 0.9.5 → 0.11.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,105 @@ follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.11.0] - 2026-10-26
10
+
11
+ ### Added
12
+
13
+ - The `session:launch` payload gains a read-only `toolCallId`, the ID of the
14
+ `delegate` tool call that started the launch, so a listener can tie a
15
+ delegate's window to that call. It is `undefined` for a launch from the
16
+ `/delegate` command.
17
+ - `delegation_resume` and `/delegate-resume <id> [--model …] [--thinking …] [--auto-exit|--no-auto-exit] <task>` reopen an exited delegate's own session (`pi --session <path>`) in a new window with a follow-up task. The resume is recorded as a new delegation with `resumedFrom`; mail, replies and usage-limit marks use the delegation's session id, so records from before resuming read as before. A delegate whose working directory is gone, one still running, one whose window is open and an older delegation of a resumed chain are refused. `delegation_status` shows `resumed from` and `resumed as`, results and closed-without-a-result messages name `resumedFrom`, and the latter point to `delegation_resume`. The `delegate` tool's description mentions it, and `--session` joins the launch invariants listed for `session:launch`. A resume emits `session:launch`
18
+ too, with `toolCallId` set to the `delegation_resume` call, or `undefined`
19
+ from `/delegate-resume`; unlike a launch's, it comes before the task is sent,
20
+ because a resume mails the task only once its window is open (mail for a
21
+ window that never opened would otherwise reach the next resume as a second
22
+ task; a failed send kills the new window). A resumed delegation is never
23
+ reported closed without a result.
24
+
25
+ ## [0.10.0] - 2026-10-24
26
+
27
+ ### Added
28
+
29
+ - `/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.
30
+
31
+ - `/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.
32
+
33
+ - Managed worktree placement can be configured per call in
34
+ `<agent dir>/squire/settings.json` with a global `worktreeRoot` template and
35
+ canonical-main-repository `worktreeRootByRepo` overrides. Placement settings
36
+ affect only worktree creation; inherited and `cwd` launches never read them.
37
+ - `/delegate --worktree <branch>` and the `delegate` tool's `worktree` input
38
+ create a managed worktree from the source checkout's frozen committed HEAD,
39
+ in the built-in sibling `{repo}-worktrees/{branch}` layout. Source dirty
40
+ paths are summarized but never copied. Launch attempts are journaled before
41
+ creation; known pre-window failures roll back only a proven pristine checkout
42
+ and branch, while uncertain or changed resources are retained with recovery
43
+ facts. Once a window opens, the workspace is retained. Managed workspace
44
+ facts appear in results, status and expanded rendering.
45
+ - The `session:launch` payload gains a read-only `cwd`, the directory the
46
+ delegate's window opens in. The window opens there whatever a listener
47
+ assigns; appending args and adding env keys work as before. The README
48
+ example and its test read it.
49
+ - Each delegation records its working directory and session-storage
50
+ directory. The `delegate` tool's structured result carries `cwd` and
51
+ `sessionDir`; `delegation_status`, result and closed-without-a-result
52
+ messages and their expanded views show the working directory, and older
53
+ records show it as `unknown (not recorded)`.
54
+
55
+ - `--cwd <dir>` on `/delegate` and `cwd` on the `delegate` tool run a
56
+ delegate in an existing directory: relative to the parent's, `~`/`~/`
57
+ expanded, symlinks resolved, refused before anything starts when it is
58
+ missing, a file or cannot be entered. The directory's branch, index and
59
+ files are never touched or cleaned up. Placeholder values (`null`, `""`,
60
+ `"null"`, `"none"`, `"false"`, `"undefined"`) mean the parent's directory;
61
+ other non-string values are refused. The delegate's prompt names the
62
+ selected directory, and the start message names it when it differs.
63
+ - Project trust for a `--cwd` directory is decided in the parent: the parent's
64
+ current trust inside its own repository (by Git common directory), else the
65
+ saved decision for a linked worktree's main repository or for the directory,
66
+ else `--no-approve` when it has no protected resources, else a parent
67
+ dialog with Pi's five choices. Without a UI, after a cancel or an abort, or
68
+ when the trust store cannot be read or written, nothing starts. Dialogs are
69
+ asked one at a time.
70
+ - Each new delegation records a `workspace` (mode, cwd and Git checkout at
71
+ launch: identity, branch or detached, launch commit, or non-Git, or unknown
72
+ with a reason). The `delegate` tool's structured result, `delegation_status`,
73
+ result and closed-without-a-result messages and their expanded views carry
74
+ it; result and closed text gain a `Workspace:` line, and status text a
75
+ `workspace:` line. The pinned record, result-text and completion tests
76
+ change with it.
77
+
78
+ - Results and `delegation_status` report a snapshot of the delegation's
79
+ checkout (`workspaceSnapshot`): branch or detached HEAD, head, launch
80
+ commit, commits since launch, committed paths since launch, and staged,
81
+ unstaged, untracked and conflicted paths, or non-Git, or unknown with a
82
+ reason. A result keeps the snapshot taken when its reply arrived; status
83
+ takes a fresh one each call. Lists are capped at 100 paths and 16 KiB with
84
+ totals; Git gets 2 s per command, 5 s and 4 MiB per checkout, runs without
85
+ optional index writes, and is asked only about the recorded checkout. A
86
+ snapshot is an observation of the Git state at the recorded location, not
87
+ proof of the same repository on disk, attribution or merge advice. Result text
88
+ gains `Checkout when the result arrived:` lines and status text
89
+ `checkout now:` lines; the pinned result entry, details and result-text
90
+ tests change with it.
91
+
92
+ ### Changed
93
+
94
+ - `/delegate` flag values may be double-quoted, with `\"` and `\\` escapes,
95
+ so a path with spaces works; the task after the flags is kept byte for
96
+ byte. A missing closing quote is refused before anything starts.
97
+ - Every delegate is launched with an explicit `--approve` or `--no-approve`
98
+ from the parent's current project trust, so a global
99
+ `defaultProjectTrust` cannot trust a project the parent does not.
100
+ - Every delegate is launched with `--session-dir` set to the parent's absolute
101
+ session-storage directory. Session lookups for running, closed and late
102
+ results use the directory recorded at launch, so they survive a parent that
103
+ resumes with another storage directory, and a found session path is kept
104
+ in the closed-without-a-result record. A parent whose session directory is
105
+ not absolute refuses to launch before sending the task. The launch argv and
106
+ record tests change with it.
107
+
9
108
  ## [0.9.5] - 2026-10-23
10
109
 
11
110
  ### Changed
@@ -375,7 +474,9 @@ author's dotfiles; the changes below are against that copy.
375
474
 
376
475
  - The published file list includes `child.ts`, which `index.ts` imports.
377
476
 
378
- [Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.9.5...HEAD
477
+ [Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.11.0...HEAD
478
+ [0.11.0]: https://github.com/gvanderclay/pi-squire/compare/v0.10.0...v0.11.0
479
+ [0.10.0]: https://github.com/gvanderclay/pi-squire/compare/v0.9.5...v0.10.0
379
480
  [0.9.5]: https://github.com/gvanderclay/pi-squire/compare/v0.9.4...v0.9.5
380
481
  [0.9.4]: https://github.com/gvanderclay/pi-squire/compare/v0.9.3...v0.9.4
381
482
  [0.9.3]: https://github.com/gvanderclay/pi-squire/compare/v0.9.2...v0.9.3
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
@@ -212,14 +428,15 @@ agent's `auto-exit`.
212
428
 
213
429
  ## Tools
214
430
 
215
- The model gets three tools. They share the command's launch path and the
431
+ The model gets four tools. They share the command's launch path and the
216
432
  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. |
439
+ | `delegation_resume` | `id`, `task`, optional `model`, `thinking` and `auto_exit` | Reopens an exited delegate's own session in a new window with a follow-up task, and returns the new delegation's id at once (see [Resuming a delegation](#resuming-a-delegation)). |
223
440
 
224
441
  The `delegate` tool's description lists the current roster, one line per agent:
225
442
  its name, description, default model and thinking, auto-exit when it is off,
@@ -253,6 +470,14 @@ nothing starts.
253
470
  name: scout-<first 8 of the id>
254
471
  state: running — the window is open and no result has arrived (last active 12 min ago)
255
472
  window: scout-<first 8 of the id> (@1)
473
+ cwd: <the directory its window opened in>
474
+ workspace: existing directory; Git checkout <main root> (linked worktree <checkout>), branch <name>, launch commit <sha>
475
+ checkout now: branch <name>, head <sha>, launch commit <sha>, 1 commit since launch, working tree dirty
476
+ committed since launch: 1 path: "src/a.ts"
477
+ staged: none
478
+ unstaged: 1 path: "README.md"
479
+ untracked: none
480
+ conflicted: none
256
481
  auto-exit: on — the delegate closes its window after a normal completion unless the user took over there
257
482
  session: <the delegate's session file, or its id when there is none yet>
258
483
 
@@ -260,6 +485,8 @@ nothing starts.
260
485
  name: researcher-<first 8 of the id>
261
486
  state: done — result status: done; the result reaches this session as a [delegate] message on its own
262
487
  window: researcher-<first 8 of the id> (@2)
488
+ cwd: unknown (not recorded)
489
+ checkout now: Git state unknown (this delegation was recorded before its launch checkout was kept)
263
490
  auto-exit: off — the window stays open after the result
264
491
  session: <session file>
265
492
  envelope: <the reply's path in the parent's cur/>
@@ -279,6 +506,19 @@ nothing starts.
279
506
  arrived before its window went. For a moment before the result is claimed
280
507
  it can read closed, since the window is already gone.
281
508
  - **Auto-exit** reads off for delegations recorded before auto-exit existed.
509
+ - **Cwd** is the directory the delegate's window opened in. A delegation
510
+ recorded before it was kept reads `unknown (not recorded)`, never the
511
+ parent's current directory.
512
+ - **Workspace** says how the directory was chosen and its Git checkout at
513
+ launch (see [Working directory](#working-directory)); older records have
514
+ none.
515
+ - **Checkout now** is a fresh [snapshot](#checkout-snapshots) of that
516
+ checkout, taken by this call; the details carry it as `workspaceSnapshot`.
517
+ A failure is unknown for that delegation alone.
518
+ - **Session** is the delegate's session file, searched for in the storage
519
+ directory recorded at launch. Once found, its path is kept, so a later
520
+ status or result uses it without searching again. Before the delegate has
521
+ written its file, the id is shown instead of an invented file name.
282
522
  - **Closed** covers a close `delegation_close` recorded and a window that is no
283
523
  longer there. A recorded close wins over a result, and a result wins over a
284
524
  window that is gone.
@@ -407,7 +647,7 @@ The parent resolves each delegation's auto-exit from the call
407
647
  - While it is on, a run that settles as a normal completion shuts the delegate
408
648
  down (`ctx.shutdown()`) on the next event-loop turn, after the settle
409
649
  reply has been sent, so its tmux window closes. The session file stays, and
410
- `pi --session <path>` reopens it.
650
+ `pi --session <path>` reopens it (see [Reopening a delegate's session](#reopening-a-delegates-session)).
411
651
  - A failed run, such as one ending on a usage limit or another provider
412
652
  error, exits like a normal completion; the parent still gets the failed
413
653
  reply and the session file stays. Only a run the user stopped never exits
@@ -434,6 +674,14 @@ the parent one message labelled delegate output:
434
674
  Status: done
435
675
  Task, quoted from <sent/ copy>:
436
676
  > the task
677
+ Working directory: <the directory its window opened in>
678
+ Workspace: <how the directory was chosen and its Git checkout at launch>
679
+ Checkout when the result arrived: <branch, head, launch commit, commits since launch, clean or dirty>
680
+ committed since launch: <paths>
681
+ staged: <paths>
682
+ unstaged: <paths>
683
+ untracked: <paths>
684
+ conflicted: <paths>
437
685
  Envelope: <cur/ path>
438
686
  Delegate session: <session file or id>
439
687
 
@@ -476,9 +724,20 @@ Delegate session: <session file or id>
476
724
  - The task is quoted from the request copy the provider puts on the payload,
477
725
  capped at 2 KiB with the copy's `sent/` path. A request with no copy is
478
726
  named by id alone.
727
+ - The working directory is the one recorded at launch, or
728
+ `unknown (not recorded)` for a delegation recorded before it was kept. The
729
+ `Workspace:` line repeats the recorded [workspace](#working-directory), and
730
+ is left out for records from before it was kept; the closed-without-a-result
731
+ message carries the same two lines.
732
+ - `Checkout when the result arrived:` is the [snapshot](#checkout-snapshots)
733
+ taken as the reply was claimed, also recorded with the result and in the
734
+ message details and expanded view. The reply is claimed first, so a slow or
735
+ failing Git cannot lose or duplicate it; the snapshot is then unknown. A
736
+ non-Git directory reads `not a Git checkout`.
479
737
  - The envelope path is the claimed reply in the parent's `cur/`; the
480
- delegate's session path is found in the session directory by the delegation
481
- id, and the id alone is shown when no file exists yet.
738
+ delegate's session path is found by the delegation id in the session
739
+ directory recorded at launch (the parent's current one for older records),
740
+ and the id alone is shown when no file exists yet.
482
741
  - The body is cut at 32 KiB with the envelope path.
483
742
  - The message is sent as a `steer` with `triggerTurn`: a result starts a
484
743
  turn when the parent is idle, and when it is mid-turn arrives after the
@@ -510,6 +769,69 @@ delegation, with the delegate session path when one is found, sent as a
510
769
  still delivered and makes the delegation done. A resumed session polls again
511
770
  for every delegation it restores as running.
512
771
 
772
+ ### Reopening a delegate's session
773
+
774
+ `delegation_status` and every result name the delegate's session file by its
775
+ absolute path. Reopen it with that path: `pi --session /absolute/path/<file>.jsonl`.
776
+ Pi groups sessions by working directory: the session picker and an id given
777
+ to `--session` search the current project first, and may miss a delegate's
778
+ session or offer to fork it rather than open it when the delegate ran in
779
+ another directory or its file lives in another storage directory. The
780
+ absolute path opens that file directly.
781
+
782
+ ## Resuming a delegation
783
+
784
+ An exited delegate can continue its own session with a follow-up task:
785
+
786
+ ```text
787
+ /delegate-resume <id> [--model <provider/id>] [--thinking <level>] [--auto-exit | --no-auto-exit] <task…>
788
+ ```
789
+
790
+ The model does the same with `delegation_resume` (`id` and `task`, optionally
791
+ `model`, `thinking` and `auto_exit`), which returns the `delegate` tool's result
792
+ plus `resumedFrom`. The command completes the ids that can still be resumed.
793
+
794
+ - The delegate's own session file is reopened with `pi --session <absolute path>`
795
+ and the recorded `--session-dir`, in a new window named like the first. The
796
+ delegate keeps everything it read and decided; Pi appends to the same file.
797
+ Opening it yourself is `pi --session <path>`, outside this tool.
798
+ - The resume is recorded as a **new delegation** with its own id and a
799
+ `resumedFrom` naming the old one. Its task goes to the old session's mail
800
+ address, and its result arrives as a `[delegate]` message that names
801
+ `resumed from <id>`. Status shows `resumed from <id>` on the new delegation and
802
+ `resumed as <id>` on the old one. A late reply to the old request still
803
+ settles the old delegation only, and a resumed delegation is never reported
804
+ closed without a result.
805
+ - Unlike a launch, the task is mailed after the window opens: the session's
806
+ inbox outlives a failed attempt, so a task sent for a window that never
807
+ opened would reach the next resume as a second task. The reopened delegate
808
+ receives it once its mailbox starts. If the send fails, the new window is
809
+ killed and nothing is recorded.
810
+ - A chain resumes from its newest delegation; resuming an older one is refused,
811
+ naming the newest.
812
+ - It is refused, before anything is sent or opened, unless: the delegation is a
813
+ started one (not a workspace attempt); it is not `running` (mail a running
814
+ delegate instead) and tmux can be asked; no window with its id and name still
815
+ runs a program (an exited one is killed first; a window that merely shares the
816
+ name is ignored); its session file is found and the working directory in the
817
+ file's header still exists; its agent is still in the roster; and no other
818
+ resume of it is under way. A delegate whose window was killed mid-turn can be
819
+ resumed.
820
+ - A removed working directory, a removed managed worktree included, makes a
821
+ delegation not resumable: Pi runs a reopened session in its stored directory.
822
+ The window opens in that directory, with project trust decided again for it as
823
+ for `cwd` (see [Project trust](#project-trust)); there is no `cwd` or
824
+ `worktree` for a resume, and no worktree is created.
825
+ - Without `model` it continues on the model the delegate finished on, moving to
826
+ its agent's fallbacks when that one is unavailable or marked (see
827
+ [Usage limits](#usage-limits)). An explicit `model` is used alone and refused
828
+ rather than substituted. `thinking` and `auto_exit` default to the
829
+ delegation's own. The agent's prompt, tools and extensions are those of the
830
+ roster now.
831
+ - `session:launch` is emitted as for any launch, with the same payload.
832
+ - Not detected: a Pi process you opened yourself on the same session outside
833
+ pi-squire's window. Close it before resuming.
834
+
513
835
  ## Hooks
514
836
 
515
837
  The `js` block below is the contract's worked example, and
@@ -520,24 +842,38 @@ that block.
520
842
 
521
843
  ### `session:launch`
522
844
 
523
- `pi-squire` provides this hook. It emits `{ args, env, agent }` after the task
524
- has been sent and just before it opens the delegate's window.
845
+ `pi-squire` provides this hook. It emits `{ args, env, agent, cwd, toolCallId }`
846
+ just before it opens the delegate's window: for a launch, after the task has
847
+ been sent; for a resume, before it is sent, since a resume mails the task once
848
+ the window is open (see [Resuming a delegation](#resuming-a-delegation)).
525
849
 
526
850
  | Field | Meaning |
527
851
  | --- | --- |
528
852
  | `args` | the child `pi`'s complete argument list, launcher first |
529
853
  | `env` | the child's environment, as `KEY`/`value` pairs |
530
854
  | `agent` | the agent's name, as in `<agent dir>/squire/agents/<name>/AGENT.md`; read it, do not change it |
855
+ | `cwd` | the directory the window opens in, the parent's or the resolved `--cwd`; read it, do not change it |
856
+ | `toolCallId` | the ID of the `delegate` or `delegation_resume` tool call that started the launch; absent when started from the `/delegate` or `/delegate-resume` command; read it, do not change it |
531
857
 
532
858
  Listeners may only append to `args` and add keys to `env`; there is no veto,
533
859
  and `pi-squire` passes both to tmux unchanged. Do all work synchronously: the
534
860
  emitter reads the payload the moment `emit` returns.
535
861
 
862
+ `cwd`, the trust flag (`--approve` or `--no-approve`), `--session-dir` and the
863
+ session choice (`--session-id` for a new delegate, `--session` for a [resumed](#resuming-a-delegation)
864
+ one) are launch invariants that `pi-squire` chooses. The window opens in its own `cwd`,
865
+ whatever a listener assigns to the payload. A listener must not add another
866
+ trust flag, `--session-dir`, `--session` or `--session-id`, or a working-directory change to `args`, since
867
+ that would launch a child whose trust or session storage the parent did not
868
+ choose and cannot find; append only unrelated settings.
869
+
536
870
  ```js session:launch
537
871
  // Any extension can shape a launch: append a flag and add an environment key.
872
+ // `cwd` says where the window opens; read it, never assign it.
538
873
  pi.events.on("session:launch", (payload) => {
539
874
  payload.args.push("--auto");
540
875
  payload.env.GATE_MODE = "auto";
876
+ payload.env.GATE_ROOT = payload.cwd;
541
877
  });
542
878
  ```
543
879
 
@@ -597,6 +933,14 @@ requests, compaction, cache behavior, or model obedience. The idle-result wake
597
933
  is simulated by dispatching the next preparation; the suite does not assert
598
934
  that the runtime emits it.
599
935
 
936
+ Working-directory and trust tests use real temporary Git repositories and
937
+ Pi's real trust store under a temporary agent directory, but a fake tmux. The
938
+ existing-directory/inherited launch evidence does not verify startup for a
939
+ managed worktree or prove that a real delegate loads the selected checkout's
940
+ instructions and settings; actual Pi/tmux startup and resource loading remain
941
+ unverified. Snapshot tests use real repositories and,
942
+ for hung, failing or flooding Git, a scripted `git` on `PATH`.
943
+
600
944
  ## Compatibility
601
945
 
602
946
  While pi-squire is below 1.0, breaking any contract below bumps the minor
@@ -604,13 +948,17 @@ version and is listed under "Breaking" in [CHANGELOG.md](CHANGELOG.md).
604
948
 
605
949
  Other extensions may rely on:
606
950
 
607
- - the `session:launch` payload `{ args, env, agent }`, emitted synchronously
608
- after the task is sent and before the window opens (see [Hooks](#hooks));
951
+ - the `session:launch` payload `{ args, env, agent, cwd, toolCallId }`, emitted
952
+ synchronously before the window opens (after the task is sent for a launch,
953
+ before it is sent for a resume), with
954
+ `cwd`, the trust flag and `--session-dir` chosen by `pi-squire` (see
955
+ [Hooks](#hooks));
609
956
  - the `message:send`, `message:inbound` and `message:scan` hooks, consumed as
610
957
  their sections document;
611
- - the `delegate`, `delegation_status` and `delegation_close` tool names and
612
- parameters;
613
- - the `/delegate` syntax and flags;
958
+ - the `delegate`, `delegation_status`, `delegation_close` and `delegation_resume` tool names,
959
+ parameters, the recorded `workspace` shape and the `workspaceSnapshot`
960
+ shape in results and status;
961
+ - the `/delegate` and `/delegate-resume` syntax and flags;
614
962
  - the `[delegate]` result text and the closed-without-a-result text;
615
963
  - `AGENT.md`'s frontmatter keys and its location,
616
964
  `<agent dir>/squire/agents/<name>/AGENT.md`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-squire",
3
- "version": "0.9.5",
3
+ "version": "0.11.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",