@cr1ms0n/pi-subagent 0.8.3 → 0.8.5
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 +8 -6
- package/package.json +1 -1
- package/skills/subagent/SKILL.md +6 -4
- package/src/model-policy.ts +2 -2
- package/src/persistence.ts +4 -3
- package/src/policy.ts +7 -0
- package/src/schema.ts +6 -9
- package/src/thinking.ts +15 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.5 — 2026-09-10
|
|
4
|
+
|
|
5
|
+
### Read-only FFF search tools
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
## 0.8.4 — 2026-09-10
|
|
10
|
+
|
|
11
|
+
### Pi-owned thinking levels
|
|
12
|
+
|
|
13
|
+
- Treat route and task `thinking` values as opaque Pi strings instead of a
|
|
14
|
+
hard-coded local enum, preserving model-specific values such as `max` and
|
|
15
|
+
passing them to Pi unchanged. Pi remains responsible for model capability
|
|
16
|
+
validation and mapping.
|
|
17
|
+
|
|
3
18
|
## 0.8.3 — 2026-09-10
|
|
4
19
|
|
|
5
20
|
### Model-policy thinking defaults
|
package/README.md
CHANGED
|
@@ -254,12 +254,14 @@ fallback lists are ignored for model selection. The minimal template is:
|
|
|
254
254
|
}
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
`thinking` is optional and
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
257
|
+
`thinking` is optional and is an opaque Pi thinking-level string. Common values
|
|
258
|
+
include `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`, but the
|
|
259
|
+
package does not remap or restrict model-specific values. Pi receives the value
|
|
260
|
+
unchanged and decides whether the active model supports it. It is a route default,
|
|
261
|
+
not a strict policy value. Resolution order is: explicit task `thinking` > agent
|
|
262
|
+
frontmatter `thinking` > profile `taskDefaults.<profile>.thinking` > the selected
|
|
263
|
+
`modelPolicy` route's `thinking` > the parent session's thinking level. Omitting it
|
|
264
|
+
preserves the existing behavior.
|
|
263
265
|
|
|
264
266
|
Every new `task`/`tasks[]` item must pass the exact mapped `model`. Omit
|
|
265
267
|
`fallback_models` to use the configured list; when supplied it must match the
|
package/package.json
CHANGED
package/skills/subagent/SKILL.md
CHANGED
|
@@ -97,9 +97,11 @@ Every new task must pass a `model` that exactly matches the current
|
|
|
97
97
|
`taskDefaults.model`, and parent-session model inheritance are ignored. An
|
|
98
98
|
agent route replaces the default route, and configured fallback order is
|
|
99
99
|
immutable. Omit `fallback_models` to use the route; if supplied, it must match
|
|
100
|
-
exactly. An optional route `thinking` value
|
|
101
|
-
|
|
102
|
-
`
|
|
103
|
-
|
|
100
|
+
exactly. An optional route `thinking` value is an opaque Pi thinking-level
|
|
101
|
+
string; common values include `off`, `minimal`, `low`, `medium`, `high`, `xhigh`,
|
|
102
|
+
and `max`, but model-specific values are passed through unchanged. It is a
|
|
103
|
+
default; explicit task, agent, and profile `taskDefaults.thinking` values
|
|
104
|
+
override it. Management actions do not require model. The extension re-reads
|
|
105
|
+
this policy on each dispatch and injects it into
|
|
104
106
|
the parent prompt. If the policy is missing or invalid, management remains
|
|
105
107
|
available but new spawns and synthesis are rejected.
|
package/src/model-policy.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { isThinkingLevel, type ThinkingLevel } from "./thinking.js";
|
|
5
5
|
|
|
6
6
|
export const MODEL_POLICY_CONFIG_FILE = path.join(os.homedir(), ".pi", "subagent.json");
|
|
7
7
|
|
|
@@ -55,7 +55,7 @@ function route(value: unknown, pathName: string, source: string): ModelRoute {
|
|
|
55
55
|
if (fallbackModels.includes(model)) invalid(source, `${pathName}.fallbackModels must not repeat the primary model`);
|
|
56
56
|
const thinking = record.thinking;
|
|
57
57
|
if (thinking !== undefined && !isThinkingLevel(thinking)) {
|
|
58
|
-
invalid(source, `${pathName}.thinking must be
|
|
58
|
+
invalid(source, `${pathName}.thinking must be a non-empty Pi thinking level string without whitespace or control characters`);
|
|
59
59
|
}
|
|
60
60
|
return Object.freeze({
|
|
61
61
|
model,
|
package/src/persistence.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import type { SubagentConfig } from "./config.js";
|
|
3
|
-
import type { ChildProcessIdentity, RunMode, RunSnapshot, RunState, TaskProfile, TimeoutPhase, UsageStats } from "./types.js";
|
|
3
|
+
import type { ChildProcessIdentity, RunMode, RunSnapshot, RunState, TaskProfile, TaskSpec, TimeoutPhase, UsageStats } from "./types.js";
|
|
4
4
|
import { emptyUsage } from "./types.js";
|
|
5
|
+
import { isThinkingLevel } from "./thinking.js";
|
|
5
6
|
|
|
6
7
|
export const RUN_ENTRY_TYPE = "subagent-run-v1";
|
|
7
8
|
|
|
@@ -23,7 +24,7 @@ export interface PersistedResult {
|
|
|
23
24
|
errorMessage?: string;
|
|
24
25
|
usage: UsageStats;
|
|
25
26
|
model?: string;
|
|
26
|
-
thinking?: "
|
|
27
|
+
thinking?: TaskSpec["thinking"];
|
|
27
28
|
profile?: TaskProfile;
|
|
28
29
|
canWrite?: boolean;
|
|
29
30
|
outputFile?: string;
|
|
@@ -141,7 +142,7 @@ function normalizeResult(value: unknown): PersistedResult | undefined {
|
|
|
141
142
|
errorMessage: typeof r.errorMessage === "string" ? utf8Prefix(r.errorMessage, 2_000) : undefined,
|
|
142
143
|
usage: normalizeUsage(r.usage),
|
|
143
144
|
model: typeof r.model === "string" ? r.model : undefined,
|
|
144
|
-
thinking:
|
|
145
|
+
thinking: isThinkingLevel(r.thinking) ? r.thinking : undefined,
|
|
145
146
|
profile: ["explore", "review", "general"].includes(String(r.profile)) ? r.profile : undefined,
|
|
146
147
|
canWrite: typeof r.canWrite === "boolean" ? r.canWrite : undefined,
|
|
147
148
|
outputFile: typeof r.outputFile === "string" ? r.outputFile : undefined,
|
package/src/policy.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { ParallelTaskInput, SubagentParams } from "./schema.js";
|
|
|
8
8
|
import { BACKEND_NAMES, checkCapabilities, type BackendName } from "./backend.js";
|
|
9
9
|
import { resolveBackend } from "./backends/index.js";
|
|
10
10
|
import { validateModelRequest, type ModelPolicySnapshot } from "./model-policy.js";
|
|
11
|
+
import { isThinkingLevel } from "./thinking.js";
|
|
11
12
|
|
|
12
13
|
export const DEPTH_ENV_VAR = "PI_SUBAGENT_DEPTH";
|
|
13
14
|
export const SPAWNS_ENV_VAR = "PI_SUBAGENT_SPAWNS";
|
|
@@ -16,6 +17,9 @@ export const READ_ONLY_TOOLS = new Set([
|
|
|
16
17
|
"grep",
|
|
17
18
|
"find",
|
|
18
19
|
"ls",
|
|
20
|
+
"fffind",
|
|
21
|
+
"ffgrep",
|
|
22
|
+
"fff-multi-grep",
|
|
19
23
|
"firecrawl_scrape",
|
|
20
24
|
"firecrawl_search",
|
|
21
25
|
"firecrawl_map",
|
|
@@ -200,6 +204,9 @@ function normalizeTask(
|
|
|
200
204
|
// validated above and is exclusively owned by modelPolicy.
|
|
201
205
|
const profileDefaults: TaskDefaults = defaults.taskDefaults?.[profile] ?? {};
|
|
202
206
|
const effectiveThinking = item.thinking ?? agent?.thinking ?? profileDefaults.thinking ?? modelRoute.thinking ?? parent.thinking;
|
|
207
|
+
if (effectiveThinking !== undefined && !isThinkingLevel(effectiveThinking)) {
|
|
208
|
+
return { error: `Task ${index + 1}: thinking must be a non-empty Pi thinking level string without whitespace or control characters` };
|
|
209
|
+
}
|
|
203
210
|
const label = item.description?.trim()
|
|
204
211
|
? item.description.trim().slice(0, 60)
|
|
205
212
|
: agent
|
package/src/schema.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { Type, type Static } from "typebox";
|
|
2
2
|
|
|
3
|
-
const ThinkingLevel = Type.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Type.Literal("high"),
|
|
9
|
-
Type.Literal("xhigh"),
|
|
10
|
-
]);
|
|
3
|
+
const ThinkingLevel = Type.String({
|
|
4
|
+
minLength: 1,
|
|
5
|
+
maxLength: 64,
|
|
6
|
+
description: "Opaque Pi thinking level passed through unchanged. Pi/model-specific values such as max are allowed; the active Pi process decides support.",
|
|
7
|
+
});
|
|
11
8
|
|
|
12
9
|
const OutputMode = Type.Union([Type.Literal("inline"), Type.Literal("file-only")]);
|
|
13
10
|
const Profile = Type.Union([Type.Literal("explore"), Type.Literal("review"), Type.Literal("general")]);
|
|
@@ -31,7 +28,7 @@ export const TaskFields = {
|
|
|
31
28
|
description: Type.Optional(Type.String({ description: "Short human label (3-5 words) shown in UIs and result indexes." })),
|
|
32
29
|
system_prompt: Type.Optional(Type.String({ description: "Extra system prompt appended to the child's prompt (does not replace it)." })),
|
|
33
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." })),
|
|
34
|
-
thinking: Type.Optional({ ...ThinkingLevel, description: "
|
|
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." }),
|
|
35
32
|
tools: Type.Optional(Type.Array(Type.String(), { description: "Optional tool allowlist. explore/review profiles reject write-capable tools." })),
|
|
36
33
|
profile: Type.Optional({ ...Profile, description: "Capability profile: explore/review are strictly read-only; general inherits the parent's active tools and may write." }),
|
|
37
34
|
cwd: Type.Optional(Type.String({ description: "Working directory for the child process." })),
|
package/src/thinking.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Opaque thinking-level value understood by the active Pi/model.
|
|
3
|
+
*
|
|
4
|
+
* Pi owns the vocabulary and model-specific mappings (for example `max` may
|
|
5
|
+
* be supported by one model while another exposes a different set). The
|
|
6
|
+
* subagent package must therefore validate only the transport-safe shape and
|
|
7
|
+
* pass the value through unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export type ThinkingLevel = string;
|
|
10
|
+
|
|
11
|
+
const MAX_THINKING_LEVEL_LENGTH = 64;
|
|
3
12
|
|
|
4
13
|
export function isThinkingLevel(value: unknown): value is ThinkingLevel {
|
|
5
|
-
return typeof value === "string"
|
|
14
|
+
return typeof value === "string"
|
|
15
|
+
&& value.length > 0
|
|
16
|
+
&& value.length <= MAX_THINKING_LEVEL_LENGTH
|
|
17
|
+
&& !/[\s\u0000-\u001f\u007f]/u.test(value);
|
|
6
18
|
}
|