@maxiaochao/pi-toolkit 0.11.19 → 0.11.20

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,3 +1,7 @@
1
+ ## 2026-10-14 - v0.11.20
2
+
3
+ - herdr-subagents v0.5.3: allow optional Pi-native tool exposure while inheriting Pi defaults when unset
4
+
1
5
  ## 2026-10-14 - v0.11.19
2
6
 
3
7
  - herdr-subagents v0.5.2: stop Pi startup from scanning historical runs or closing recorded Herdr tabs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-toolkit",
3
- "version": "0.11.19",
3
+ "version": "0.11.20",
4
4
  "description": "Personal pi coding agent toolkit: scheduler, /btw, /cache_export, apply_patch, browser automation, and bundled theme/skills.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -110,3 +110,7 @@ Writing the same report again is idempotent. A conflicting replacement is reject
110
110
  ## Cancellation and shutdown
111
111
 
112
112
  Caller or parent-session cancellation aborts active waits, marks nonterminal tasks cancelled, and closes the Herdr tab owned by that session. A new Pi session never scans, mutates, resumes, or closes historical runs during startup. If Pi is terminated before its shutdown handlers run, its visible Subagent tabs remain for explicit user inspection or cleanup.
113
+
114
+ ## Tool exposure
115
+
116
+ The extension reads optional `exposure` once before registering its two tools. When omitted, the extension also omits the registration field and delegates to Pi's default. Global or project configuration can select Pi's `model-only` or `direct` exposure explicitly. Changing this registration-time setting requires `/reload` or a Pi restart; the extension does not re-register tools during a session.
@@ -1,3 +1,7 @@
1
+ ## 2026-10-14 - v0.5.3
2
+
3
+ - allow optional Pi-native tool exposure while inheriting Pi defaults when unset
4
+
1
5
  ## 2026-10-14 - v0.5.2
2
6
 
3
7
  - stop Pi startup from scanning historical runs or closing recorded Herdr tabs
