pi-pstack-adapter 0.3.0 → 0.4.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 +8 -0
- package/README.md +23 -5
- package/VERSION +1 -1
- package/adapter-skills/setup.md +9 -1
- package/docs/compatibility.md +64 -17
- package/index.ts +23 -7
- package/package.json +1 -1
- package/src/activation.ts +41 -24
- package/src/agents.ts +36 -8
- package/src/capabilities.ts +56 -31
- package/src/certified.ts +3 -3
- package/src/config.ts +17 -1
- package/src/delegation.ts +143 -0
- package/src/generate.ts +200 -20
- package/src/registry.ts +5 -2
- package/src/status.ts +40 -0
- package/src/types.ts +9 -0
- package/src/upstream.ts +19 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here. release-please writes new entries from Conventional Commit PR titles.
|
|
4
4
|
|
|
5
|
+
## [0.4.0](https://github.com/elderfo/pi-pstack-adapter/compare/v0.3.0...v0.4.0) (2026-10-14)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **delegation:** delegate through T3 Code's delegate_task, with pi-subagents as fallback ([#11](https://github.com/elderfo/pi-pstack-adapter/issues/11)) ([4f9c8e8](https://github.com/elderfo/pi-pstack-adapter/commit/4f9c8e8bef690885c37cb0d215473db11a5e9049))
|
|
11
|
+
* **pstack:** certify pstack 0.15.23 with in-your-own-words, ketchup, and poteto-help ([#9](https://github.com/elderfo/pi-pstack-adapter/issues/9)) ([73bb412](https://github.com/elderfo/pi-pstack-adapter/commit/73bb4125fcd470e5f8990e1953e7de80a05a0485))
|
|
12
|
+
|
|
5
13
|
## [0.3.0](https://github.com/elderfo/pi-pstack-adapter/compare/v0.2.0...v0.3.0) (2026-10-05)
|
|
6
14
|
|
|
7
15
|
|
package/README.md
CHANGED
|
@@ -46,17 +46,34 @@ counts as Linux.
|
|
|
46
46
|
|
|
47
47
|
### Add optional providers for delegating workflows
|
|
48
48
|
|
|
49
|
-
Some workflows hand work to subagents or ask you multiple-choice questions.
|
|
50
|
-
|
|
49
|
+
Some workflows hand work to subagents or ask you multiple-choice questions. Two providers can run
|
|
50
|
+
the subagents:
|
|
51
|
+
|
|
52
|
+
- **T3 Code.** When Pi runs inside [T3 Code](https://github.com/pingdotgg/t3code), workflows
|
|
53
|
+
delegate through T3's `delegate_task` tool. Each child appears in T3, can run on any provider
|
|
54
|
+
and model T3 offers, and can work in its own worktree. You install nothing extra.
|
|
55
|
+
- **pi-subagents.** Outside T3 Code, install `pi-subagents`. The upstream agents register as
|
|
56
|
+
`pistack-comment-sicko` and `pistack-poteto-agent`.
|
|
57
|
+
|
|
58
|
+
For multiple-choice questions, install `pi-ask-user`:
|
|
51
59
|
|
|
52
60
|
```bash
|
|
53
61
|
pi install npm:pi-subagents
|
|
54
62
|
pi install npm:pi-ask-user
|
|
55
63
|
```
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
`
|
|
59
|
-
|
|
65
|
+
The adapter uses T3 Code first and falls back to `pi-subagents`. To reverse that order, set
|
|
66
|
+
`"delegation": "pi-subagents"` in the adapter config, or set `PISTACK_DELEGATION=pi-subagents`.
|
|
67
|
+
Restart Pi or run `/reload` after changing it. `/pistack-status` shows the order and the
|
|
68
|
+
provider the session resolved.
|
|
69
|
+
|
|
70
|
+
The adapter counts T3's tools, including `task_status` and `orchestrator_capabilities`, only
|
|
71
|
+
when T3 Code's own bridge registered them. A tool that another extension or MCP server registers
|
|
72
|
+
under the same name is ignored, and `/pistack-check <skill>` names the one tool to delegate with.
|
|
73
|
+
|
|
74
|
+
When a delegating workflow starts, the adapter turns on that provider's tool, even if you turned
|
|
75
|
+
it off in the tool selector. This happens
|
|
76
|
+
whether you run the workflow's `/skill:` command or the agent opens the skill itself. While Poteto
|
|
60
77
|
Mode is on, the adapter turns it on for every prompt.
|
|
61
78
|
|
|
62
79
|
To find missing providers or executables before you start a workflow, run
|
|
@@ -172,6 +189,7 @@ settings to `.pi/pi-pstack-adapter.json`. Environment variables override both fi
|
|
|
172
189
|
| `PISTACK_ALLOW_BOOTSTRAP` | Allows a noninteractive first download. |
|
|
173
190
|
| `PISTACK_CACHE_DIR` | Changes the cache directory. |
|
|
174
191
|
| `PISTACK_MODELS` | Assigns models as comma-separated `role=provider/model` pairs. |
|
|
192
|
+
| `PISTACK_DELEGATION` | Sets the delegation provider to try first: `t3` (default) or `pi-subagents`. The config file key is `delegation`. |
|
|
175
193
|
|
|
176
194
|
## Security and trust
|
|
177
195
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.4.0
|
package/adapter-skills/setup.md
CHANGED
|
@@ -4,7 +4,7 @@ Upstream pstack ships a `setup-pstack` skill that writes `~/.cursor/rules/pstack
|
|
|
4
4
|
a Cursor-only always-applied rule full of Cursor model slugs. Pi has neither that file nor
|
|
5
5
|
those models, so this adapter replaces the body with the Pi equivalent.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Three things are configurable. Model roles, the upstream source, and the delegation order.
|
|
8
8
|
|
|
9
9
|
## Model roles
|
|
10
10
|
|
|
@@ -61,6 +61,14 @@ plain words before you call it, and report a refusal as a refusal.
|
|
|
61
61
|
|
|
62
62
|
Regeneration happens on the next Pi start. Tell the user to restart Pi, or run `/reload`.
|
|
63
63
|
|
|
64
|
+
## Delegation order
|
|
65
|
+
|
|
66
|
+
Delegating workflows try T3 Code's `delegate_task` first, then pi-subagents' `subagent`. A user
|
|
67
|
+
who wants `subagent` first sets `"delegation": "pi-subagents"` in the adapter config file, or
|
|
68
|
+
`PISTACK_DELEGATION=pi-subagents`. The adapter tool has no action for this. Tell the user which
|
|
69
|
+
file to edit: the `config layers` row of the status report lists them. Regeneration happens on
|
|
70
|
+
the next Pi start.
|
|
71
|
+
|
|
64
72
|
## Finish
|
|
65
73
|
|
|
66
74
|
End by reporting, in your own words, which roles were set, which still inherit, the resolved
|
package/docs/compatibility.md
CHANGED
|
@@ -4,7 +4,7 @@ The compatibility registry in `src/registry.ts` assigns one support tier to ever
|
|
|
4
4
|
certified pstack revision. Generation reads it, the wrapper frontmatter publishes it, and
|
|
5
5
|
`/pistack-status` reports it.
|
|
6
6
|
|
|
7
|
-
Certified revision: pstack `0.15.
|
|
7
|
+
Certified revision: pstack `0.15.23` at `dbf49813a39c1a5389c5e28cb8e4c1c2379e2454` in
|
|
8
8
|
`https://github.com/cursor/plugins`.
|
|
9
9
|
|
|
10
10
|
## Platforms
|
|
@@ -21,21 +21,37 @@ executables in the shell the skills run in, with Pi's `shellCommandPrefix` appli
|
|
|
21
21
|
that matters most, because Git Bash adds `/usr/bin` and `/mingw64/bin` to the `PATH` Pi
|
|
22
22
|
started with. A shell that is missing, inactive, or cannot run a command blocks every workflow.
|
|
23
23
|
|
|
24
|
-
Windows evidence at the certified revision, on Windows 11 with Git for
|
|
25
|
-
and Pi 1.0
|
|
26
|
-
|
|
27
|
-
- The unit suite
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
|
|
24
|
+
Windows evidence at the certified revision, pstack `0.15.23`, on Windows 11 with Git for
|
|
25
|
+
Windows 2.53, Node 22, Bun 1.3, and Pi 1.2.0:
|
|
26
|
+
|
|
27
|
+
- The unit suite and the type check pass. `npm run test:isolated` passes from Git Bash: Pi
|
|
28
|
+
registers all 54 skills, and a later run reuses the cache without the network. CI runs the
|
|
29
|
+
unit suite on Windows for every change.
|
|
30
|
+
- The certification probe passes on an LF export of the tree: no diagnostics, 52 bodies
|
|
31
|
+
byte-identical, and none mismatched. The adapter's cache checkout matches that export byte for
|
|
32
|
+
byte.
|
|
33
|
+
- `in-your-own-words`, `ketchup`, and `poteto-help` ran end to end in the isolated install, with
|
|
34
|
+
no `subagent` or `ask_user` tool. `ketchup` reported a commit and a pending decision with
|
|
35
|
+
Mermaid diagrams and changed nothing. `poteto-help` answered with `/pistack-mode on` and read
|
|
36
|
+
the guide from the `plugin directory` row, whose Windows path it resolved.
|
|
37
|
+
- The status report's `plugin directory` and `generated skills` rows give native Windows paths.
|
|
38
|
+
`/pistack-check` clears `ketchup`, and still reports `uptime` missing for `benchmark-checklist`.
|
|
39
|
+
- The helpers changed in `0.15.23` ran in Git Bash. `worktree-audit.sh` handled `-b <ref>`,
|
|
40
|
+
`--help`, an unknown base, and the `origin/HEAD` fallback, and classified a linked worktree.
|
|
41
|
+
`check-plan.mjs` ran on Node. Upstream's helper suite passed 61 of 63 tests under Bun. The two
|
|
42
|
+
failures fake the Graphite `gt` CLI with a shell script, which Bun cannot spawn on Windows.
|
|
43
|
+
Both fail at `0.15.9` too, and the adapter does not gate on `gt`.
|
|
44
|
+
- At pstack `0.15.9`, `show-me-your-work/scripts/log.sh`, `watch-pr`, and `orch.ts` also ran
|
|
45
|
+
in Git Bash. None of them changed in a way that adds an executable or a path assumption.
|
|
32
46
|
- Git for Windows bundles `git`, `curl`, `nproc`, and the POSIX tools the skill bodies call,
|
|
33
47
|
but not `uptime`, so `benchmark-checklist` reports it missing. `gh`, `tailscale`, and `sudo`
|
|
34
48
|
are separate installs on Windows, as on the other platforms.
|
|
35
49
|
- The adapter checks out upstream with `core.autocrlf=false`. Git for Windows enables
|
|
36
50
|
`autocrlf` system-wide, which would otherwise rewrite every upstream file to CRLF. A cache
|
|
37
51
|
written before that setting existed is repaired on the next start, or refused with a recovery
|
|
38
|
-
step when it cannot be written.
|
|
52
|
+
step when it cannot be written. `git archive` follows the same setting, so a certification
|
|
53
|
+
export on Windows needs `-c core.autocrlf=false`. Without it, 155 upstream text files at
|
|
54
|
+
`0.15.23` come out as CRLF and no longer match what Pi loads.
|
|
39
55
|
|
|
40
56
|
A session that replaces the `bash` tool with `powershell` cannot run the skills.
|
|
41
57
|
`/pistack-status` reports it, and `/pistack-check` refuses to clear any workflow.
|
|
@@ -66,11 +82,12 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
|
|
|
66
82
|
|
|
67
83
|
## Registry
|
|
68
84
|
|
|
69
|
-
### native (
|
|
85
|
+
### native (31)
|
|
70
86
|
|
|
71
87
|
| upstream skill | Pi skill | capabilities | executables | why |
|
|
72
88
|
| --- | --- | --- | --- | --- |
|
|
73
89
|
| `bro` | `/skill:pistack-bro` | - | - | - |
|
|
90
|
+
| `in-your-own-words` | `/skill:pistack-in-your-own-words` | - | - | - |
|
|
74
91
|
| `principle-attack-the-premise` | `/skill:pistack-principle-attack-the-premise` | - | - | - |
|
|
75
92
|
| `principle-boundary-discipline` | `/skill:pistack-principle-boundary-discipline` | - | - | - |
|
|
76
93
|
| `principle-build-the-lever` | `/skill:pistack-principle-build-the-lever` | - | - | - |
|
|
@@ -101,11 +118,12 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
|
|
|
101
118
|
| `typescript-best-practices` | `/skill:pistack-typescript-best-practices` | - | - | - |
|
|
102
119
|
| `unslop` | `/skill:pistack-unslop` | - | - | - |
|
|
103
120
|
|
|
104
|
-
### adapted (
|
|
121
|
+
### adapted (2)
|
|
105
122
|
|
|
106
123
|
| upstream skill | Pi skill | capabilities | executables | why |
|
|
107
124
|
| --- | --- | --- | --- | --- |
|
|
108
125
|
| `create-verification-skill` | `/skill:pistack-create-verification-skill` | - | - | Only the generated skill's output path needs remapping to Pi's skill directory. |
|
|
126
|
+
| `ketchup` | `/skill:pistack-ketchup` | - | - | Read-only. Pi has no `/visualize` skill or image generation, so its visuals are Mermaid diagrams and screenshots that already exist. It checks PR and CI state only for work the chat touched. |
|
|
109
127
|
|
|
110
128
|
### dependency-gated (13)
|
|
111
129
|
|
|
@@ -120,16 +138,17 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
|
|
|
120
138
|
| `how` | `/skill:pistack-how` | `delegation` | - | Every path spawns explorer or explainer subagents; nothing runs inline. |
|
|
121
139
|
| `interrogate` | `/skill:pistack-interrogate` | `delegation` | `git` | Multi-model adversarial review is entirely one subagent per configured model. |
|
|
122
140
|
| `maintain-verification-skill` | `/skill:pistack-maintain-verification-skill` | `delegation` | - | Source wave launches one read-only subagent per feature file. |
|
|
123
|
-
| `no-comments` | `/skill:pistack-no-comments` | `delegation` | `git` | Step 1 must spawn the
|
|
141
|
+
| `no-comments` | `/skill:pistack-no-comments` | `delegation` | `git` | Step 1 must spawn the comment-sicko agent; the skill only triages its report. |
|
|
124
142
|
| `show-me-your-work` | `/skill:pistack-show-me-your-work` | `delegation` | - | The mandatory cross-model review of the trail requires spawning a different-family subagent. |
|
|
125
143
|
| `teach` | `/skill:pistack-teach` | `delegation` | - | It runs the how and why skills, both of which need subagents. |
|
|
126
144
|
| `why` | `/skill:pistack-why` | `delegation` | `git`, `gh` | Default posture spawns one investigator subagent per evidence category plus a synthesizer. |
|
|
127
145
|
|
|
128
|
-
### experimental (
|
|
146
|
+
### experimental (6)
|
|
129
147
|
|
|
130
148
|
| upstream skill | Pi skill | capabilities | executables | why |
|
|
131
149
|
| --- | --- | --- | --- | --- |
|
|
132
150
|
| `automate-me` | `/skill:pistack-automate-me` | `delegation`, `structured-question` | `git`, `gh` | Mining step depends on Cursor transcript files and the built-in create-skill skill, with no Pi equivalent. |
|
|
151
|
+
| `poteto-help` | `/skill:pistack-poteto-help` | - | - | Its answers describe Cursor setup, the slash menu, and Custom Modes. A request for work runs poteto-mode, which is experimental. It asks a multiple-choice question only when the need is unclear. Its README and guide links resolve under the status report's plugin directory. |
|
|
133
152
|
| `poteto-mode` | `/skill:pistack-poteto-mode` | `delegation`, `structured-question` | `git`, `gh` | Sticky-mode hosting plus references to Cursor built-in and plugin skills only partially map to Pi. |
|
|
134
153
|
| `recall` | `/skill:pistack-recall` | `delegation` | `git`, `gh` | Core corpus is Cursor's on-disk transcript JSONL, so only the git/gh and shared-record half survives. |
|
|
135
154
|
| `reflect` | `/skill:pistack-reflect` | `delegation` | - | Transcript mining and create-skill routing are Cursor-specific, though the digest fallback partly works. |
|
|
@@ -145,12 +164,40 @@ Lowering a tier needs one reproduction of the failure and a note saying what bro
|
|
|
145
164
|
|
|
146
165
|
| upstream agent | Pi agent | notes |
|
|
147
166
|
| --- | --- | --- |
|
|
148
|
-
| `comment-sicko` | `pistack-comment-sicko` |
|
|
149
|
-
| `poteto-agent` | `pistack-poteto-agent` | Upstream `is_background: true` has no runtime equivalent; the caller chooses `async` on the `
|
|
167
|
+
| `comment-sicko` | `pistack-comment-sicko` | Upstream names it `comment-sicko` since pstack 0.15.23, so the registered description starts with that name. Needs `git` to scope a diff. |
|
|
168
|
+
| `poteto-agent` | `pistack-poteto-agent` | Upstream `is_background: true` has no runtime equivalent; the caller chooses `async: true` on `subagent`, or `mode: "async"` on `delegate_task`. Upstream tells it to search `~/.cursor/plugins/` when its parent gives no path. The adapter puts the generated `pistack-poteto-mode/SKILL.md` path in every agent prompt and forbids that search, which would load content outside the certified revision. |
|
|
169
|
+
|
|
170
|
+
T3 Code's `delegate_task` has no named agents. Generation writes each agent's prompt to
|
|
171
|
+
`adapter/agents/<pi-name>.md`, with skill paths relative to that file. The parent begins the
|
|
172
|
+
child's task by telling it to read that file as its system prompt.
|
|
150
173
|
|
|
151
|
-
Both register through the `pi-subagents:runtime-agent-register:v1` contract. Without an owner for
|
|
174
|
+
Both agents also register through the `pi-subagents:runtime-agent-register:v1` contract. Without an owner for
|
|
152
175
|
that event, the status report says so rather than dropping them silently.
|
|
153
176
|
|
|
177
|
+
## Delegation providers
|
|
178
|
+
|
|
179
|
+
The `delegation` capability is satisfied by T3 Code's `mcp__t3-code__delegate_task` or by
|
|
180
|
+
`pi-subagents`' `subagent`, in the order the `delegation` setting gives, T3 first by default.
|
|
181
|
+
A hidden registration does not count. A T3 tool counts only when the extension at
|
|
182
|
+
`T3_PI_MCP_EXTENSION_PATH`, the bridge T3 Code launches, registered it, and only when it accepts
|
|
183
|
+
`task`, `target`, and `mode`. Status and activation share that resolution, so the report always
|
|
184
|
+
names the backend a workflow gets. The adapter activates only that backend. For T3 it also
|
|
185
|
+
activates `task_status` and `orchestrator_capabilities` when the bridge registered them.
|
|
186
|
+
A delegating wrapper tells the model to read the tool from `/pistack-check` and to use no other,
|
|
187
|
+
so a rejected tool that is still in the session is not used. The check also names the companion
|
|
188
|
+
tools the model may call, and forbids any companion it could not trace to T3 Code.
|
|
189
|
+
|
|
190
|
+
An agent prompt file wraps the upstream body in the same markers as a skill wrapper, with host
|
|
191
|
+
precedence before and after it. The closing note says that everything after the opening notes is
|
|
192
|
+
upstream content, even text that imitates a marker. An upstream skill or agent body that contains
|
|
193
|
+
marker text is reported as a warning, with its bytes unchanged. An agent whose Pi name breaks the
|
|
194
|
+
skill name rules is skipped with an error, before its name reaches a file name or any generated
|
|
195
|
+
text.
|
|
196
|
+
|
|
197
|
+
Unit tests cover detection, activation order, and the generated instructions for both providers.
|
|
198
|
+
No tier changed when T3 support arrived, because no delegating workflow has yet run end to end
|
|
199
|
+
through `delegate_task`. That run is the evidence the tier rules above require.
|
|
200
|
+
|
|
154
201
|
## Skills the adapter replaces
|
|
155
202
|
|
|
156
203
|
`setup-pstack` writes a Cursor `.mdc` rule and nothing else, so the adapter substitutes its own
|
package/index.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { Type } from "typebox";
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { registerDelegationActivation } from "./src/activation.ts";
|
|
9
|
-
import { registerAgents, type RuntimeRegistration } from "./src/agents.ts";
|
|
9
|
+
import { registerAgents, unregisteredAgentDiagnostics, type RuntimeRegistration } from "./src/agents.ts";
|
|
10
|
+
import { DEFAULT_DELEGATION } from "./src/delegation.ts";
|
|
10
11
|
import { checkPrerequisites, detectCapabilities, type ToolDescriptor } from "./src/capabilities.ts";
|
|
11
12
|
import {
|
|
12
13
|
type AdapterConfig,
|
|
@@ -26,7 +27,7 @@ import { entryFor } from "./src/registry.ts";
|
|
|
26
27
|
import { platformSupport, renderCheck, renderStatus, shellDiagnostics, type ShellState } from "./src/status.ts";
|
|
27
28
|
import { BootstrapRequiredError, resolveRefToCommit, resolveSource } from "./src/upstream.ts";
|
|
28
29
|
import { normalizeRepo } from "./src/config.ts";
|
|
29
|
-
import type { CapabilityStatus, Diagnostic, GenerationResult, ResolvedSource } from "./src/types.ts";
|
|
30
|
+
import type { CapabilityStatus, Diagnostic, GeneratedAgent, GenerationResult, ResolvedSource } from "./src/types.ts";
|
|
30
31
|
|
|
31
32
|
const PACKAGE_ROOT = dirname(fileURLToPath(import.meta.url));
|
|
32
33
|
const ADAPTER_VERSION = readAdapterVersion();
|
|
@@ -45,6 +46,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
45
46
|
let startupError: Error | undefined;
|
|
46
47
|
let registrations: readonly RuntimeRegistration[] = [];
|
|
47
48
|
let agentDiagnostics: readonly Diagnostic[] = [];
|
|
49
|
+
let unregisteredAgents: readonly GeneratedAgent[] = [];
|
|
48
50
|
let modeActive = false;
|
|
49
51
|
const commandNamespace = startupNamespace();
|
|
50
52
|
|
|
@@ -77,6 +79,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
77
79
|
|
|
78
80
|
const result = registerAgents(pi.events, state.generation.agents, state.config.namespace);
|
|
79
81
|
registrations = result.registrations;
|
|
82
|
+
unregisteredAgents = result.unregistered;
|
|
80
83
|
const diagnostics: Diagnostic[] = [...result.diagnostics];
|
|
81
84
|
if (result.registrations.length > 0) {
|
|
82
85
|
diagnostics.push({
|
|
@@ -100,6 +103,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
100
103
|
registerDelegationActivation(pi, {
|
|
101
104
|
skills: () => state?.generation.skills ?? [],
|
|
102
105
|
modeActive: () => modeActive && state !== undefined,
|
|
106
|
+
preferred: () => state?.config.delegation ?? DEFAULT_DELEGATION,
|
|
103
107
|
});
|
|
104
108
|
|
|
105
109
|
pi.on("before_agent_start", async (event) => {
|
|
@@ -202,21 +206,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
202
206
|
if (!state) return "The pstack adapter has not resolved its upstream checkout yet.";
|
|
203
207
|
|
|
204
208
|
switch (params.action) {
|
|
205
|
-
case "status":
|
|
209
|
+
case "status": {
|
|
210
|
+
const current = capabilities();
|
|
211
|
+
const delegation = current.find((capability) => capability.id === "delegation");
|
|
206
212
|
return renderStatus({
|
|
207
213
|
adapterVersion: ADAPTER_VERSION,
|
|
208
214
|
config: state.config,
|
|
209
215
|
source: state.source,
|
|
210
216
|
upstreamVersion: state.upstreamVersion,
|
|
211
217
|
generation: state.generation,
|
|
212
|
-
capabilities:
|
|
218
|
+
capabilities: current,
|
|
213
219
|
platform: platformSupport(platform()),
|
|
214
220
|
platformName: platform(),
|
|
215
221
|
modeActive,
|
|
216
222
|
cacheRoot: state.cacheRoot,
|
|
217
223
|
bootstrapped: state.bootstrapped,
|
|
218
|
-
runtimeDiagnostics: [
|
|
224
|
+
runtimeDiagnostics: [
|
|
225
|
+
...agentDiagnostics,
|
|
226
|
+
// Judged now, because T3's tools can connect after the agents were registered.
|
|
227
|
+
...unregisteredAgentDiagnostics(unregisteredAgents, delegation?.backend, state.generation.agentPromptsDir),
|
|
228
|
+
...shellDiagnostics(probedShellState(ctx, pi.getActiveTools())),
|
|
229
|
+
],
|
|
219
230
|
});
|
|
231
|
+
}
|
|
220
232
|
case "check":
|
|
221
233
|
return checkSkill(params.skill ?? "", ctx);
|
|
222
234
|
case "models":
|
|
@@ -231,7 +243,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
231
243
|
}
|
|
232
244
|
|
|
233
245
|
function capabilities(): CapabilityStatus[] {
|
|
234
|
-
return detectCapabilities(pi.getAllTools() as unknown as ToolDescriptor[]);
|
|
246
|
+
return detectCapabilities(pi.getAllTools() as unknown as ToolDescriptor[], state?.config.delegation);
|
|
235
247
|
}
|
|
236
248
|
|
|
237
249
|
function checkSkill(requested: string, ctx: ExtensionContext): string {
|
|
@@ -250,10 +262,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
250
262
|
const executables = probeExecutables(skill.executables, usable ? () => sessionBashContext(ctx) : undefined);
|
|
251
263
|
const shell: ShellState = { ...resolved, runError: executables.shellFailure };
|
|
252
264
|
const shellProblems = shellDiagnostics(shell).map((diagnostic) => `${diagnostic.message} ${diagnostic.action ?? ""}`.trim());
|
|
265
|
+
const current = capabilities();
|
|
253
266
|
return renderCheck(
|
|
254
267
|
skill,
|
|
255
268
|
entryFor(skill.upstreamName, skill.generatedName.slice(prefix.length)),
|
|
256
|
-
checkPrerequisites(skill.capabilities,
|
|
269
|
+
checkPrerequisites(skill.capabilities, current, skill.executables, executables.has, shellProblems),
|
|
270
|
+
current.find((capability) => capability.id === "delegation"),
|
|
257
271
|
);
|
|
258
272
|
}
|
|
259
273
|
|
|
@@ -336,6 +350,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
336
350
|
}
|
|
337
351
|
}
|
|
338
352
|
registrations = [];
|
|
353
|
+
unregisteredAgents = [];
|
|
339
354
|
}
|
|
340
355
|
}
|
|
341
356
|
|
|
@@ -418,6 +433,7 @@ function buildState(
|
|
|
418
433
|
namespace: config.namespace,
|
|
419
434
|
cacheRoot: root,
|
|
420
435
|
adapterVersion: ADAPTER_VERSION,
|
|
436
|
+
delegation: config.delegation,
|
|
421
437
|
// A local checkout has uncommitted edits the digest cannot see, so never reuse its output.
|
|
422
438
|
force: source.selection.kind === "local",
|
|
423
439
|
});
|
package/package.json
CHANGED
package/src/activation.ts
CHANGED
|
@@ -2,23 +2,26 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { posix, win32 } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_DELEGATION,
|
|
7
|
+
type DelegationHost,
|
|
8
|
+
type ToolPresence,
|
|
9
|
+
callableTool,
|
|
10
|
+
delegationHost,
|
|
11
|
+
resolveDelegation,
|
|
12
|
+
} from "./delegation.ts";
|
|
13
|
+
import type { DelegationProvider, GeneratedSkill } from "./types.ts";
|
|
11
14
|
|
|
12
15
|
/** The slice of Pi's tool registry that activation reads and writes. */
|
|
13
16
|
export interface ToolLoadout {
|
|
14
|
-
getAllTools(): readonly
|
|
17
|
+
getAllTools(): readonly ToolPresence[];
|
|
15
18
|
getActiveTools(): string[];
|
|
16
19
|
setActiveTools(toolNames: string[]): void;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export type ActivationResult =
|
|
20
|
-
| { readonly kind: "already-active" }
|
|
21
|
-
| { readonly kind: "activated" }
|
|
23
|
+
| { readonly kind: "already-active"; readonly backend: DelegationProvider }
|
|
24
|
+
| { readonly kind: "activated"; readonly backend: DelegationProvider }
|
|
22
25
|
| { readonly kind: "unavailable" };
|
|
23
26
|
|
|
24
27
|
export function needsDelegation(skill: GeneratedSkill): boolean {
|
|
@@ -54,37 +57,51 @@ export function skillFromReadPath(
|
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
/**
|
|
57
|
-
* Adds
|
|
58
|
-
* delegation skill never depends on the model
|
|
59
|
-
*
|
|
60
|
+
* Adds the delegation backend that capability detection resolves to the active tools, with its
|
|
61
|
+
* companion tools, so a delegation skill never depends on the model loading it first. For
|
|
62
|
+
* pi-subagents that is the selection `subagents_enable` makes; T3's `delegate_task` may be
|
|
63
|
+
* deferred behind `tool_search`. Pi applies the new loadout on the next model request.
|
|
60
64
|
*/
|
|
61
|
-
export function activateDelegation(
|
|
62
|
-
|
|
65
|
+
export function activateDelegation(
|
|
66
|
+
loadout: ToolLoadout,
|
|
67
|
+
preferred: DelegationProvider = DEFAULT_DELEGATION,
|
|
68
|
+
host: DelegationHost = delegationHost(),
|
|
69
|
+
): ActivationResult {
|
|
70
|
+
const registered = loadout.getAllTools();
|
|
71
|
+
const resolution = resolveDelegation(registered, preferred, host);
|
|
72
|
+
if (resolution.kind === "unavailable") return { kind: "unavailable" };
|
|
73
|
+
const { backend, companions } = resolution;
|
|
74
|
+
const wanted = [backend.tool, ...companions];
|
|
63
75
|
const active = loadout.getActiveTools();
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
76
|
+
const missing = wanted.filter((name) => !active.includes(name));
|
|
77
|
+
if (missing.length === 0) return { kind: "already-active", backend: backend.id };
|
|
78
|
+
loadout.setActiveTools([...active, ...missing]);
|
|
79
|
+
return { kind: "activated", backend: backend.id };
|
|
67
80
|
}
|
|
68
81
|
|
|
69
82
|
export interface ActivationSources {
|
|
70
83
|
/** Generated skills, or none while the adapter has no upstream checkout. */
|
|
71
84
|
readonly skills: () => readonly GeneratedSkill[];
|
|
72
85
|
readonly modeActive: () => boolean;
|
|
86
|
+
/** The configured backend preference, read at activation time. */
|
|
87
|
+
readonly preferred: () => DelegationProvider;
|
|
88
|
+
/** Defaults to the running process's T3 Code bridge, if any. */
|
|
89
|
+
readonly host?: DelegationHost;
|
|
73
90
|
}
|
|
74
91
|
|
|
75
92
|
type ActivationHost = Pick<ExtensionAPI, "on" | "getAllTools" | "getActiveTools" | "setActiveTools">;
|
|
76
93
|
|
|
77
94
|
/**
|
|
78
|
-
* A delegation skill must not depend on the model remembering to
|
|
79
|
-
* so a slash command, a model-initiated SKILL.md read, and Poteto Mode each activate
|
|
80
|
-
* `subagent` in code.
|
|
95
|
+
* A delegation skill must not depend on the model remembering to load its delegation tool,
|
|
96
|
+
* so a slash command, a model-initiated SKILL.md read, and Poteto Mode each activate it in code.
|
|
81
97
|
*/
|
|
82
98
|
export function registerDelegationActivation(pi: ActivationHost, sources: ActivationSources): void {
|
|
99
|
+
const activate = () => activateDelegation(pi, sources.preferred(), sources.host ?? delegationHost());
|
|
83
100
|
// `input` runs before Pi copies the tool list for the prompt, so the first model request
|
|
84
|
-
// already carries
|
|
101
|
+
// already carries the tool. It also covers messages queued while the agent is streaming.
|
|
85
102
|
pi.on("input", async (event) => {
|
|
86
103
|
const skill = skillFromPrompt(event.text, sources.skills());
|
|
87
|
-
if (sources.modeActive() || (skill && needsDelegation(skill)))
|
|
104
|
+
if (sources.modeActive() || (skill && needsDelegation(skill))) activate();
|
|
88
105
|
});
|
|
89
106
|
|
|
90
107
|
pi.on("tool_call", async (event, ctx) => {
|
|
@@ -92,12 +109,12 @@ export function registerDelegationActivation(pi: ActivationHost, sources: Activa
|
|
|
92
109
|
const path = (event.input as { path?: unknown }).path;
|
|
93
110
|
if (typeof path !== "string") return;
|
|
94
111
|
const skill = skillFromReadPath(path, ctx.cwd, sources.skills());
|
|
95
|
-
if (skill && needsDelegation(skill))
|
|
112
|
+
if (skill && needsDelegation(skill)) activate();
|
|
96
113
|
});
|
|
97
114
|
|
|
98
115
|
// Backstop for prompts that reach the agent without an `input` event.
|
|
99
116
|
pi.on("before_agent_start", async () => {
|
|
100
|
-
if (sources.modeActive())
|
|
117
|
+
if (sources.modeActive()) activate();
|
|
101
118
|
});
|
|
102
119
|
}
|
|
103
120
|
|
package/src/agents.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { T3_DELEGATE_TOOL } from "./delegation.ts";
|
|
3
|
+
import type { DelegationProvider, Diagnostic, GeneratedAgent } from "./types.ts";
|
|
2
4
|
|
|
3
5
|
export const RUNTIME_AGENT_REGISTER_EVENT = "pi-subagents:runtime-agent-register:v1";
|
|
4
6
|
|
|
@@ -19,6 +21,9 @@ export interface EventBus {
|
|
|
19
21
|
|
|
20
22
|
export interface AgentRegistrationResult {
|
|
21
23
|
readonly registrations: readonly RuntimeRegistration[];
|
|
24
|
+
/** Agents no installed extension accepted as named agents. */
|
|
25
|
+
readonly unregistered: readonly GeneratedAgent[];
|
|
26
|
+
/** Registrations a provider refused. */
|
|
22
27
|
readonly diagnostics: readonly Diagnostic[];
|
|
23
28
|
}
|
|
24
29
|
|
|
@@ -32,6 +37,7 @@ export function registerAgents(
|
|
|
32
37
|
namespace: string,
|
|
33
38
|
): AgentRegistrationResult {
|
|
34
39
|
const registrations: RuntimeRegistration[] = [];
|
|
40
|
+
const unregistered: GeneratedAgent[] = [];
|
|
35
41
|
const diagnostics: Diagnostic[] = [];
|
|
36
42
|
|
|
37
43
|
for (const agent of agents) {
|
|
@@ -45,12 +51,7 @@ export function registerAgents(
|
|
|
45
51
|
};
|
|
46
52
|
bus.emit(RUNTIME_AGENT_REGISTER_EVENT, request);
|
|
47
53
|
if (!request.result) {
|
|
48
|
-
|
|
49
|
-
level: "warning",
|
|
50
|
-
resource: `agent:${agent.upstreamName}`,
|
|
51
|
-
message: `No installed delegation provider accepted \`${agent.generatedName}\`, so pstack workflows cannot delegate to it.`,
|
|
52
|
-
action: `Install a provider that owns ${RUNTIME_AGENT_REGISTER_EVENT}, for example \`pi install npm:pi-subagents\`.`,
|
|
53
|
-
});
|
|
54
|
+
unregistered.push(agent);
|
|
54
55
|
continue;
|
|
55
56
|
}
|
|
56
57
|
if (!request.result.ok) {
|
|
@@ -65,5 +66,32 @@ export function registerAgents(
|
|
|
65
66
|
registrations.push(request.result.registration);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
return { registrations, diagnostics };
|
|
69
|
+
return { registrations, unregistered, diagnostics };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* T3 Code children have no named agents and load an agent from its prompt file, so a missing
|
|
74
|
+
* pi-subagents registration only blocks delegation when T3 is not the resolved backend.
|
|
75
|
+
*/
|
|
76
|
+
export function unregisteredAgentDiagnostics(
|
|
77
|
+
unregistered: readonly GeneratedAgent[],
|
|
78
|
+
backend: DelegationProvider | undefined,
|
|
79
|
+
promptsDir: string,
|
|
80
|
+
): Diagnostic[] {
|
|
81
|
+
return unregistered.map((agent) => {
|
|
82
|
+
const promptPath = join(promptsDir, `${agent.generatedName}.md`);
|
|
83
|
+
if (backend === "t3") {
|
|
84
|
+
return {
|
|
85
|
+
level: "info",
|
|
86
|
+
resource: `agent:${agent.upstreamName}`,
|
|
87
|
+
message: `\`${agent.generatedName}\` is not a named agent here. T3 Code children run it by reading ${promptPath} as their system prompt.`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
level: "warning",
|
|
92
|
+
resource: `agent:${agent.upstreamName}`,
|
|
93
|
+
message: `No installed delegation provider accepted \`${agent.generatedName}\` as a named agent, so pstack workflows cannot delegate to it through \`subagent\`.`,
|
|
94
|
+
action: `Install a provider that owns ${RUNTIME_AGENT_REGISTER_EVENT}, for example \`pi install npm:pi-subagents\`, or run Pi inside T3 Code, whose \`${T3_DELEGATE_TOOL}\` loads the agent from ${promptPath}.`,
|
|
95
|
+
};
|
|
96
|
+
});
|
|
69
97
|
}
|