@cr1ms0n/pi-subagent 0.8.4 → 0.8.6
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 +12 -0
- package/README.md +13 -5
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/SECURITY.md +8 -5
- package/package.json +1 -1
- package/skills/subagent/SKILL.md +11 -5
- package/src/extension.ts +2 -2
- package/src/policy.ts +38 -12
- package/src/schema.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.6 — 2026-09-14
|
|
4
|
+
|
|
5
|
+
### Pi context management in child profiles
|
|
6
|
+
|
|
7
|
+
- Keep Pi context-management tools available to child tool allowlists without granting project-file write access, so configured gateway models such as `uwoacrimson/gpt-5.6-luna` and `uwoacrimson/gpt-5.6-sol` can use `contextManagement` from `explore` and `review` profiles.
|
|
8
|
+
|
|
9
|
+
## 0.8.5 — 2026-09-10
|
|
10
|
+
|
|
11
|
+
### Read-only FFF search tools
|
|
12
|
+
|
|
13
|
+
- Classify `fffind`, `ffgrep`, and `fff-multi-grep` as read-only tools so `explore` and `review` profiles can use the `@ff-labs/pi-fff` search tools without granting write access.
|
|
14
|
+
|
|
3
15
|
## 0.8.4 — 2026-09-10
|
|
4
16
|
|
|
5
17
|
### Pi-owned thinking levels
|
package/README.md
CHANGED
|
@@ -191,11 +191,19 @@ Set a persona's backend in agent frontmatter with `backend: codex`.
|
|
|
191
191
|
|
|
192
192
|
## Profiles
|
|
193
193
|
|
|
194
|
-
| Profile | Tools
|
|
195
|
-
| ---------------------------- |
|
|
196
|
-
| `explore` (parallel default) | read/grep/find/ls + safe extras | no
|
|
197
|
-
| `review` | same as explore
|
|
198
|
-
| `general` | inherited active tools
|
|
194
|
+
| Profile | Tools | Writes |
|
|
195
|
+
| ---------------------------- | ------------------------------------------------------- | ------------------------------------------- |
|
|
196
|
+
| `explore` (parallel default) | read/grep/find/ls + safe extras + Pi context tools | no project-file writes |
|
|
197
|
+
| `review` | same as explore | no project-file writes |
|
|
198
|
+
| `general` | inherited active tools + Pi context tools | yes if tools include bash/edit/write |
|
|
199
|
+
|
|
200
|
+
For the Pi backend, the context-management tools `new_context`,
|
|
201
|
+
`get_context_remaining`, `history`, and `notes` are retained in child tool
|
|
202
|
+
allowlists when the parent exposes them. They are control-plane tools: they may
|
|
203
|
+
update continuity notes or the remote context window, but cannot modify the
|
|
204
|
+
child checkout or run a shell command. This exception also applies when a task
|
|
205
|
+
supplies a narrower tool list, so Pi's `contextManagement` remains usable for
|
|
206
|
+
configured gateway models.
|
|
199
207
|
|
|
200
208
|
Parallel write-capable tasks sharing one checkout are rejected unless each uses
|
|
201
209
|
`isolation: "worktree"`, distinct `cwd`, or explicit `allow_shared_writes: true`.
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
- `persistence.ts`: versioned active-branch event folding and bounded child transcript metadata.
|
|
30
30
|
- `maintenance.ts`: filesystem GC (session files) and abort-race helpers; kept out of persistence.
|
|
31
31
|
- `usage.ts`: provider-reported root/subagent/combined accounting.
|
|
32
|
-
- `policy.ts` / `schema.ts`: discriminated request validation and safe capability profiles.
|
|
32
|
+
- `policy.ts` / `schema.ts`: discriminated request validation and safe capability profiles. Pi context-management control-plane tools remain available to child allowlists without granting project-file write access.
|
|
33
33
|
- `config.ts`: defaults ← `~/.pi/subagent.json` ← `PI_SUBAGENT_*` env overrides.
|
|
34
34
|
- `structured.ts`: structured-output contract (dependency-free JSON-Schema subset
|
|
35
35
|
validation, fenced json:result extraction, contract/repair prompts) and
|
package/docs/SECURITY.md
CHANGED
|
@@ -8,16 +8,19 @@ and can use tools according to their capability profile.
|
|
|
8
8
|
|
|
9
9
|
| Profile | Default tools | Writes? |
|
|
10
10
|
|---------|---------------|---------|
|
|
11
|
-
| `explore` | `read`, `grep`, `find`, `ls` (+
|
|
12
|
-
| `review` | same as explore | No |
|
|
13
|
-
| `general` | inherited active tools (
|
|
11
|
+
| `explore` | `read`, `grep`, `find`, `ls` (+ safe extras and Pi context tools) | No project-file writes |
|
|
12
|
+
| `review` | same as explore | No project-file writes |
|
|
13
|
+
| `general` | inherited active tools (+ Pi context tools) | Yes if `bash`/`edit`/`write` are active |
|
|
14
14
|
|
|
15
15
|
Parallel mode defaults to `explore` to avoid concurrent shared writes.
|
|
16
16
|
|
|
17
17
|
## Hard rules
|
|
18
18
|
|
|
19
|
-
1. **Read-only means no
|
|
20
|
-
an explore/review profile.
|
|
19
|
+
1. **Read-only means no project-file mutation.** `bash` can rewrite the disk and is never part of
|
|
20
|
+
an explore/review profile. Pi context-management tools (`new_context`,
|
|
21
|
+
`get_context_remaining`, `history`, `notes`) are an explicit control-plane
|
|
22
|
+
exception: they may update continuity notes/window state but cannot access
|
|
23
|
+
the project write tools.
|
|
21
24
|
2. **Parallel writers** require `isolation: "worktree"`, distinct `cwd` values,
|
|
22
25
|
or an explicit `allow_shared_writes: true` opt-in.
|
|
23
26
|
3. **Depth is capped** (`maxDepth`, default 2). Nested children at the ceiling do
|
package/package.json
CHANGED
package/skills/subagent/SKILL.md
CHANGED
|
@@ -57,11 +57,17 @@ const routeModel = "<exact model from current modelPolicy route>";
|
|
|
57
57
|
|
|
58
58
|
## Profiles
|
|
59
59
|
|
|
60
|
-
| Profile | Tools
|
|
61
|
-
| --------- |
|
|
62
|
-
| `explore` | read/search/ls (+safe) | no
|
|
63
|
-
| `review` | same as explore
|
|
64
|
-
| `general` | inherited active tools | yes if tools include bash/edit/write |
|
|
60
|
+
| Profile | Tools | Writes |
|
|
61
|
+
| --------- | --------------------------------------------------------- | ------------------------------------------- |
|
|
62
|
+
| `explore` | read/search/ls (+safe) + Pi context tools | no project-file writes |
|
|
63
|
+
| `review` | same as explore | no project-file writes |
|
|
64
|
+
| `general` | inherited active tools + Pi context tools | yes if tools include bash/edit/write |
|
|
65
|
+
|
|
66
|
+
For Pi children, `new_context`, `get_context_remaining`, `history`, and
|
|
67
|
+
`notes` are control-plane tools. When available in the parent they remain in
|
|
68
|
+
the child allowlist—even if a narrower tool list was requested—so Pi's remote
|
|
69
|
+
`contextManagement` can stay active. They may update context notes/window
|
|
70
|
+
state, but never grant `bash`, `edit`, or `write` access.
|
|
65
71
|
|
|
66
72
|
Parallel write-capable tasks sharing one checkout are rejected unless each uses
|
|
67
73
|
`isolation: "worktree"`, a distinct `cwd`, or `allow_shared_writes: true`.
|
package/src/extension.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { abortAsPromise } from "./maintenance.js";
|
|
|
24
24
|
import { sweepSessionsLifecycle } from "./distill.js";
|
|
25
25
|
import { runTasks } from "./orchestrator.js";
|
|
26
26
|
import { OutputManager } from "./output.js";
|
|
27
|
-
import { parseDepth, parseSpawnPolicy, SPAWNS_ENV_VAR, validateSubagentRequest, type ResolvedTask } from "./policy.js";
|
|
27
|
+
import { CONTEXT_MANAGEMENT_TOOLS, parseDepth, parseSpawnPolicy, SPAWNS_ENV_VAR, validateSubagentRequest, type ResolvedTask } from "./policy.js";
|
|
28
28
|
import type { ChildRunner } from "./runner.js";
|
|
29
29
|
import { ProcessLockManager } from "./process-lock.js";
|
|
30
30
|
import { SessionScopedRunRegistry, snapshotFromLiveRun } from "./registry.js";
|
|
@@ -563,7 +563,7 @@ async function runSynthesis(
|
|
|
563
563
|
label: "synthesis",
|
|
564
564
|
profile: "review",
|
|
565
565
|
canWrite: false,
|
|
566
|
-
tools: ["read"],
|
|
566
|
+
tools: ["read", ...CONTEXT_MANAGEMENT_TOOLS],
|
|
567
567
|
model: approved.route.model,
|
|
568
568
|
fallbackModels: [...approved.route.fallbackModels],
|
|
569
569
|
thinking: approved.route.thinking ?? "low",
|
package/src/policy.ts
CHANGED
|
@@ -17,6 +17,9 @@ export const READ_ONLY_TOOLS = new Set([
|
|
|
17
17
|
"grep",
|
|
18
18
|
"find",
|
|
19
19
|
"ls",
|
|
20
|
+
"fffind",
|
|
21
|
+
"ffgrep",
|
|
22
|
+
"fff-multi-grep",
|
|
20
23
|
"firecrawl_scrape",
|
|
21
24
|
"firecrawl_search",
|
|
22
25
|
"firecrawl_map",
|
|
@@ -24,6 +27,19 @@ export const READ_ONLY_TOOLS = new Set([
|
|
|
24
27
|
"web_search",
|
|
25
28
|
"web_fetch",
|
|
26
29
|
]);
|
|
30
|
+
/**
|
|
31
|
+
* Pi context-management tools are control-plane capabilities: they may update
|
|
32
|
+
* continuity notes or the remote context window, but they cannot modify the
|
|
33
|
+
* child checkout. Keep them separate from ordinary source-inspection tools so
|
|
34
|
+
* the read-only profile's exception remains explicit.
|
|
35
|
+
*/
|
|
36
|
+
export const CONTEXT_MANAGEMENT_TOOLS = new Set([
|
|
37
|
+
"new_context",
|
|
38
|
+
"get_context_remaining",
|
|
39
|
+
"history",
|
|
40
|
+
"notes",
|
|
41
|
+
]);
|
|
42
|
+
const NON_WRITING_TOOLS = new Set([...READ_ONLY_TOOLS, ...CONTEXT_MANAGEMENT_TOOLS]);
|
|
27
43
|
export const KNOWN_WRITE_TOOLS = new Set(["bash", "edit", "write"]);
|
|
28
44
|
/** Backward-compatible export; policy uses fail-closed classification above. */
|
|
29
45
|
export const WRITE_TOOLS = KNOWN_WRITE_TOOLS;
|
|
@@ -72,31 +88,41 @@ function resolveTools(
|
|
|
72
88
|
requested: string[] | undefined,
|
|
73
89
|
availableTools: string[],
|
|
74
90
|
activeTools: string[],
|
|
91
|
+
backend: BackendName,
|
|
75
92
|
): { tools?: string[]; canWrite?: boolean; error?: string } {
|
|
76
93
|
const available = new Set(availableTools);
|
|
94
|
+
const contextTools = backend === "pi"
|
|
95
|
+
? [...CONTEXT_MANAGEMENT_TOOLS].filter((tool) => available.has(tool))
|
|
96
|
+
: [];
|
|
97
|
+
const nonWritingTools = backend === "pi" ? NON_WRITING_TOOLS : READ_ONLY_TOOLS;
|
|
98
|
+
// Keep Pi's context-management control plane available to every child when
|
|
99
|
+
// the parent exposes it, even if the task requested a narrower tool subset.
|
|
100
|
+
const addContextTools = (tools: readonly string[]): string[] =>
|
|
101
|
+
[...new Set([...tools, ...contextTools])];
|
|
102
|
+
|
|
77
103
|
if (requested) {
|
|
78
104
|
const unknown = requested.filter((tool) => !available.has(tool));
|
|
79
105
|
if (unknown.length) return { error: `Unknown or unavailable tools: ${unknown.join(", ")}` };
|
|
80
106
|
}
|
|
81
107
|
|
|
82
108
|
if (profile === "explore" || profile === "review") {
|
|
83
|
-
const source = requested ?? [...
|
|
84
|
-
const unsafe = source.filter((tool) => !
|
|
109
|
+
const source = addContextTools(requested ?? [...nonWritingTools].filter((tool) => available.has(tool)));
|
|
110
|
+
const unsafe = source.filter((tool) => !nonWritingTools.has(tool));
|
|
85
111
|
if (unsafe.length) {
|
|
86
112
|
return {
|
|
87
113
|
error: `${profile} is strictly read-only. Unclassified or writable tools are not allowed: ${unsafe.join(", ")}`,
|
|
88
114
|
};
|
|
89
115
|
}
|
|
90
|
-
return { tools:
|
|
116
|
+
return { tools: source, canWrite: false };
|
|
91
117
|
}
|
|
92
118
|
|
|
93
|
-
const source = requested ?? activeTools;
|
|
119
|
+
const source = addContextTools(requested ?? activeTools);
|
|
94
120
|
const unknown = source.filter((tool) => !available.has(tool));
|
|
95
121
|
if (unknown.length) return { error: `Active tools are unavailable: ${unknown.join(", ")}` };
|
|
96
|
-
// General-profile custom tools are conservatively write-capable unless explicitly known
|
|
122
|
+
// General-profile custom tools are conservatively write-capable unless explicitly known non-writing.
|
|
97
123
|
return {
|
|
98
|
-
tools:
|
|
99
|
-
canWrite: source.some((tool) => KNOWN_WRITE_TOOLS.has(tool) || !
|
|
124
|
+
tools: source,
|
|
125
|
+
canWrite: source.some((tool) => KNOWN_WRITE_TOOLS.has(tool) || !nonWritingTools.has(tool)),
|
|
100
126
|
};
|
|
101
127
|
}
|
|
102
128
|
|
|
@@ -190,9 +216,13 @@ function normalizeTask(
|
|
|
190
216
|
}
|
|
191
217
|
}
|
|
192
218
|
|
|
219
|
+
const backend: BackendName = item.backend ?? agent?.backend ?? "pi";
|
|
220
|
+
if (!BACKEND_NAMES.includes(backend)) {
|
|
221
|
+
return { error: `Task ${index + 1}: unknown backend '${backend}' (expected ${BACKEND_NAMES.join(", ")})` };
|
|
222
|
+
}
|
|
193
223
|
const profile = item.profile ?? agent?.profile ?? defaultProfile;
|
|
194
224
|
const requestedTools = item.tools ?? agent?.tools;
|
|
195
|
-
const resolved = resolveTools(profile, requestedTools, parent.availableTools, parent.activeTools ?? parent.availableTools);
|
|
225
|
+
const resolved = resolveTools(profile, requestedTools, parent.availableTools, parent.activeTools ?? parent.availableTools, backend);
|
|
196
226
|
if (resolved.error || !resolved.tools || resolved.canWrite === undefined) return { error: resolved.error ?? "Tool resolution failed" };
|
|
197
227
|
const cwd = resolvePath(parent.cwd, item.cwd);
|
|
198
228
|
const output = item.output ? resolvePath(cwd, item.output) : undefined;
|
|
@@ -213,10 +243,6 @@ function normalizeTask(
|
|
|
213
243
|
|
|
214
244
|
// Backend capability gate. Refuse combinations the backend cannot honor
|
|
215
245
|
// rather than silently dropping a budget or a read-only guarantee.
|
|
216
|
-
const backend: BackendName = item.backend ?? agent?.backend ?? "pi";
|
|
217
|
-
if (!BACKEND_NAMES.includes(backend)) {
|
|
218
|
-
return { error: `Task ${index + 1}: unknown backend '${backend}' (expected ${BACKEND_NAMES.join(", ")})` };
|
|
219
|
-
}
|
|
220
246
|
const capabilities = resolveBackend(backend).capabilities;
|
|
221
247
|
const problems = checkCapabilities(
|
|
222
248
|
{
|
package/src/schema.ts
CHANGED
|
@@ -29,8 +29,8 @@ export const TaskFields = {
|
|
|
29
29
|
system_prompt: Type.Optional(Type.String({ description: "Extra system prompt appended to the child's prompt (does not replace it)." })),
|
|
30
30
|
model: Type.Optional(Type.String({ description: "**REQUIRED for every spawn call (task/tasks).** Exact model id from modelPolicy, in provider/model-id form. Calls without an explicit model are rejected; agent-file model, taskDefaults, and parent-session inheritance are ignored. Management actions (status/wait/cancel/steer/diff/apply/discard) do not need it." })),
|
|
31
31
|
thinking: Type.Optional({ ...ThinkingLevel, description: "Opaque Pi thinking level for the child. Values such as max are passed through unchanged; Pi/model support decides validity. Defaults to agent thinking, profile taskDefaults.thinking, modelPolicy route thinking, then the parent's level." }),
|
|
32
|
-
tools: Type.Optional(Type.Array(Type.String(), { description: "Optional tool allowlist. explore/review profiles reject
|
|
33
|
-
profile: Type.Optional({ ...Profile, description: "Capability profile: explore/review
|
|
32
|
+
tools: Type.Optional(Type.Array(Type.String({ minLength: 1 }), { description: "Optional tool allowlist. explore/review profiles reject project-writing tools; Pi context-management tools remain available for context continuity." })),
|
|
33
|
+
profile: Type.Optional({ ...Profile, description: "Capability profile: explore/review cannot write project files but retain Pi context-management tools; general inherits the parent's active tools and may write." }),
|
|
34
34
|
cwd: Type.Optional(Type.String({ description: "Working directory for the child process." })),
|
|
35
35
|
timeout_ms: Type.Optional(Type.Number({ minimum: 1, maximum: 24 * 60 * 60_000, description: "Total budget in milliseconds including queue time. Timed-out runs report which phase timed out." })),
|
|
36
36
|
max_turns: Type.Optional(Type.Number({ minimum: 1, maximum: 500, description: "Budget: at this many turns the child is steered to wrap up and given grace turns for a final answer; ends as 'partial' with output preserved." })),
|