@@ -89,10 +89,12 @@ The orchestrator is the only writer of `run.json`, `task.json`, and promoted `re
89
89
 
90
90
  `herdr_subagents_control` supports `list`, `status`, `history`, `cancel`, and `cleanup`. Follow-up work is a new task or run; settled tasks are never revived.
91
91
 
92
+ When `exposure` is absent, both tools omit the registration field and inherit Pi's default exposure. Set `"exposure": "model-only"` to keep explicit top-level model calls while excluding Codemode scripts and other nested tools, or `"exposure": "direct"` to request Pi's direct-and-nested behavior explicitly. Exposure is read when the extension loads, so use `/reload` or restart Pi after changing it.
93
+
92
94
  Use `/herdr-subagents focus <task-number>` to focus a running pane. `/herdr-subagents active`, `history`, `agents`, `models`, and `settings` provide compact inspection.
93
95
 
94
96
  ## Personas and settings
95
97
 
96
98
  Bundled personas are `worker`, `explorer`, `analyst`, and `reviewer`. Project personas can be added under `.pi/agents/` using the same frontmatter format as the bundled files. Access is declared as `read` or `write` and drives concurrency safety.
97
99
 
98
- Settings are read from the existing package configuration. `defaultConcurrency`, `maxConcurrency`, and `stalledWarningSeconds` remain supported.
100
+ Settings are read from `~/.pi/agent/herdr-subagents.json` and the project-level `.pi/herdr-subagents.json`, with the project value taking precedence. Optional `exposure` accepts Pi's `model-only` or `direct`; omitting it delegates to Pi. `defaultConcurrency`, `maxConcurrency`, and `stalledWarningSeconds` remain supported.
@@ -17,6 +17,7 @@ It verifies:
17
17
  - explicit failure when the process exits without a report;
18
18
  - parent cancellation of running and queued tasks;
19
19
  - startup isolation: a new Pi session neither mutates historical records nor closes their recorded Herdr tabs;
20
+ - omitted exposure delegates both tools to Pi's default, while project configuration can consistently select model-only exposure;
20
21
  - immutable terminal task records;
21
22
  - idempotent duplicate reports and rejection of conflicting replacements;
22
23
  - keeping a settled child alive for focus-aware cleanup instead of force-exiting its TUI.
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import type { AgentToolResult, ExtensionAPI, ExtensionCommandContext, Theme, ToolRenderResultOptions } from "@earendil-works/pi-coding-agent";
4
4
  import { Container, Editor, Text } from "@earendil-works/pi-tui";
5
- import { loadSubagentConfiguration } from "../src/config.ts";
5
+ import { loadSubagentConfiguration, type ConfigurationPaths } from "../src/config.ts";
6
6
  import { CliHerdrAutomation, type HerdrAutomation } from "../src/herdr.ts";
7
7
  import { activeRunText } from "../src/monitor.ts";
8
8
  import { historyText, listSubagentHistory } from "../src/history.ts";
@@ -62,7 +62,7 @@ function renderToolResult(result: AgentToolResult<unknown>, _options: ToolRender
62
62
  return new Text(content?.type === "text" ? content.text : "(no output)", 0, 0);
63
63
  }
64
64
 
65
- async function handleHerdrCommand(args: string, ctx: ExtensionCommandContext, fleet: FleetController): Promise<void> {
65
+ async function handleHerdrCommand(args: string, ctx: ExtensionCommandContext, fleet: FleetController, registeredExposure: { value?: "direct" | "model-only"; source: string }): Promise<void> {
66
66
  const section = args.trim() || "active";
67
67
  if (section === "active") {
68
68
  ctx.ui.notify(fleet.active ? activeRunText(fleet.active) : "No active Subagent run.", "info");
@@ -90,7 +90,7 @@ async function handleHerdrCommand(args: string, ctx: ExtensionCommandContext, fl
90
90
  if (section === "settings") {
91
91
  const settings = configuration.settings;
92
92
  const values: Record<string, string | number | undefined> = { defaultConcurrency: settings.defaultConcurrency, maxConcurrency: settings.maxConcurrency, stalledWarningSeconds: settings.stalledWarningSeconds, defaultModel: settings.defaultModel, defaultThinking: settings.defaultThinking };
93
- const lines = Object.entries(values).map(([key, value]) => `${key}=${String(value ?? "unset")} (${configuration.settingSources[key] ?? "built-in"})`);
93
+ const lines = [`exposure=${registeredExposure.value ?? "Pi default"} (${registeredExposure.source}; loaded at extension registration)`, ...Object.entries(values).map(([key, value]) => `${key}=${String(value ?? "unset")} (${configuration.settingSources[key] ?? "built-in"})`)];
94
94
  if (configuration.diagnostics.length) lines.push("Diagnostics:", ...configuration.diagnostics);
95
95
  ctx.ui.notify(lines.join("\n"), "info");
96
96
  return;
@@ -100,6 +100,8 @@ async function handleHerdrCommand(args: string, ctx: ExtensionCommandContext, fl
100
100
 
101
101
  export interface HerdrSubagentsExtensionDependencies {
102
102
  herdr?: HerdrAutomation;
103
+ configurationCwd?: string;
104
+ configurationPaths?: ConfigurationPaths;
103
105
  execute?: typeof executeHerdrSubagents;
104
106
  executeControl?: typeof executeHerdrSubagentsControl;
105
107
  isEditor?: (value: unknown) => boolean;
@@ -121,6 +123,9 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
121
123
  const herdr = dependencies.herdr ?? new CliHerdrAutomation();
122
124
  const execute = dependencies.execute ?? executeHerdrSubagents;
123
125
  const executeControl = dependencies.executeControl ?? executeHerdrSubagentsControl;
126
+ const registrationConfiguration = loadSubagentConfiguration(dependencies.configurationCwd ?? process.cwd(), packageAgentsDir, undefined, dependencies.configurationPaths);
127
+ const exposure = registrationConfiguration.settings.exposure;
128
+ const registeredExposure = { value: exposure, source: registrationConfiguration.settingSources.exposure ?? "Pi" };
124
129
  const isEditorComponent = dependencies.isEditor ?? ((value: unknown) => value instanceof Editor);
125
130
  const fleet = new FleetController(herdr, isEditorComponent, dependencies.createFleetWidget, dependencies.fleetWidgetRuntime);
126
131
  const runControllers = new Map<string, AbortController>();
@@ -141,6 +146,7 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
141
146
 
142
147
  pi.registerTool({
143
148
  name: "herdr_subagents",
149
+ ...(exposure ? { exposure } : {}),
144
150
  label: "Herdr Subagents",
145
151
  description: "Run one or more ordered tasks in visible, focusable Herdr Pi panes. concurrency 1 is strict serial execution; higher values form a bounded pool for read-only tasks. Write-capable tasks require concurrency 1. The call returns only after every task has one terminal completed, failed, or cancelled result. Complete results and Pi sessions stay in project-local records.",
146
152
  promptSnippet: "Run focused work in visible Herdr subagents",
@@ -181,6 +187,7 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
181
187
 
182
188
  pi.registerTool({
183
189
  name: "herdr_subagents_control",
190
+ ...(exposure ? { exposure } : {}),
184
191
  label: "Herdr Subagents Control",
185
192
  description: "Inspect Herdr personas, query one run by ID, list run history, cancel active work, or remove an archived run. Use herdr_subagents to start new work.",
186
193
  parameters: HerdrSubagentsControlParams,
@@ -231,7 +238,7 @@ export function registerHerdrSubagents(pi: ExtensionAPI, dependencies: HerdrSuba
231
238
  pi.registerCommand("herdr-subagents", {
232
239
  description: "Inspect effective subagent agents, models, and settings",
233
240
  getArgumentCompletions: (prefix) => ["active", "history", "agents", "models", "settings", "focus "].filter((value) => value.startsWith(prefix)).map((value) => ({ value, label: value })),
234
- handler: (args, ctx) => handleHerdrCommand(args, ctx, fleet),
241
+ handler: (args, ctx) => handleHerdrCommand(args, ctx, fleet, registeredExposure),
235
242
  });
236
243
  }
237
244
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-herdr-subagents",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Run inspectable interactive Pi subagent batches through Herdr.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -4,8 +4,10 @@ import { dirname, join } from "node:path";
4
4
  import { discoverPackageAgents, type AgentDefinition } from "./personas.ts";
5
5
 
6
6
  export type PersonaAccess = "read" | "write";
7
+ export type SubagentToolExposure = "direct" | "model-only";
7
8
  export interface PersonaConfig { model?: string; thinking?: string; skills?: string[]; extensions?: string[]; }
8
9
  export interface SubagentConfig {
10
+ exposure?: SubagentToolExposure;
9
11
  defaultConcurrency: number;
10
12
  maxConcurrency: number;
11
13
  stalledWarningSeconds: number;
@@ -32,6 +34,7 @@ export interface SubagentConfiguration {
32
34
  diagnostics: string[];
33
35
  }
34
36
  interface ConfigFile {
37
+ exposure?: string;
35
38
  defaultConcurrency?: number;
36
39
  maxConcurrency?: number;
37
40
  stalledWarningSeconds?: number;
@@ -61,6 +64,10 @@ function readConfig(path: string, source: string, diagnostics: string[]): Config
61
64
 
62
65
  function mergeConfig(base: SubagentConfig, input: ConfigFile, source: string, sources: Record<string, string>, diagnostics: string[]): SubagentConfig {
63
66
  const result: SubagentConfig = { ...base, personas: { ...base.personas } };
67
+ if (input.exposure !== undefined) {
68
+ if (input.exposure === "direct" || input.exposure === "model-only") { result.exposure = input.exposure; sources.exposure = source; }
69
+ else diagnostics.push(`${source} exposure must be 'direct' or 'model-only'`);
70
+ }
64
71
  for (const key of ["defaultConcurrency", "maxConcurrency", "stalledWarningSeconds"] as const) {
65
72
  const value = input[key];
66
73
  if (value === undefined) continue;
@@ -160,10 +160,11 @@ assert.throws(() => applyTaskStatus(terminal, "running"), /terminal tasks are im
160
160
  writeFileSync(join(runDirectory, "run.json"), JSON.stringify(historicalRun));
161
161
  writeFileSync(join(taskDirectory, "task.json"), JSON.stringify({ id: "historical-task", runId: "historical-run", order: 1, status: "completed", completedAt: historicalRun.completedAt }));
162
162
  const events = new Map();
163
+ const registeredTools = new Map();
163
164
  const herdrCalls = [];
164
165
  registerHerdrSubagents({
165
166
  on(name, handler) { events.set(name, handler); },
166
- registerTool() {},
167
+ registerTool(tool) { registeredTools.set(tool.name, tool); },
167
168
  registerCommand() {},
168
169
  }, {
169
170
  herdr: {
@@ -180,6 +181,18 @@ assert.throws(() => applyTaskStatus(terminal, "running"), /terminal tasks are im
180
181
  });
181
182
  assert.deepEqual(herdrCalls, [], "Pi startup must not inspect or close historical Subagent tabs");
182
183
  assert.deepEqual(JSON.parse(readFileSync(join(runDirectory, "run.json"), "utf8")), historicalRun, "Pi startup must not mutate historical Subagent records");
184
+ assert.equal(registeredTools.get("herdr_subagents")?.exposure, undefined, "Unconfigured Subagent execution must inherit Pi's exposure default");
185
+ assert.equal(registeredTools.get("herdr_subagents_control")?.exposure, undefined, "Unconfigured Subagent control must inherit Pi's exposure default");
186
+ mkdirSync(join(lifecycleRoot, ".pi"), { recursive: true });
187
+ writeFileSync(join(lifecycleRoot, ".pi", "herdr-subagents.json"), JSON.stringify({ exposure: "model-only" }));
188
+ const configuredTools = new Map();
189
+ registerHerdrSubagents({
190
+ on() {},
191
+ registerTool(tool) { configuredTools.set(tool.name, tool); },
192
+ registerCommand() {},
193
+ }, { herdr: {}, isEditor: () => false, configurationCwd: lifecycleRoot });
194
+ assert.equal(configuredTools.get("herdr_subagents")?.exposure, "model-only", "Project configuration can exclude Subagent execution from Codemode");
195
+ assert.equal(configuredTools.get("herdr_subagents_control")?.exposure, "model-only", "Project configuration applies consistently to both Subagent tools");
183
196
  }
184
197
 
185
198
  {