@cr1ms0n/pi-subagent 0.8.3 → 0.8.4

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.4 — 2026-09-10
4
+
5
+ ### Pi-owned thinking levels
6
+
7
+ - Treat route and task `thinking` values as opaque Pi strings instead of a
8
+ hard-coded local enum, preserving model-specific values such as `max` and
9
+ passing them to Pi unchanged. Pi remains responsible for model capability
10
+ validation and mapping.
11
+
3
12
  ## 0.8.3 — 2026-09-10
4
13
 
5
14
  ### 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 accepts `off`, `minimal`, `low`, `medium`, `high`,
258
- or `xhigh`. It is a route default, not a strict policy value. Resolution order
259
- is: explicit task `thinking` > agent frontmatter `thinking` > profile
260
- `taskDefaults.<profile>.thinking` > the selected `modelPolicy` route's
261
- `thinking` > the parent session's thinking level. Omitting it preserves the
262
- existing behavior.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cr1ms0n/pi-subagent",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
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",
@@ -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 (`off`, `minimal`, `low`, `medium`,
101
- `high`, or `xhigh`) is a default; explicit task, agent, and profile
102
- `taskDefaults.thinking` values override it. Management actions do not require
103
- model. The extension re-reads this policy on each dispatch and injects it into
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.
@@ -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 { THINKING_LEVELS, isThinkingLevel, type ThinkingLevel } from "./thinking.js";
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 one of: ${THINKING_LEVELS.join(", ")}`);
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,
@@ -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?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
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: ["off", "minimal", "low", "medium", "high", "xhigh"].includes(String(r.thinking)) ? r.thinking : undefined,
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";
@@ -200,6 +201,9 @@ function normalizeTask(
200
201
  // validated above and is exclusively owned by modelPolicy.
201
202
  const profileDefaults: TaskDefaults = defaults.taskDefaults?.[profile] ?? {};
202
203
  const effectiveThinking = item.thinking ?? agent?.thinking ?? profileDefaults.thinking ?? modelRoute.thinking ?? parent.thinking;
204
+ if (effectiveThinking !== undefined && !isThinkingLevel(effectiveThinking)) {
205
+ return { error: `Task ${index + 1}: thinking must be a non-empty Pi thinking level string without whitespace or control characters` };
206
+ }
203
207
  const label = item.description?.trim()
204
208
  ? item.description.trim().slice(0, 60)
205
209
  : agent
package/src/schema.ts CHANGED
@@ -1,13 +1,10 @@
1
1
  import { Type, type Static } from "typebox";
2
2
 
3
- const ThinkingLevel = Type.Union([
4
- Type.Literal("off"),
5
- Type.Literal("minimal"),
6
- Type.Literal("low"),
7
- Type.Literal("medium"),
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: "Reasoning effort for the child. Defaults to agent thinking, profile taskDefaults.thinking, modelPolicy route thinking, then the parent's level." }),
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
- export const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
2
- export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
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" && (THINKING_LEVELS as readonly string[]).includes(value);
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
  }