@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 +4 -0
- package/package.json +1 -1
- package/packages/herdr-subagents/ARCHITECTURE.md +4 -0
- package/packages/herdr-subagents/CHANGELOG.md +4 -0
- package/packages/herdr-subagents/README.md +3 -1
- package/packages/herdr-subagents/TESTING.md +1 -0
- package/packages/herdr-subagents/extensions/herdr-subagents.ts +11 -4
- package/packages/herdr-subagents/package.json +1 -1
- package/packages/herdr-subagents/src/config.ts +7 -0
- package/packages/herdr-subagents/tests/core-state-machine.mjs +14 -1
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.
|
|
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.
|
|
@@ -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
|
|
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
|
|
|
@@ -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
|
{
|