@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 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 | Writes |
195
- | ---------------------------- | ------------------------------- | ------------------------------------ |
196
- | `explore` (parallel default) | read/grep/find/ls + safe extras | no |
197
- | `review` | same as explore | no |
198
- | `general` | inherited active tools | yes if tools include bash/edit/write |
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`.
@@ -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` (+ explicit read-only extras) | No |
12
- | `review` | same as explore | No |
13
- | `general` | inherited active tools (may include `bash`/`edit`/`write`) | Yes |
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 `bash`.** `bash` can rewrite the disc and is never part of
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cr1ms0n/pi-subagent",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "Community fork of Luke Parke's pi-subagent with explicit model policy and model visibility for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -57,11 +57,17 @@ const routeModel = "<exact model from current modelPolicy route>";
57
57
 
58
58
  ## Profiles
59
59
 
60
- | Profile | Tools | Writes |
61
- | --------- | ---------------------- | ------------------------------------------- |
62
- | `explore` | read/search/ls (+safe) | no |
63
- | `review` | same as explore | no |
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 ?? [...READ_ONLY_TOOLS].filter((tool) => available.has(tool));
84
- const unsafe = source.filter((tool) => !READ_ONLY_TOOLS.has(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: [...new Set(source)], canWrite: false };
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 read-only.
122
+ // General-profile custom tools are conservatively write-capable unless explicitly known non-writing.
97
123
  return {
98
- tools: [...new Set(source)],
99
- canWrite: source.some((tool) => KNOWN_WRITE_TOOLS.has(tool) || !READ_ONLY_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 write-capable tools." })),
33
- profile: Type.Optional({ ...Profile, description: "Capability profile: explore/review are strictly read-only; general inherits the parent's active tools and may write." }),
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." })),