pi-pstack-adapter 0.3.0 → 0.5.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 +15 -0
- package/README.md +38 -6
- package/VERSION +1 -1
- package/adapter-skills/setup.md +48 -6
- package/docs/compatibility.md +64 -17
- package/index.ts +61 -24
- 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 +189 -8
- package/src/delegation.ts +143 -0
- package/src/echo.ts +9 -0
- package/src/generate.ts +212 -20
- package/src/namespace.ts +3 -1
- package/src/registry.ts +5 -2
- package/src/status.ts +51 -8
- package/src/types.ts +9 -0
- package/src/upstream.ts +19 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
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.5.0](https://github.com/elderfo/pi-pstack-adapter/compare/v0.4.0...v0.5.0) (2026-10-15)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **models:** add a per-role thinking level ([#12](https://github.com/elderfo/pi-pstack-adapter/issues/12)) ([ae99d0c](https://github.com/elderfo/pi-pstack-adapter/commit/ae99d0cb9ab6c516a6847eb47ea44a0b5ec7c141))
|
|
11
|
+
|
|
12
|
+
## [0.4.0](https://github.com/elderfo/pi-pstack-adapter/compare/v0.3.0...v0.4.0) (2026-10-14)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **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))
|
|
18
|
+
* **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))
|
|
19
|
+
|
|
5
20
|
## [0.3.0](https://github.com/elderfo/pi-pstack-adapter/compare/v0.2.0...v0.3.0) (2026-10-05)
|
|
6
21
|
|
|
7
22
|
|
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
|
|
@@ -133,6 +150,20 @@ In a noninteractive session, set roles with `PISTACK_MODELS`:
|
|
|
133
150
|
PISTACK_MODELS='feature=provider/model,bug-fix=provider/other-model' pi
|
|
134
151
|
```
|
|
135
152
|
|
|
153
|
+
Add a thinking level to a role with a `:level` suffix. The levels are `off`, `minimal`, `low`,
|
|
154
|
+
`medium`, `high`, `xhigh`, and `max`:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
PISTACK_MODELS='feature=provider/model:medium,judgment=provider/other-model:high' pi
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Role names are lowercase letters, digits, and hyphens. A model value is printable ASCII with no spaces or markup characters, at most 128 characters. A config entry that breaks these rules stops the adapter at startup with an error that names the file, so fix the key there and restart Pi.
|
|
161
|
+
|
|
162
|
+
A role without a level passes none, so the delegation backend applies its own default. A role you leave unset inherits the
|
|
163
|
+
session model and the session thinking level, so a session at high thinking makes every
|
|
164
|
+
inheriting delegate expensive. The `pstack_adapter` tool's `set_model` action takes the same
|
|
165
|
+
level in its `thinking` parameter. A level the model does not offer is clamped by Pi at run time.
|
|
166
|
+
|
|
136
167
|
## Run Pi noninteractively
|
|
137
168
|
|
|
138
169
|
A noninteractive session, such as `pi --print`, cannot answer the download question. To allow
|
|
@@ -171,7 +202,8 @@ settings to `.pi/pi-pstack-adapter.json`. Environment variables override both fi
|
|
|
171
202
|
| `PISTACK_UPSTREAM_PLUGIN_PATH` | Selects the plugin directory inside the checkout. |
|
|
172
203
|
| `PISTACK_ALLOW_BOOTSTRAP` | Allows a noninteractive first download. |
|
|
173
204
|
| `PISTACK_CACHE_DIR` | Changes the cache directory. |
|
|
174
|
-
| `PISTACK_MODELS` | Assigns models as comma-separated `role=provider/model` pairs. |
|
|
205
|
+
| `PISTACK_MODELS` | Assigns models as comma-separated `role=provider/model[:thinking]` pairs. |
|
|
206
|
+
| `PISTACK_DELEGATION` | Sets the delegation provider to try first: `t3` (default) or `pi-subagents`. The config file key is `delegation`. |
|
|
175
207
|
|
|
176
208
|
## Security and trust
|
|
177
209
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.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
|
|
|
@@ -27,6 +27,13 @@ Roles used by pstack workflows.
|
|
|
27
27
|
The status report lists the same roles, so read it back rather than trusting this table if the
|
|
28
28
|
adapter has been upgraded.
|
|
29
29
|
|
|
30
|
+
Each role also has a thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or
|
|
31
|
+
`max`. Upstream pstack calls this a reasoning budget and encodes it in the model slug. A role with
|
|
32
|
+
no thinking level passes none, so the delegation backend applies its own default. A role left on
|
|
33
|
+
`inherit` uses the parent session's model and thinking level, so a parent at high thinking makes
|
|
34
|
+
every inheriting child expensive. Model choice often affects cost more than thinking level does,
|
|
35
|
+
so consider a cheaper model for the bulk roles as well.
|
|
36
|
+
|
|
30
37
|
Steps.
|
|
31
38
|
|
|
32
39
|
1. Call the adapter tool with `action: "models"` to list the provider and model ids this Pi
|
|
@@ -34,13 +41,40 @@ Steps.
|
|
|
34
41
|
2. Ask the user which model each role should use. Offer the listed ids, and offer
|
|
35
42
|
`inherit` for any role they do not care about. Use the structured question tool when it is
|
|
36
43
|
available; otherwise ask in plain text. Only ids from step 1 are accepted.
|
|
37
|
-
3.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
3. Ask for a thinking level. Do not assume the user wants none. Skip this step only when every
|
|
45
|
+
role is on `inherit`. It applies to every role that got a model in step 2. A role on
|
|
46
|
+
`inherit` cannot have a level, so tell the user that and say that those roles follow the
|
|
47
|
+
parent session's level. Ask one single-select question first. Put the whole choice in each option
|
|
48
|
+
title so the user can pick without opening anything else, for example "Balanced: code roles
|
|
49
|
+
medium, judgment high, strongest-judgment xhigh (suggested)".
|
|
50
|
+
|
|
51
|
+
| choice | code roles (`feature`, `refactoring`, `bug-fix`, `perf-issue`, `hillclimb`) | `judgment` | `strongest-judgment` |
|
|
52
|
+
| --- | --- | --- | --- |
|
|
53
|
+
| Balanced (suggest this first) | `medium` | `high` | `xhigh` |
|
|
54
|
+
| Low cost | `low` | `medium` | `high` |
|
|
55
|
+
| Maximum quality | `high` | `xhigh` | `max` |
|
|
56
|
+
| Backend default | none | none | none |
|
|
57
|
+
| Custom | ask once per column, as a single-select of level names | | |
|
|
58
|
+
|
|
59
|
+
These are starting points, not measurements. They keep the heaviest reasoning on the roles
|
|
60
|
+
that make the hardest calls. Offer Custom for a user who wants different levels. Ask once for
|
|
61
|
+
the code roles, once for `judgment`, and once for `strongest-judgment`. Ask a role on its own
|
|
62
|
+
only when the user says it should differ from its group. Backend default passes no level and
|
|
63
|
+
leaves the choice to the delegation backend. A Custom user who wants thinking disabled picks
|
|
64
|
+
`off`.
|
|
65
|
+
A model may not offer every level. A level it lacks is clamped when it runs, so say that
|
|
66
|
+
instead of asking again. The tool rejects any level other than `off` on a model that has no reasoning support.
|
|
67
|
+
When it does, set that role with no level and tell the user which role has none.
|
|
68
|
+
4. For each role, call the adapter tool with `action: "set_model"`, the role, the chosen
|
|
69
|
+
`provider/model` id, and `thinking` when step 3 picked a level. Pass `scope: "project"` when
|
|
70
|
+
the user wants the choice to apply only to this repository. Calling `set_model` for a role
|
|
71
|
+
again without `thinking` clears a level set earlier, so always pass it again.
|
|
72
|
+
5. Read back `action: "status"` and show the user what was written, including the thinking
|
|
73
|
+
column.
|
|
41
74
|
|
|
42
75
|
For a scripted environment, set `PISTACK_MODELS` instead, for example
|
|
43
|
-
`PISTACK_MODELS=feature=anthropic/claude-sonnet-4-5,bug-fix=openai/gpt-5`. The
|
|
76
|
+
`PISTACK_MODELS=feature=anthropic/claude-sonnet-4-5:medium,bug-fix=openai/gpt-5:high`. The
|
|
77
|
+
optional `:level` suffix sets the thinking level. The environment wins
|
|
44
78
|
over both config files, so CI never needs this dialog.
|
|
45
79
|
|
|
46
80
|
## Upstream source
|
|
@@ -61,6 +95,14 @@ plain words before you call it, and report a refusal as a refusal.
|
|
|
61
95
|
|
|
62
96
|
Regeneration happens on the next Pi start. Tell the user to restart Pi, or run `/reload`.
|
|
63
97
|
|
|
98
|
+
## Delegation order
|
|
99
|
+
|
|
100
|
+
Delegating workflows try T3 Code's `delegate_task` first, then pi-subagents' `subagent`. A user
|
|
101
|
+
who wants `subagent` first sets `"delegation": "pi-subagents"` in the adapter config file, or
|
|
102
|
+
`PISTACK_DELEGATION=pi-subagents`. The adapter tool has no action for this. Tell the user which
|
|
103
|
+
file to edit: the `config layers` row of the status report lists them. Regeneration happens on
|
|
104
|
+
the next Pi start.
|
|
105
|
+
|
|
64
106
|
## Finish
|
|
65
107
|
|
|
66
108
|
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,19 +6,27 @@ 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,
|
|
13
14
|
type ConfigFile,
|
|
14
15
|
type ConfigLayer,
|
|
16
|
+
THINKING_LEVELS,
|
|
15
17
|
environmentLayer,
|
|
18
|
+
invalidRoleMessage,
|
|
19
|
+
isValidModelSpec,
|
|
20
|
+
isValidRole,
|
|
21
|
+
joinModelSpec,
|
|
16
22
|
mergeConfig,
|
|
23
|
+
planModelAssignment,
|
|
17
24
|
readConfigFile,
|
|
18
25
|
trustWarning,
|
|
19
26
|
updateConfigFile,
|
|
20
27
|
} from "./src/config.ts";
|
|
21
28
|
import { generate } from "./src/generate.ts";
|
|
29
|
+
import { safeEcho } from "./src/echo.ts";
|
|
22
30
|
import { MODE_ENTRY_TYPE, modeFromEntries, modeInstruction } from "./src/mode.ts";
|
|
23
31
|
import { CONFIG_FILE_NAME, agentDir, cacheRoot } from "./src/paths.ts";
|
|
24
32
|
import { bashToolContext, probeExecutables } from "./src/executables.ts";
|
|
@@ -26,7 +34,7 @@ import { entryFor } from "./src/registry.ts";
|
|
|
26
34
|
import { platformSupport, renderCheck, renderStatus, shellDiagnostics, type ShellState } from "./src/status.ts";
|
|
27
35
|
import { BootstrapRequiredError, resolveRefToCommit, resolveSource } from "./src/upstream.ts";
|
|
28
36
|
import { normalizeRepo } from "./src/config.ts";
|
|
29
|
-
import type { CapabilityStatus, Diagnostic, GenerationResult, ResolvedSource } from "./src/types.ts";
|
|
37
|
+
import type { CapabilityStatus, Diagnostic, GeneratedAgent, GenerationResult, ResolvedSource } from "./src/types.ts";
|
|
30
38
|
|
|
31
39
|
const PACKAGE_ROOT = dirname(fileURLToPath(import.meta.url));
|
|
32
40
|
const ADAPTER_VERSION = readAdapterVersion();
|
|
@@ -45,6 +53,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
45
53
|
let startupError: Error | undefined;
|
|
46
54
|
let registrations: readonly RuntimeRegistration[] = [];
|
|
47
55
|
let agentDiagnostics: readonly Diagnostic[] = [];
|
|
56
|
+
let unregisteredAgents: readonly GeneratedAgent[] = [];
|
|
48
57
|
let modeActive = false;
|
|
49
58
|
const commandNamespace = startupNamespace();
|
|
50
59
|
|
|
@@ -54,9 +63,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
54
63
|
startupError = undefined;
|
|
55
64
|
modeActive = modeFromEntries(ctx.sessionManager.getBranch() as never[]);
|
|
56
65
|
|
|
57
|
-
const config = loadConfig(ctx);
|
|
58
66
|
try {
|
|
59
|
-
state = await bringUpUpstream(
|
|
67
|
+
state = await bringUpUpstream(loadConfig(ctx), ctx);
|
|
60
68
|
} catch (error) {
|
|
61
69
|
startupError = error as Error;
|
|
62
70
|
if (ctx.hasUI) ctx.ui.notify(`pstack adapter: ${(error as Error).message}`, "error");
|
|
@@ -77,6 +85,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
77
85
|
|
|
78
86
|
const result = registerAgents(pi.events, state.generation.agents, state.config.namespace);
|
|
79
87
|
registrations = result.registrations;
|
|
88
|
+
unregisteredAgents = result.unregistered;
|
|
80
89
|
const diagnostics: Diagnostic[] = [...result.diagnostics];
|
|
81
90
|
if (result.registrations.length > 0) {
|
|
82
91
|
diagnostics.push({
|
|
@@ -100,6 +109,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
100
109
|
registerDelegationActivation(pi, {
|
|
101
110
|
skills: () => state?.generation.skills ?? [],
|
|
102
111
|
modeActive: () => modeActive && state !== undefined,
|
|
112
|
+
preferred: () => state?.config.delegation ?? DEFAULT_DELEGATION,
|
|
103
113
|
});
|
|
104
114
|
|
|
105
115
|
pi.on("before_agent_start", async (event) => {
|
|
@@ -131,6 +141,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
131
141
|
skill: Type.Optional(Type.String({ description: "Skill name for `check`, with or without the namespace prefix" })),
|
|
132
142
|
role: Type.Optional(Type.String({ description: "pstack model role for `set_model`" })),
|
|
133
143
|
model: Type.Optional(Type.String({ description: "`provider/model` id, or `inherit` to clear the role" })),
|
|
144
|
+
thinking: Type.Optional(
|
|
145
|
+
Type.String({
|
|
146
|
+
description: `Thinking level for \`set_model\`: ${THINKING_LEVELS.join(", ")}. Omit to pass no level, which also clears a level set earlier.`,
|
|
147
|
+
}),
|
|
148
|
+
),
|
|
134
149
|
repo: Type.Optional(Type.String({ description: "Upstream repository URL for `set_source`" })),
|
|
135
150
|
ref: Type.Optional(Type.String({ description: "Branch, tag, or commit for `set_source`" })),
|
|
136
151
|
localPath: Type.Optional(Type.String({ description: "Local pstack checkout for `set_source`" })),
|
|
@@ -191,6 +206,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
191
206
|
skill?: string;
|
|
192
207
|
role?: string;
|
|
193
208
|
model?: string;
|
|
209
|
+
thinking?: string;
|
|
194
210
|
repo?: string;
|
|
195
211
|
ref?: string;
|
|
196
212
|
localPath?: string;
|
|
@@ -202,27 +218,35 @@ export default function (pi: ExtensionAPI) {
|
|
|
202
218
|
if (!state) return "The pstack adapter has not resolved its upstream checkout yet.";
|
|
203
219
|
|
|
204
220
|
switch (params.action) {
|
|
205
|
-
case "status":
|
|
221
|
+
case "status": {
|
|
222
|
+
const current = capabilities();
|
|
223
|
+
const delegation = current.find((capability) => capability.id === "delegation");
|
|
206
224
|
return renderStatus({
|
|
207
225
|
adapterVersion: ADAPTER_VERSION,
|
|
208
226
|
config: state.config,
|
|
209
227
|
source: state.source,
|
|
210
228
|
upstreamVersion: state.upstreamVersion,
|
|
211
229
|
generation: state.generation,
|
|
212
|
-
capabilities:
|
|
230
|
+
capabilities: current,
|
|
213
231
|
platform: platformSupport(platform()),
|
|
214
232
|
platformName: platform(),
|
|
215
233
|
modeActive,
|
|
216
234
|
cacheRoot: state.cacheRoot,
|
|
217
235
|
bootstrapped: state.bootstrapped,
|
|
218
|
-
runtimeDiagnostics: [
|
|
236
|
+
runtimeDiagnostics: [
|
|
237
|
+
...agentDiagnostics,
|
|
238
|
+
// Judged now, because T3's tools can connect after the agents were registered.
|
|
239
|
+
...unregisteredAgentDiagnostics(unregisteredAgents, delegation?.backend, state.generation.agentPromptsDir),
|
|
240
|
+
...shellDiagnostics(probedShellState(ctx, pi.getActiveTools())),
|
|
241
|
+
],
|
|
219
242
|
});
|
|
243
|
+
}
|
|
220
244
|
case "check":
|
|
221
245
|
return checkSkill(params.skill ?? "", ctx);
|
|
222
246
|
case "models":
|
|
223
247
|
return renderModels(ctx);
|
|
224
248
|
case "set_model":
|
|
225
|
-
return setModel(params.role, params.model, params.scope ?? "user", ctx);
|
|
249
|
+
return setModel(params.role, params.model, params.thinking, params.scope ?? "user", ctx);
|
|
226
250
|
case "set_source":
|
|
227
251
|
return setSource(params, params.scope ?? "user", ctx);
|
|
228
252
|
default:
|
|
@@ -231,7 +255,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
231
255
|
}
|
|
232
256
|
|
|
233
257
|
function capabilities(): CapabilityStatus[] {
|
|
234
|
-
return detectCapabilities(pi.getAllTools() as unknown as ToolDescriptor[]);
|
|
258
|
+
return detectCapabilities(pi.getAllTools() as unknown as ToolDescriptor[], state?.config.delegation);
|
|
235
259
|
}
|
|
236
260
|
|
|
237
261
|
function checkSkill(requested: string, ctx: ExtensionContext): string {
|
|
@@ -242,7 +266,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
242
266
|
(candidate) => candidate.generatedName === requested || candidate.generatedName === `${prefix}${bare}`,
|
|
243
267
|
);
|
|
244
268
|
if (!skill) {
|
|
245
|
-
return `No pstack skill named ${
|
|
269
|
+
return `No pstack skill named ${safeEcho(requested)}. Run the adapter tool with action "status" for the full list.`;
|
|
246
270
|
}
|
|
247
271
|
const resolved = shellState(ctx, pi.getActiveTools());
|
|
248
272
|
// An inactive bash tool or an unresolved shell is already a blocker, so do not spawn anything.
|
|
@@ -250,37 +274,48 @@ export default function (pi: ExtensionAPI) {
|
|
|
250
274
|
const executables = probeExecutables(skill.executables, usable ? () => sessionBashContext(ctx) : undefined);
|
|
251
275
|
const shell: ShellState = { ...resolved, runError: executables.shellFailure };
|
|
252
276
|
const shellProblems = shellDiagnostics(shell).map((diagnostic) => `${diagnostic.message} ${diagnostic.action ?? ""}`.trim());
|
|
277
|
+
const current = capabilities();
|
|
253
278
|
return renderCheck(
|
|
254
279
|
skill,
|
|
255
280
|
entryFor(skill.upstreamName, skill.generatedName.slice(prefix.length)),
|
|
256
|
-
checkPrerequisites(skill.capabilities,
|
|
281
|
+
checkPrerequisites(skill.capabilities, current, skill.executables, executables.has, shellProblems),
|
|
282
|
+
current.find((capability) => capability.id === "delegation"),
|
|
257
283
|
);
|
|
258
284
|
}
|
|
259
285
|
|
|
260
286
|
function renderModels(ctx: ExtensionContext): string {
|
|
261
287
|
const scoped = ctx.scopedModels ?? [];
|
|
262
288
|
const models = scoped.length > 0 ? scoped.map((entry) => entry.model) : ctx.modelRegistry.getAvailable();
|
|
263
|
-
const ids = models.map((model) => `${model.provider}/${model.id}`).sort();
|
|
289
|
+
const ids = models.map((model) => `${model.provider}/${model.id}`).filter(isValidModelSpec).sort();
|
|
264
290
|
if (ids.length === 0) return "No models are available in this Pi installation.";
|
|
265
291
|
return `Available model ids.\n\n${ids.map((id) => `- \`${id}\``).join("\n")}`;
|
|
266
292
|
}
|
|
267
293
|
|
|
268
|
-
function setModel(
|
|
294
|
+
function setModel(
|
|
295
|
+
role: string | undefined,
|
|
296
|
+
model: string | undefined,
|
|
297
|
+
thinking: string | undefined,
|
|
298
|
+
scope: "user" | "project",
|
|
299
|
+
ctx: ExtensionContext,
|
|
300
|
+
): string {
|
|
269
301
|
if (!role) return "set_model needs a `role`.";
|
|
270
302
|
if (!model) return "set_model needs a `model`, or `inherit` to clear the role.";
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
303
|
+
if (!isValidRole(role)) return invalidRoleMessage(role);
|
|
304
|
+
const available = ctx.modelRegistry
|
|
305
|
+
.getAvailable()
|
|
306
|
+
.map((candidate) => ({ id: `${candidate.provider}/${candidate.id}`, reasoning: candidate.reasoning }));
|
|
307
|
+
const plan = planModelAssignment(model, thinking, available);
|
|
308
|
+
if (plan.kind === "rejected") return plan.message;
|
|
275
309
|
const path = configPath(scope, ctx);
|
|
276
|
-
const
|
|
277
|
-
const models = { ...
|
|
278
|
-
if (
|
|
279
|
-
else models[role] =
|
|
310
|
+
const stored = readConfigFile(path)?.values.models;
|
|
311
|
+
const models = typeof stored === "object" && stored !== null && !Array.isArray(stored) ? { ...stored } : {};
|
|
312
|
+
if (plan.kind === "inherit") delete models[role];
|
|
313
|
+
else models[role] = joinModelSpec(plan.spec);
|
|
280
314
|
updateConfigFile(path, { models });
|
|
281
|
-
return
|
|
282
|
-
|
|
283
|
-
|
|
315
|
+
if (plan.kind === "inherit") return `Role \`${role}\` now inherits the parent session model and thinking. Written to ${path}.`;
|
|
316
|
+
return plan.spec.thinking === undefined
|
|
317
|
+
? `Role \`${role}\` now uses \`${plan.spec.model}\` at the model's default thinking. Written to ${path}.`
|
|
318
|
+
: `Role \`${role}\` now uses \`${plan.spec.model}\` with thinking \`${plan.spec.thinking}\`. Written to ${path}.`;
|
|
284
319
|
}
|
|
285
320
|
|
|
286
321
|
async function setSource(
|
|
@@ -336,6 +371,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
336
371
|
}
|
|
337
372
|
}
|
|
338
373
|
registrations = [];
|
|
374
|
+
unregisteredAgents = [];
|
|
339
375
|
}
|
|
340
376
|
}
|
|
341
377
|
|
|
@@ -418,6 +454,7 @@ function buildState(
|
|
|
418
454
|
namespace: config.namespace,
|
|
419
455
|
cacheRoot: root,
|
|
420
456
|
adapterVersion: ADAPTER_VERSION,
|
|
457
|
+
delegation: config.delegation,
|
|
421
458
|
// A local checkout has uncommitted edits the digest cannot see, so never reuse its output.
|
|
422
459
|
force: source.selection.kind === "local",
|
|
423
460
|
});
|