@maxiaochao/pi-toolkit 0.13.4 → 0.13.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 CHANGED
@@ -1,3 +1,9 @@
1
+ ## 2026-10-22 - v0.13.5
2
+
3
+ - herdr-subagents v0.7.4: preserve Codex patch editing in child allowlists and hide the parent shortcut footer hint
4
+ - Herdr Subagents: hide the child parent-navigation footer hint while keeping `Alt+P`, `/parent`, and other extensions' editor status.
5
+ - Herdr Subagents: permit `apply_patch` in edit-capable child CLI allowlists so editor switching does not remove the only active editing tool; preserve read-only limits and native fallback.
6
+
1
7
  ## 2026-10-22 - v0.13.4
2
8
 
3
9
  - herdr-subagents v0.7.3: keep Persona selection lists concise
package/README.md CHANGED
@@ -42,6 +42,8 @@ Each child README owns its feature description, motivation or upstream alignment
42
42
 
43
43
  Codex Edit personal routing rules belong in `~/.pi/agent/codex-edit.json`, not inside `node_modules`. User fields override bundled defaults; `models` replaces the complete default list. The file survives package updates and is shared by parent sessions and explicitly configured subagents using the same agent directory. Use `/reload` after editing it, and start new subagents. See [Codex Edit configuration](packages/codex-edit/README.md#configuration).
44
44
 
45
+ Child panes keep `Alt+P` and `/parent` navigation without a persistent shortcut footer hint. Editor status remains owned by the loaded editor extension.
46
+
45
47
  Herdr Subagents tasks require an explicit `agent` Persona; no default `worker` is selected. Choose `reviewer` for independent reviews of proposals, designs, documents, implementations, or analysis; `explorer` for codebase investigation; `worker` for file-changing work; and `analyst` for charter-defined agent-team roles. Control `list` returns names and descriptions for selection, plus configuration diagnostics when present; use `/herdr-subagents agents` and `settings` for detailed configuration. See [built-in Personas](packages/herdr-subagents/README.md#built-in-personas) for selection guidance and the distinction between parent-facing descriptions and child instructions. Personas can opt into local extension files or npm/git Pi packages through `extensions`. Set top-level `defaultExtensions` in `~/.pi/agent/herdr-subagents.json` (or project `.pi/herdr-subagents.json`) to share a fallback list; Persona-specific lists take precedence, and `[]` clears inherited sources. This uses native Pi package semantics: package-provided skills, prompt templates, and themes may also load, and `--no-skills` does not exclude explicitly selected package skills. See the [extension-source contract](packages/herdr-subagents/README.md#explicit-extension-sources-and-pi-packages) for precedence, isolation, and examples. The [real Herdr smoke test](packages/herdr-subagents/TESTING.md) preserves the configured analyst model and explicit authentication extensions while isolating unrelated settings.
46
48
 
47
49
  Multiple standalone children may be installed together. `pi-openai-account-pool` may also be installed alongside the complete toolkit because the toolkit does not embed it. Every other child in the table above, including `pi-background-tasks`, is embedded, so a second install exposes the same resource twice.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-toolkit",
3
- "version": "0.13.4",
3
+ "version": "0.13.5",
4
4
  "description": "Personal pi coding agent toolkit: background tasks, scheduler, /btw, /cache_export, apply_patch, browser automation, and bundled theme/skills.",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -1,3 +1,9 @@
1
+ ## 2026-10-22 - v0.7.4
2
+
3
+ - preserve Codex patch editing in child allowlists and hide the parent shortcut footer hint
4
+ - Hide the child parent-navigation footer hint and clear its legacy status on reload, while retaining `Alt+P`, `/parent`, and editor/plugin status.
5
+ - Permit `apply_patch` in the persistent CLI allowlist only for Personas that allow `edit`; test actual Pi startup selection, read-only limits and native fallback instead of relying on mocked tool lists or status labels.
6
+
1
7
  ## 2026-10-22 - v0.7.3
2
8
 
3
9
  - keep Persona selection lists concise
@@ -48,6 +48,10 @@ The tools and command are registered only when Pi is running inside Herdr with a
48
48
 
49
49
  Each task starts a single-task interactive Pi process in its own Herdr pane. Herdr first confirms the interactive Pi is ready, then the prompt is delivered exactly once. Prompt acknowledgement never decides task completion. Panes remain ordinary Herdr panes: they can be focused and inspected while work is running or after it settles. An early-finishing child stays at the Pi UI while sibling tasks continue. After the whole run settles, cleanup closes an unfocused run tab immediately or waits for the user to leave a focused run tab.
50
50
 
51
+ Use `Alt+P` or `/parent` in a child to focus its parent Agent pane. The shortcut has no persistent footer hint. Other extensions' status text, such as Codex Edit's `editor: apply_patch`, remains visible when those extensions are loaded and their editing tools are active. Changing Persona extensions does not change an already running child.
52
+
53
+ For a Persona whose `tools` includes `edit`, the child CLI also permits `apply_patch` so a trusted editing extension can switch protocols. This does not load an extension or enable both editors by itself. Personas without `edit` do not receive this permission, and a child without a patch extension keeps native `edit`.
54
+
51
55
  Cleanup belongs to the Pi session that created the run. Starting a new Pi session never scans historical runs or closes their recorded Herdr tabs. If Pi terminates before shutdown cleanup runs, any surviving Subagent tab remains visible for explicit inspection or cleanup.
52
56
 
53
57
  ## Result protocol
@@ -29,6 +29,8 @@ It verifies:
29
29
  - immutable terminal task records;
30
30
  - idempotent duplicate reports and rejection of conflicting replacements;
31
31
  - keeping a settled child alive for focus-aware cleanup instead of force-exiting its TUI;
32
+ - clearing the legacy parent shortcut footer hint while preserving other extension status, with `Alt+P` and `/parent` still focusing the exact parent pane;
33
+ - adding `apply_patch` to the child CLI allowlist only when the Persona permits `edit`;
32
34
  - preserving the configured smoke model, thinking level, and authentication extensions while isolating unrelated settings;
33
35
  - project-over-global smoke configuration, explicit extension clearing, model overrides, and local-source paths after moving to a temporary directory.
34
36
 
@@ -40,6 +42,8 @@ Run extension source loading. The direct-Pi fixture resolves global `defaultExte
40
42
  npm run load-test --workspace @maxiaochao/pi-herdr-subagents
41
43
  ```
42
44
 
45
+ The native CLI fixture also exercises an extension that switches `edit` to `apply_patch`. It checks the final active tools, not just status text: writable children retain the selected patch tool, read-only/no-editor children do not gain it, and children without the patch extension retain native `edit`. This deterministic probe does not call a model or test Codex Edit's parser.
46
+
43
47
  Run the real Herdr smoke test from a Herdr session:
44
48
 
45
49
  ```sh
@@ -96,7 +96,8 @@ export default function (pi: ExtensionAPI) {
96
96
  async handler(ctx) { await returnToParent(pi, ctx); },
97
97
  });
98
98
  pi.on("session_start", async (_event, ctx) => {
99
- if (process.env[PARENT_PANE_ENV]?.trim()) ctx.ui.setStatus("herdr-subagents-parent", `${RETURN_SHORTCUT} parent`);
99
+ // Remove the legacy hint on reload without touching editor/plugin status.
100
+ ctx.ui.setStatus("herdr-subagents-parent", undefined);
100
101
  });
101
102
  pi.registerTool({
102
103
  name: "subagent_report",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-herdr-subagents",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "Run inspectable interactive Pi subagent batches through Herdr.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -42,8 +42,11 @@ export function childPiArgs(task: LiveTaskRecord, systemPromptFile: string): str
42
42
  for (const extension of task.extensions ?? []) args.push("--extension", extension);
43
43
  if (task.model) args.push("--model", task.model);
44
44
  if (task.thinking) args.push("--thinking", task.thinking);
45
- if (task.tools?.length) args.push("--tools", [...new Set([...task.tools, "codemode", "subagent_report"])].join(","));
46
- else args.push("--tools", "+codemode");
45
+ if (task.tools?.length) {
46
+ // Pi keeps this allowlist after startup, including editor protocol switches.
47
+ const editorTools = task.tools.includes("edit") ? ["apply_patch"] : [];
48
+ args.push("--tools", [...new Set([...task.tools, ...editorTools, "codemode", "subagent_report"])].join(","));
49
+ } else args.push("--tools", "+codemode");
47
50
  for (const skill of task.skills ?? []) args.push("--skill", skill);
48
51
  return args;
49
52
  }
@@ -108,6 +108,14 @@ try {
108
108
  assert.ok(clearedArgs.some((arg) => arg.endsWith("/extensions/subagent-report.ts")));
109
109
  assert.ok(!clearedArgs.includes("./updated.ts"));
110
110
  assert.deepEqual(selected.extensions, ["./updated.ts"]);
111
+ console.log("PASS defaults reach child arguments and control listings, reload per call, and preserve mandatory extensions");
112
+
113
+ const editorArgs = childPiArgs({ id: "editor", sessionFile: join(root, "editor.jsonl"), tools: ["read", "edit", "write"] }, join(root, "system.md"));
114
+ assert.deepEqual(editorArgs[editorArgs.indexOf("--tools") + 1].split(","), ["read", "edit", "write", "apply_patch", "codemode", "subagent_report"]);
115
+ const readonlyArgs = childPiArgs({ id: "readonly", sessionFile: join(root, "readonly.jsonl"), tools: ["read", "bash"] }, join(root, "system.md"));
116
+ assert.ok(!readonlyArgs[readonlyArgs.indexOf("--tools") + 1].split(",").includes("apply_patch"));
117
+ console.log("PASS edit-capable children allow the patch editor without adding it to read-only tool lists");
118
+
111
119
  console.log("PASS concise Persona listings preserve diagnostics and detailed metadata; defaults reach child arguments and reload per call");
112
120
  } finally {
113
121
  rmSync(root, { recursive: true, force: true });
@@ -291,6 +291,42 @@ assert.throws(() => applyTaskStatus(terminal, "running"), /terminal tasks are im
291
291
  assert.match(notifications[1], /: (?:package|global|project), (?:read|write), model=.*thinking=.*tools=.*skills=.*extensions=/, "User-facing inspection must retain detailed configuration fields");
292
292
  }
293
293
 
294
+ {
295
+ const previousParent = process.env.PI_HERDR_SUBAGENTS_PARENT_PANE_ID;
296
+ process.env.PI_HERDR_SUBAGENTS_PARENT_PANE_ID = "test-workspace:parent";
297
+ const handlers = new Map();
298
+ const commands = new Map();
299
+ const shortcuts = new Map();
300
+ const calls = [];
301
+ const statuses = new Map([
302
+ ["herdr-subagents-parent", "alt+p parent"],
303
+ ["codex-edit", "editor: apply_patch"],
304
+ ]);
305
+ const ctx = { ui: { notify() {}, setStatus(key, value) { statuses.set(key, value); } } };
306
+ try {
307
+ registerSubagentReport({
308
+ registerTool() {},
309
+ registerCommand(name, command) { commands.set(name, command); },
310
+ registerShortcut(key, shortcut) { shortcuts.set(key, shortcut); },
311
+ on(name, handler) { handlers.set(name, handler); },
312
+ events: { on() {} },
313
+ async exec(command, args) { calls.push([command, args]); return { code: 0, stdout: "", stderr: "" }; },
314
+ });
315
+ await handlers.get("session_start")({}, ctx);
316
+ assert.equal(statuses.get("herdr-subagents-parent"), undefined, "child startup clears the old parent shortcut hint");
317
+ assert.equal(statuses.get("codex-edit"), "editor: apply_patch", "child navigation must not overwrite editor status");
318
+ await commands.get("parent").handler("", ctx);
319
+ await shortcuts.get("alt+p").handler(ctx);
320
+ assert.deepEqual(calls, [
321
+ ["herdr", ["agent", "focus", "test-workspace:parent"]],
322
+ ["herdr", ["agent", "focus", "test-workspace:parent"]],
323
+ ], "the parent command and shortcut must remain functional without a footer hint");
324
+ } finally {
325
+ if (previousParent === undefined) delete process.env.PI_HERDR_SUBAGENTS_PARENT_PANE_ID;
326
+ else process.env.PI_HERDR_SUBAGENTS_PARENT_PANE_ID = previousParent;
327
+ }
328
+ }
329
+
294
330
  {
295
331
  const directory = join(root, "report-idempotency");
296
332
  mkdirSync(directory, { recursive: true });
@@ -19,6 +19,15 @@ try {
19
19
  }));
20
20
  writeFileSync(join(packageDirectory, "extension.ts"), `export default function (pi) {
21
21
  pi.registerCommand("package-extension", { description: "Package extension loaded", handler() {} });
22
+ pi.registerTool({
23
+ name: "apply_patch", label: "Patch editor probe", description: "Editing protocol selection probe",
24
+ parameters: { type: "object", properties: {}, additionalProperties: false },
25
+ async execute() { return { content: [], details: {} }; },
26
+ });
27
+ pi.on("session_start", () => {
28
+ const active = pi.getActiveTools();
29
+ if (active.includes("edit")) pi.setActiveTools([...active.filter((name) => name !== "edit"), "apply_patch"]);
30
+ });
22
31
  }`);
23
32
  writeFileSync(join(packageDirectory, "skills", "package-skill", "SKILL.md"), "---\nname: package-skill\ndescription: Package skill loading probe\n---\n# Probe\n");
24
33
  writeFileSync(join(packageDirectory, "prompts", "package-prompt.md"), "Package prompt loading probe.");
@@ -58,6 +67,22 @@ try {
58
67
  for (const command of ["file-extension", "package-extension", "skill:package-skill", "package-prompt", "parent"]) assert.ok(loaded.commands.includes(command), `Missing ${command}: ${JSON.stringify(loaded)}`);
59
68
  assert.deepEqual([...loaded.tools].sort(), ["read", "subagent_report"]);
60
69
  console.log("PASS real Pi loads defaultExtensions file and package sources, including package skills/prompts despite discovery flags, without broadening the direct-Pi tool allowlist");
70
+
71
+ for (const [id, tools, extensions, expected] of [
72
+ ["patch-editor", ["read", "edit", "write"], analyst.extensions, ["read", "write", "apply_patch", "subagent_report"]],
73
+ ["no-editor", ["read", "write"], analyst.extensions, ["read", "write", "subagent_report"]],
74
+ ["native-editor", ["read", "edit", "write"], [fileExtension, probe], ["read", "edit", "write", "subagent_report"]],
75
+ ]) {
76
+ const childArgs = childPiArgs({ id, sessionFile: join(root, `${id}.jsonl`), tools, extensions }, join(root, "system.md"));
77
+ const builtin = childArgs.indexOf("builtin:codemode");
78
+ childArgs.splice(builtin - 1, 2);
79
+ const child = spawnSync(process.env.PI_TEST_BINARY?.trim() || "pi", [...childArgs, "--offline"], { cwd: root, env, encoding: "utf8", timeout: 30_000 });
80
+ assert.equal(child.error, undefined, String(child.error));
81
+ assert.equal(child.status, 0, child.stderr || child.stdout);
82
+ const actual = JSON.parse(readFileSync(resultPath, "utf8"));
83
+ assert.deepEqual([...actual.tools].sort(), [...expected].sort(), `${id}: the real CLI must preserve the selected editing protocol`);
84
+ }
85
+ console.log("PASS real Pi CLI permits apply_patch for edit-capable children, preserves native fallback, and denies it to read-only/no-editor children");
61
86
  } finally {
62
87
  rmSync(root, { recursive: true, force: true });
63
88
  }
@@ -430,7 +430,7 @@ try {
430
430
  await childEvents.get("session_start")?.({}, childContext);
431
431
  await childCommands.get("parent")?.handler("", childContext);
432
432
  await childShortcuts.get("alt+p")?.handler(childContext);
433
- check("child command and shortcut return to the exact parent Agent pane", childExecCalls.length === 2 && childExecCalls.every(([command, args]) => command === "herdr" && args.join(",") === "agent,focus,w1:p-parent") && childStatuses.some(([, value]) => value.includes("alt+p")));
433
+ check("child command and shortcut return to the exact parent Agent pane without a footer hint", childExecCalls.length === 2 && childExecCalls.every(([command, args]) => command === "herdr" && args.join(",") === "agent,focus,w1:p-parent") && childStatuses.every(([, value]) => value === undefined));
434
434
  const reportToolDirectory = join(root, "child-report-tool");
435
435
  const reportAttemptId = "11111111-1111-4111-8111-111111111111";
436
436
  mkdirSync(reportToolDirectory, { recursive: true });