pi-squire 0.9.5 → 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 +85 -1
- package/README.md +305 -16
- package/package.json +1 -1
- package/src/git.ts +177 -0
- package/src/index.ts +415 -65
- package/src/render.ts +106 -1
- package/src/result-sessions.ts +47 -0
- package/src/result-types.ts +36 -0
- package/src/results.ts +122 -77
- package/src/snapshot.ts +207 -0
- package/src/tmux.ts +30 -3
- package/src/tracking-attempt.ts +65 -0
- package/src/tracking.ts +78 -20
- package/src/workspace.ts +420 -0
- package/src/worktree-placement.ts +138 -0
- package/src/worktree.ts +474 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,89 @@ 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
|
+
|
|
9
92
|
## [0.9.5] - 2026-10-23
|
|
10
93
|
|
|
11
94
|
### Changed
|
|
@@ -375,7 +458,8 @@ author's dotfiles; the changes below are against that copy.
|
|
|
375
458
|
|
|
376
459
|
- The published file list includes `child.ts`, which `index.ts` imports.
|
|
377
460
|
|
|
378
|
-
[Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.
|
|
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
|
|
379
463
|
[0.9.5]: https://github.com/gvanderclay/pi-squire/compare/v0.9.4...v0.9.5
|
|
380
464
|
[0.9.4]: https://github.com/gvanderclay/pi-squire/compare/v0.9.3...v0.9.4
|
|
381
465
|
[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
|
-
|
|
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
|
|
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
|
|
|
@@ -476,9 +723,20 @@ Delegate session: <session file or id>
|
|
|
476
723
|
- The task is quoted from the request copy the provider puts on the payload,
|
|
477
724
|
capped at 2 KiB with the copy's `sent/` path. A request with no copy is
|
|
478
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`.
|
|
479
736
|
- The envelope path is the claimed reply in the parent's `cur/`; the
|
|
480
|
-
delegate's session path is found
|
|
481
|
-
|
|
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.
|
|
482
740
|
- The body is cut at 32 KiB with the envelope path.
|
|
483
741
|
- The message is sent as a `steer` with `triggerTurn`: a result starts a
|
|
484
742
|
turn when the parent is idle, and when it is mid-turn arrives after the
|
|
@@ -510,6 +768,16 @@ delegation, with the delegate session path when one is found, sent as a
|
|
|
510
768
|
still delivered and makes the delegation done. A resumed session polls again
|
|
511
769
|
for every delegation it restores as running.
|
|
512
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
|
+
|
|
513
781
|
## Hooks
|
|
514
782
|
|
|
515
783
|
The `js` block below is the contract's worked example, and
|
|
@@ -520,24 +788,34 @@ that block.
|
|
|
520
788
|
|
|
521
789
|
### `session:launch`
|
|
522
790
|
|
|
523
|
-
`pi-squire` provides this hook. It emits `{ args, env, agent }` after the
|
|
524
|
-
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.
|
|
525
793
|
|
|
526
794
|
| Field | Meaning |
|
|
527
795
|
| --- | --- |
|
|
528
796
|
| `args` | the child `pi`'s complete argument list, launcher first |
|
|
529
797
|
| `env` | the child's environment, as `KEY`/`value` pairs |
|
|
530
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 |
|
|
531
800
|
|
|
532
801
|
Listeners may only append to `args` and add keys to `env`; there is no veto,
|
|
533
802
|
and `pi-squire` passes both to tmux unchanged. Do all work synchronously: the
|
|
534
803
|
emitter reads the payload the moment `emit` returns.
|
|
535
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
|
+
|
|
536
812
|
```js session:launch
|
|
537
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.
|
|
538
815
|
pi.events.on("session:launch", (payload) => {
|
|
539
816
|
payload.args.push("--auto");
|
|
540
817
|
payload.env.GATE_MODE = "auto";
|
|
818
|
+
payload.env.GATE_ROOT = payload.cwd;
|
|
541
819
|
});
|
|
542
820
|
```
|
|
543
821
|
|
|
@@ -597,6 +875,14 @@ requests, compaction, cache behavior, or model obedience. The idle-result wake
|
|
|
597
875
|
is simulated by dispatching the next preparation; the suite does not assert
|
|
598
876
|
that the runtime emits it.
|
|
599
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
|
+
|
|
600
886
|
## Compatibility
|
|
601
887
|
|
|
602
888
|
While pi-squire is below 1.0, breaking any contract below bumps the minor
|
|
@@ -604,12 +890,15 @@ version and is listed under "Breaking" in [CHANGELOG.md](CHANGELOG.md).
|
|
|
604
890
|
|
|
605
891
|
Other extensions may rely on:
|
|
606
892
|
|
|
607
|
-
- the `session:launch` payload `{ args, env, agent }`, emitted
|
|
608
|
-
after the task is sent and before the window opens
|
|
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));
|
|
609
897
|
- the `message:send`, `message:inbound` and `message:scan` hooks, consumed as
|
|
610
898
|
their sections document;
|
|
611
|
-
- the `delegate`, `delegation_status` and `delegation_close` tool names
|
|
612
|
-
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;
|
|
613
902
|
- the `/delegate` syntax and flags;
|
|
614
903
|
- the `[delegate]` result text and the closed-without-a-result text;
|
|
615
904
|
- `AGENT.md`'s frontmatter keys and its location,
|
package/package.json
CHANGED