pi-squire 0.8.1 → 0.9.1
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 +43 -1
- package/README.md +66 -35
- package/package.json +4 -4
- package/src/agents.ts +3 -3
- package/src/child.ts +11 -7
- package/src/index.ts +59 -10
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,46 @@ follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.1] - 2026-10-22
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- A delegate's footer names its agent, as in `👦 worker delegate · auto-exit on`.
|
|
14
|
+
The launch passes the agent name to the delegate as `PI_DELEGATE_AGENT`.
|
|
15
|
+
|
|
16
|
+
## [0.9.0] - 2026-10-21
|
|
17
|
+
|
|
18
|
+
### Breaking
|
|
19
|
+
|
|
20
|
+
- Agent definitions now live under `<agent dir>/squire/agents/<name>/AGENT.md`,
|
|
21
|
+
and only there. The old `<agent dir>/agents/<name>/AGENT.md` location is no
|
|
22
|
+
longer read, with no fallback or dual discovery, so an agent left there is
|
|
23
|
+
neither listed nor launched.
|
|
24
|
+
- Pi 1.3.0 or later is now required. The raised floor supports structured
|
|
25
|
+
per-run system-prompt sections and `before_agent_start` preparation for runs
|
|
26
|
+
started by an idle custom-message result, so parent guidance and roster
|
|
27
|
+
metadata are current before each new run.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Optional parent delegation guidance: Markdown in
|
|
32
|
+
`<agent dir>/squire/delegation_prompt.md` is inserted unchanged into the
|
|
33
|
+
`squire_parent_delegation` system-prompt section before each new run, only
|
|
34
|
+
while the `delegate` tool is active and not hidden. Missing, empty or deleted
|
|
35
|
+
files add no guidance; a read failure warns with the path, removes stale
|
|
36
|
+
guidance for that run, and leaves delegation working. The package ships no
|
|
37
|
+
default delegation or approval policy.
|
|
38
|
+
- The `delegate` tool description now refreshes from the current roster before
|
|
39
|
+
each new run, including a run woken by an idle delegate result. A changed
|
|
40
|
+
agent description, addition or removal shows up in the run being prepared;
|
|
41
|
+
unchanged metadata is not re-registered.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- The `delegate` tool now says to keep working or end the turn rather than poll
|
|
46
|
+
or send a blocking mail request merely to wait for a result, while a
|
|
47
|
+
substantive follow-up remains supported.
|
|
48
|
+
|
|
9
49
|
## [0.8.1] - 2026-10-20
|
|
10
50
|
|
|
11
51
|
### Fixed
|
|
@@ -285,7 +325,9 @@ author's dotfiles; the changes below are against that copy.
|
|
|
285
325
|
|
|
286
326
|
- The published file list includes `child.ts`, which `index.ts` imports.
|
|
287
327
|
|
|
288
|
-
[Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.
|
|
328
|
+
[Unreleased]: https://github.com/gvanderclay/pi-squire/compare/v0.9.1...HEAD
|
|
329
|
+
[0.9.1]: https://github.com/gvanderclay/pi-squire/compare/v0.9.0...v0.9.1
|
|
330
|
+
[0.9.0]: https://github.com/gvanderclay/pi-squire/compare/v0.8.1...v0.9.0
|
|
289
331
|
[0.8.1]: https://github.com/gvanderclay/pi-squire/compare/v0.8.0...v0.8.1
|
|
290
332
|
[0.8.0]: https://github.com/gvanderclay/pi-squire/compare/v0.7.0...v0.8.0
|
|
291
333
|
[0.7.0]: https://github.com/gvanderclay/pi-squire/compare/v0.6.0...v0.7.0
|
package/README.md
CHANGED
|
@@ -10,13 +10,12 @@ answers with one message, which the parent session shows as a result.
|
|
|
10
10
|
|
|
11
11
|
You need:
|
|
12
12
|
|
|
13
|
-
- Pi
|
|
14
|
-
pi-squire is tested with.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`
|
|
19
|
-
`@earendil-works/pi-coding-agent` and `typebox`.
|
|
13
|
+
- Pi 1.3.0 or later, on macOS or Linux (not Windows). Pi 1.3.0 is the version
|
|
14
|
+
pi-squire is tested with. It provides the per-run `before_agent_start`
|
|
15
|
+
preparation for idle custom-message results, the structured system-prompt
|
|
16
|
+
options the parent guidance uses, and the extension APIs the package relies
|
|
17
|
+
on. Pi supplies the peer dependencies `@earendil-works/pi-coding-agent` and
|
|
18
|
+
`typebox`.
|
|
20
19
|
- tmux 3.0 or later, with the Pi session running in a tmux pane. That is the
|
|
21
20
|
first version with `new-window -e`. A window is opened in the pane's
|
|
22
21
|
session, so a client attached elsewhere does not matter.
|
|
@@ -45,8 +44,8 @@ The package has no runtime dependencies and no build step.
|
|
|
45
44
|
|
|
46
45
|
The package ships no agents, so create one first. `<agent dir>` is Pi's agent
|
|
47
46
|
directory: `~/.pi/agent`, or `PI_CODING_AGENT_DIR` when that is set. Save this
|
|
48
|
-
as `<agent dir>/agents/scout/AGENT.md`, replacing
|
|
49
|
-
model your Pi can use:
|
|
47
|
+
as `<agent dir>/squire/agents/scout/AGENT.md`, replacing
|
|
48
|
+
`provider/model-id` with a model your Pi can use:
|
|
50
49
|
|
|
51
50
|
```markdown
|
|
52
51
|
---
|
|
@@ -60,8 +59,10 @@ You research one question at a time and answer concisely, citing the files
|
|
|
60
59
|
you read.
|
|
61
60
|
```
|
|
62
61
|
|
|
63
|
-
The roster is read at call time, so no reload is needed.
|
|
64
|
-
|
|
62
|
+
The roster is read at call time, so no reload is needed. Delegation guidance
|
|
63
|
+
for the parent is optional: save Markdown as
|
|
64
|
+
`<agent dir>/squire/delegation_prompt.md`, and it is read before each run.
|
|
65
|
+
Then, from a Pi session running in tmux, hand it a task:
|
|
65
66
|
|
|
66
67
|
```text
|
|
67
68
|
/delegate scout --label first Which files define the command-line entry point?
|
|
@@ -75,10 +76,11 @@ closes its own window ([auto-exit](#auto-exit)).
|
|
|
75
76
|
## How a delegation works
|
|
76
77
|
|
|
77
78
|
`/delegate <agent> [--model <provider/id>] [--thinking <level>] [--label <name>] [--auto-exit | --no-auto-exit] <task…>`
|
|
78
|
-
reads the agent from `<agent dir>/agents/<name>/AGENT.md`, writes
|
|
79
|
-
the delegate's inbox through `message:send`, emits
|
|
80
|
-
extensions can shape the launch, then opens a
|
|
81
|
-
parent's own Pi in the parent's working directory
|
|
79
|
+
reads the agent from `<agent dir>/squire/agents/<name>/AGENT.md`, writes
|
|
80
|
+
the task to the delegate's inbox through `message:send`, emits
|
|
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
84
|
prompt reaches the child as a path to a private file under the system
|
|
83
85
|
temporary folder, not as text on the command line.
|
|
84
86
|
|
|
@@ -112,8 +114,9 @@ temporary folder, not as text on the command line.
|
|
|
112
114
|
|
|
113
115
|
## Agents
|
|
114
116
|
|
|
115
|
-
The roster is read at call time from
|
|
116
|
-
directory per agent, so a new
|
|
117
|
+
The roster is read at call time from
|
|
118
|
+
`<agent dir>/squire/agents/<name>/AGENT.md`, one directory per agent, so a new
|
|
119
|
+
agent is usable without a reload. Frontmatter
|
|
117
120
|
carries `description`, `model` (`provider/id`) and `thinking`, and optionally
|
|
118
121
|
`auto-exit` (`true` or `false`, default `true`; see [Auto-exit](#auto-exit)),
|
|
119
122
|
`exclude-tools`, `tools`, `extensions` and `fallback`. The body is the delegate's system prompt,
|
|
@@ -173,8 +176,23 @@ add one.
|
|
|
173
176
|
|
|
174
177
|
## Configuration
|
|
175
178
|
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
`<agent dir>/squire/` holds the roster and the optional parent guidance file.
|
|
180
|
+
`delegation_prompt.md` is Markdown, inserted unchanged as the
|
|
181
|
+
`squire_parent_delegation` system-prompt section before each new run, including
|
|
182
|
+
a run started by an idle delegate result. It is not a conversation or user
|
|
183
|
+
message, is not duplicated in the `delegate` tool description or guidelines,
|
|
184
|
+
and is not sent to the child. The guidance is added only while the `delegate`
|
|
185
|
+
tool is active and not hidden; when delegation is inactive or hidden, the
|
|
186
|
+
section is removed and tool selection is left as it was. A missing or whitespace-only
|
|
187
|
+
file adds no section. Editing the file replaces the section at the next run;
|
|
188
|
+
deleting it removes the section. A read failure other than a missing file
|
|
189
|
+
warns with the path, removes stale guidance for that run, and leaves otherwise
|
|
190
|
+
valid delegation working. The package ships no default delegation or approval
|
|
191
|
+
policy.
|
|
192
|
+
|
|
193
|
+
Refresh happens before a new run, not before every model request in a tool
|
|
194
|
+
loop, and the extension does not watch the filesystem. `pi-squire` reads no
|
|
195
|
+
settings file and no environment variable of its own. `PI_DELEGATE_PARENT` and `PI_DELEGATE_AUTO_EXIT` are set
|
|
178
196
|
for the child, never read as configuration by the parent. The window starts
|
|
179
197
|
with the tmux server's environment plus only these from the parent:
|
|
180
198
|
`PI_CODING_AGENT_DIR`, `PI_CODING_AGENT_SESSION_DIR` (when the parent has it
|
|
@@ -213,13 +231,18 @@ and its excluded tools.
|
|
|
213
231
|
|
|
214
232
|
The model picks an agent from these lines without reading files. The
|
|
215
233
|
description also tells the model to turn `auto_exit` off when it means to keep
|
|
216
|
-
talking to the delegate by mail after its result.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
234
|
+
talking to the delegate by mail after its result. It says a task must stand
|
|
235
|
+
alone, that results arrive automatically, and that the parent should keep
|
|
236
|
+
working or end its turn rather than poll or send a blocking mail request
|
|
237
|
+
merely to wait; a substantive follow-up remains fine. The description is
|
|
238
|
+
rebuilt at session start and before each new run when the exposed roster
|
|
239
|
+
changes, so an edit, addition or removal shows up in the run being prepared,
|
|
240
|
+
including a run woken by an idle delegate result. Unchanged metadata is not
|
|
241
|
+
re-registered. Pi cannot list the session's tools while extensions load, so
|
|
242
|
+
the description built then skips the `exclude-tools` check, and the one built
|
|
243
|
+
after startup applies it. The roster itself is still read at call time, and an
|
|
244
|
+
unknown agent, model or thinking level is refused with close matches and
|
|
245
|
+
nothing starts.
|
|
223
246
|
|
|
224
247
|
`delegation_status` reports one state per delegation:
|
|
225
248
|
|
|
@@ -393,9 +416,9 @@ The parent resolves each delegation's auto-exit from the call
|
|
|
393
416
|
notice: typed or RPC input, or a run the user stopped (Esc).
|
|
394
417
|
- `/auto-exit` inside the delegate turns it on again for the next normal
|
|
395
418
|
completion, whatever the launch said.
|
|
396
|
-
- The footer shows that this is a delegate and whether auto-exit
|
|
397
|
-
`👦 delegate · auto-exit on`, or
|
|
398
|
-
`👦 delegate · auto-exit off`, with 👦 or 👧
|
|
419
|
+
- The footer shows that this is a delegate, its agent, and whether auto-exit
|
|
420
|
+
is on: `👦 worker delegate · auto-exit on`, or
|
|
421
|
+
`👦 worker delegate · auto-exit off`, with 👦 or 👧
|
|
399
422
|
picked at random per session.
|
|
400
423
|
|
|
401
424
|
There is no `subagent_done` tool: auto-exit off only means the window stays.
|
|
@@ -485,7 +508,7 @@ has been sent and just before it opens the delegate's window.
|
|
|
485
508
|
| --- | --- |
|
|
486
509
|
| `args` | the child `pi`'s complete argument list, launcher first |
|
|
487
510
|
| `env` | the child's environment, as `KEY`/`value` pairs |
|
|
488
|
-
| `agent` | the agent's name, as in `<agent dir>/agents/<name>/AGENT.md`; read it, do not change it |
|
|
511
|
+
| `agent` | the agent's name, as in `<agent dir>/squire/agents/<name>/AGENT.md`; read it, do not change it |
|
|
489
512
|
|
|
490
513
|
Listeners may only append to `args` and add keys to `env`; there is no veto,
|
|
491
514
|
and `pi-squire` passes both to tmux unchanged. Do all work synchronously: the
|
|
@@ -520,10 +543,6 @@ to the provider.
|
|
|
520
543
|
Inside a delegate, `pi-squire` also takes over the task: a `request` whose
|
|
521
544
|
`from` is the parent's address. It sets `handled` and sends the task as a user
|
|
522
545
|
prompt labelled `[delegate]`, so the run starts the way a typed prompt does.
|
|
523
|
-
Pi 1.0.0 starts a turn for an injected custom message without preparing the
|
|
524
|
-
system prompt, so a fresh delegate's first request would otherwise carry no
|
|
525
|
-
context files, skills or role paragraph
|
|
526
|
-
([earendil-works/pi#5581](https://github.com/earendil-works/pi/issues/5581)).
|
|
527
546
|
The provider still arms the automatic answer for a request a listener took
|
|
528
547
|
over. Every other message to the delegate is left to the provider. `pi-squire` reads `from` and requires
|
|
529
548
|
it to be the sender's address, which for a delegate is its session id; a reply
|
|
@@ -549,6 +568,16 @@ written just before a window closed is never mistaken for no reply. **If
|
|
|
549
568
|
because a waiting reply cannot be ruled out. The provider's README carries the
|
|
550
569
|
contract.
|
|
551
570
|
|
|
571
|
+
## Testing limits
|
|
572
|
+
|
|
573
|
+
The automated tests drive the extension through a fake Pi session. They check
|
|
574
|
+
the registered tool definitions, the structured prompt options a simulated run
|
|
575
|
+
preparation receives, file handling, visibility gating and the existing launch
|
|
576
|
+
and result paths. They do not prove actual Pi event scheduling, final provider
|
|
577
|
+
requests, compaction, cache behavior, or model obedience. The idle-result wake
|
|
578
|
+
is simulated by dispatching the next preparation; the suite does not assert
|
|
579
|
+
that the runtime emits it.
|
|
580
|
+
|
|
552
581
|
## Compatibility
|
|
553
582
|
|
|
554
583
|
While pi-squire is below 1.0, breaking any contract below bumps the minor
|
|
@@ -565,7 +594,9 @@ Other extensions may rely on:
|
|
|
565
594
|
- the `/delegate` syntax and flags;
|
|
566
595
|
- the `[delegate]` result text and the closed-without-a-result text;
|
|
567
596
|
- `AGENT.md`'s frontmatter keys and its location,
|
|
568
|
-
`<agent dir>/agents/<name>/AGENT.md`;
|
|
597
|
+
`<agent dir>/squire/agents/<name>/AGENT.md`;
|
|
598
|
+
- the optional `<agent dir>/squire/delegation_prompt.md` file and the
|
|
599
|
+
`squire_parent_delegation` system-prompt section;
|
|
569
600
|
- the child environment variables `PI_DELEGATE_PARENT`,
|
|
570
601
|
`PI_DELEGATE_AUTO_EXIT` and `PI_UNATTENDED=1`.
|
|
571
602
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-squire",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
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",
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@earendil-works/pi-coding-agent": "
|
|
47
|
+
"@earendil-works/pi-coding-agent": ">=1.3.0",
|
|
48
48
|
"@earendil-works/pi-tui": "*",
|
|
49
49
|
"typebox": "*"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@biomejs/biome": "2.5.14",
|
|
53
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
54
|
-
"@earendil-works/pi-tui": "1.
|
|
53
|
+
"@earendil-works/pi-coding-agent": "1.3.0",
|
|
54
|
+
"@earendil-works/pi-tui": "1.3.0",
|
|
55
55
|
"@types/node": "26.6.3",
|
|
56
56
|
"typebox": "1.3.27",
|
|
57
57
|
"typescript": "7.0.2"
|
package/src/agents.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The parent's agent roster: `<agent dir>/agents/<name>/AGENT.md`, read at
|
|
1
|
+
// The parent's agent roster: `<agent dir>/squire/agents/<name>/AGENT.md`, read at
|
|
2
2
|
// call time so a new agent is usable without a reload. Frontmatter carries
|
|
3
3
|
// `description`, `model` and `thinking`, and optionally `auto-exit` (default
|
|
4
4
|
// true) and `exclude-tools` (tools the delegate goes without, passed to its Pi
|
|
@@ -179,14 +179,14 @@ function flag(value: unknown): boolean | undefined | null {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
|
-
* Read every agent under `<agentDir>/agents/`, in name order. A directory
|
|
182
|
+
* Read every agent under `<agentDir>/squire/agents/`, in name order. A directory
|
|
183
183
|
* with no `AGENT.md` is not an agent and is passed over quietly; an
|
|
184
184
|
* `AGENT.md` that cannot be used is warned about and left out. `tools` are
|
|
185
185
|
* the session's tool names, which `exclude-tools` is checked against;
|
|
186
186
|
* undefined skips that check, as before the session's tools are known.
|
|
187
187
|
*/
|
|
188
188
|
export function readRoster(agentDir: string, tools?: readonly string[]): Roster {
|
|
189
|
-
const dir = join(agentDir, "agents");
|
|
189
|
+
const dir = join(agentDir, "squire", "agents");
|
|
190
190
|
let names: string[];
|
|
191
191
|
try {
|
|
192
192
|
names = readdirSync(dir).sort();
|
package/src/child.ts
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
// edxeth/pi-subagents' `auto-exit` (README "Child lifecycle", `cf6dbf4`).
|
|
14
14
|
//
|
|
15
15
|
// It also takes over the parent's task request on `message:inbound` and sends
|
|
16
|
-
// it as a user prompt
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
16
|
+
// it as a user prompt, so the task starts through the same path as a typed
|
|
17
|
+
// prompt, labelled `[delegate]`. Pi 1.2.0 already prepares the system prompt
|
|
18
|
+
// for custom-message runs (earendil-works/pi#5581); this takeover remains the
|
|
19
|
+
// package's pinned startup delivery path. The provider still arms the
|
|
20
|
+
// automatic answer for a request a listener took over.
|
|
21
21
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
22
22
|
|
|
23
23
|
import { activeMark, classify, type Response, recordLimit } from "./limits.ts";
|
|
@@ -27,6 +27,8 @@ import { STATUS_KEY } from "./results.ts";
|
|
|
27
27
|
export const AUTO_EXIT_ENV = "PI_DELEGATE_AUTO_EXIT";
|
|
28
28
|
/** The launch variable listing, comma-separated, the agent's models after the one launched. */
|
|
29
29
|
export const FALLBACK_ENV = "PI_DELEGATE_FALLBACK";
|
|
30
|
+
/** The launch variable naming the roster agent this delegate runs as, for its footer. */
|
|
31
|
+
export const AGENT_ENV = "PI_DELEGATE_AGENT";
|
|
30
32
|
|
|
31
33
|
const OFF_NOTICE =
|
|
32
34
|
"delegate: auto-exit is off for this session because you took over here; the window stays open. /auto-exit turns it back on.";
|
|
@@ -122,11 +124,13 @@ const ICON = Math.random() < 0.5 ? "👦" : "👧";
|
|
|
122
124
|
|
|
123
125
|
function showStatus(ctx: ExtensionContext, armed: boolean): void {
|
|
124
126
|
const { theme } = ctx.ui;
|
|
127
|
+
const agent = process.env[AGENT_ENV];
|
|
128
|
+
const label = agent === undefined || agent === "" ? "delegate" : `${agent} delegate`;
|
|
125
129
|
ctx.ui.setStatus(
|
|
126
130
|
STATUS_KEY,
|
|
127
131
|
armed
|
|
128
|
-
? `${ICON}
|
|
129
|
-
: `${ICON}
|
|
132
|
+
? `${ICON} ${label} · auto-exit ${theme.fg("success", "on")}`
|
|
133
|
+
: `${ICON} ${label} · auto-exit ${theme.fg("dim", "off")}`,
|
|
130
134
|
);
|
|
131
135
|
}
|
|
132
136
|
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// `/delegate <agent> [--model <provider/id>] [--thinking <level>]
|
|
5
5
|
// [--label <name>] [--auto-exit | --no-auto-exit] <task…>`
|
|
6
|
-
// reads the agent from `<agent dir>/agents/<name>/AGENT.md`, writes the task
|
|
6
|
+
// reads the agent from `<agent dir>/squire/agents/<name>/AGENT.md`, writes the task
|
|
7
7
|
// to the delegate's inbox through `message:send`, emits `session:launch` so
|
|
8
8
|
// listeners can add arguments and environment, then opens a window in the
|
|
9
9
|
// parent's working directory running the parent's own Pi. The window and the
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// The `session:launch` contract this package provides, and the `message:*`
|
|
35
35
|
// hooks it consumes, live in this package's README.
|
|
36
36
|
import { randomUUID } from "node:crypto";
|
|
37
|
-
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
37
|
+
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
38
38
|
import { tmpdir } from "node:os";
|
|
39
39
|
import { basename, join } from "node:path";
|
|
40
40
|
|
|
@@ -47,13 +47,17 @@ import {
|
|
|
47
47
|
import { Type } from "typebox";
|
|
48
48
|
|
|
49
49
|
import { type Agent, isThinking, type Roster, readRoster, THINKING_LEVELS } from "./agents.ts";
|
|
50
|
-
import { AUTO_EXIT_ENV, FALLBACK_ENV, registerChild } from "./child.ts";
|
|
50
|
+
import { AGENT_ENV, AUTO_EXIT_ENV, FALLBACK_ENV, registerChild } from "./child.ts";
|
|
51
51
|
import { activeMark, clearMarks, isFree, readMarks } from "./limits.ts";
|
|
52
52
|
import { createResults, delegateName } from "./results.ts";
|
|
53
53
|
import { createTmuxClient, type TmuxClient } from "./tmux.ts";
|
|
54
54
|
import { createTracking, toolResult } from "./tracking.ts";
|
|
55
55
|
import { createProactive, createUsageClient, type Proactive, type UsageClient } from "./usage.ts";
|
|
56
56
|
|
|
57
|
+
/** The optional parent guidance file under `<agent dir>/squire/`. */
|
|
58
|
+
const PARENT_PROMPT = "delegation_prompt.md";
|
|
59
|
+
/** The parent system-prompt section this extension owns. */
|
|
60
|
+
const PARENT_SECTION = "squire_parent_delegation";
|
|
57
61
|
/** The hook a provider of `message:*` answers with the written request. */
|
|
58
62
|
const SEND = "message:send";
|
|
59
63
|
/** The hook emitted just before a delegate's window opens. */
|
|
@@ -325,13 +329,33 @@ function thinkingProblem(value: string): string | undefined {
|
|
|
325
329
|
}`;
|
|
326
330
|
}
|
|
327
331
|
|
|
332
|
+
/** Read `<agent dir>/squire/delegation_prompt.md`; missing or whitespace-only means no guidance. */
|
|
333
|
+
function readParentPrompt(): { text?: string; error?: string } {
|
|
334
|
+
const path = join(getAgentDir(), "squire", PARENT_PROMPT);
|
|
335
|
+
try {
|
|
336
|
+
const text = readFileSync(path, "utf8");
|
|
337
|
+
return text.trim() === "" ? {} : { text };
|
|
338
|
+
} catch (err) {
|
|
339
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return {};
|
|
340
|
+
return {
|
|
341
|
+
error: `could not read ${path}: ${(err as Error).message}; parent delegation guidance is off for this run`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Whether this run exposes `delegate` after tool selection and hidden declarations. */
|
|
347
|
+
function delegateVisible(options: { selectedTools: string[]; hiddenTools: string[] }): boolean {
|
|
348
|
+
return options.selectedTools.includes("delegate") && !options.hiddenTools.includes("delegate");
|
|
349
|
+
}
|
|
350
|
+
|
|
328
351
|
/** The `delegate` tool's description: what delegation does, and the roster as it stands. */
|
|
329
352
|
function toolDescription(agents: readonly Agent[]): string {
|
|
330
353
|
const intro =
|
|
331
|
-
"Hand a self-contained task to a delegate Pi session in a background tmux window, so it works while you do not. Each call starts the delegation at once; the delegate runs in this session's working directory and agent root and reports back later as one message. While it runs, keep working or end your turn: its result starts your next turn on its own
|
|
354
|
+
"Hand a self-contained task to a delegate Pi session in a background tmux window, so it works while you do not. Each call starts the delegation at once; the delegate runs in this session's working directory and agent root and reports back later as one message. While it runs, keep working or end your turn: its result starts your next turn on its own. Do not poll delegation_status or send a blocking mail request merely to wait for the result; send a substantive follow-up only when you have something to add or ask. Use it when the work is self-contained and you would rather keep your own context for it.";
|
|
332
355
|
if (agents.length === 0) {
|
|
333
356
|
return `${intro}\n\nNo agents are defined yet. Add ${join(
|
|
334
357
|
getAgentDir(),
|
|
358
|
+
"squire",
|
|
335
359
|
"agents",
|
|
336
360
|
"<name>",
|
|
337
361
|
"AGENT.md",
|
|
@@ -411,6 +435,7 @@ export default function delegate(
|
|
|
411
435
|
throw new Error(
|
|
412
436
|
`no agents are defined; add ${join(
|
|
413
437
|
getAgentDir(),
|
|
438
|
+
"squire",
|
|
414
439
|
"agents",
|
|
415
440
|
"<name>",
|
|
416
441
|
"AGENT.md",
|
|
@@ -485,6 +510,7 @@ export default function delegate(
|
|
|
485
510
|
PI_CODING_AGENT_DIR: getAgentDir(),
|
|
486
511
|
[PARENT_ENV]: parent,
|
|
487
512
|
[AUTO_EXIT_ENV]: start.autoExit ? "1" : "0",
|
|
513
|
+
[AGENT_ENV]: start.agent.name,
|
|
488
514
|
// A general signal for any extension: no human watches this session, so don't wait on a dialog.
|
|
489
515
|
PI_UNATTENDED: "1",
|
|
490
516
|
};
|
|
@@ -541,11 +567,13 @@ export default function delegate(
|
|
|
541
567
|
}
|
|
542
568
|
}
|
|
543
569
|
|
|
544
|
-
/** The
|
|
545
|
-
|
|
570
|
+
/** The roster description last registered, so unchanged metadata is not registered again. */
|
|
571
|
+
let registeredDescription: string | undefined;
|
|
572
|
+
|
|
573
|
+
const delegateTool = (description: string) => ({
|
|
546
574
|
name: "delegate",
|
|
547
575
|
label: "Delegate",
|
|
548
|
-
description
|
|
576
|
+
description,
|
|
549
577
|
parameters: Type.Object({
|
|
550
578
|
agent: Type.String({ description: "Roster agent name to run." }),
|
|
551
579
|
task: Type.String({
|
|
@@ -619,11 +647,32 @@ export default function delegate(
|
|
|
619
647
|
},
|
|
620
648
|
});
|
|
621
649
|
|
|
650
|
+
const registerDelegate = (description: string): void => {
|
|
651
|
+
pi.registerTool(delegateTool(description) as never);
|
|
652
|
+
registeredDescription = description;
|
|
653
|
+
};
|
|
654
|
+
|
|
622
655
|
pi.on("session_start", (_event, ctx) => {
|
|
623
656
|
registry = ctx.modelRegistry;
|
|
624
657
|
tracking.restore(ctx);
|
|
625
|
-
// The roster is read at call time; the description
|
|
626
|
-
|
|
658
|
+
// The roster is read at call time; the description is rebuilt here and before each run.
|
|
659
|
+
registerDelegate(toolDescription(currentRoster().agents));
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// One roster/prompt snapshot prepares this run: refresh the description only when
|
|
663
|
+
// it changed, and replace only this extension's prompt section from the same snapshot.
|
|
664
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
665
|
+
const description = toolDescription(currentRoster().agents);
|
|
666
|
+
const prompt = readParentPrompt();
|
|
667
|
+
const visible = delegateVisible(event.systemPromptOptions);
|
|
668
|
+
// Report a read failure even when guidance cannot be used, so it does not go unseen.
|
|
669
|
+
if (prompt.error !== undefined) ctx.ui.notify(`delegate: ${prompt.error}`, "warning");
|
|
670
|
+
if (visible && description !== registeredDescription) registerDelegate(description);
|
|
671
|
+
if (!visible || prompt.error !== undefined || prompt.text === undefined) {
|
|
672
|
+
delete event.systemPromptOptions.sections[PARENT_SECTION];
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
event.systemPromptOptions.sections[PARENT_SECTION] = prompt.text;
|
|
627
676
|
});
|
|
628
677
|
|
|
629
678
|
pi.on("session_shutdown", () => {
|
|
@@ -680,6 +729,6 @@ export default function delegate(
|
|
|
680
729
|
},
|
|
681
730
|
});
|
|
682
731
|
|
|
683
|
-
|
|
732
|
+
registerDelegate(toolDescription(currentRoster().agents));
|
|
684
733
|
tracking.registerTools();
|
|
685
734
|
}
|