@maxiaochao/pi-toolkit 0.13.3 → 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,13 @@
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
+
7
+ ## 2026-10-22 - v0.13.4
8
+
9
+ - herdr-subagents v0.7.3: keep Persona selection lists concise
10
+
1
11
  ## 2026-10-21 - v0.13.3
2
12
 
3
13
  - codex-edit v0.2.0: persist personal routing rules in the Pi agent directory across package updates
@@ -9,6 +19,7 @@
9
19
 
10
20
  ## Unreleased
11
21
 
22
+ - Herdr Subagents: show only Persona names and descriptions in model-facing selection lists, preserving diagnostics and detailed user-facing inspection.
12
23
  - Make Agent Team user-invoked only through `/skill:agent-team`, hidden from automatic model selection.
13
24
  - Add `npm run release:check -- vX.Y.Z` to wait for GitHub publication and verify exact npm versions, remote tags, integrity, and all tarball contents against the release commit, including coordinated child releases.
14
25
 
package/README.md CHANGED
@@ -42,7 +42,9 @@ 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
- 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. 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.
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
+
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.
48
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-toolkit",
3
- "version": "0.13.3",
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,14 @@
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
+
7
+ ## 2026-10-22 - v0.7.3
8
+
9
+ - keep Persona selection lists concise
10
+ - Limit model-facing Persona listings and unknown-Persona suggestions to names and descriptions. Preserve configuration diagnostics, detailed result metadata, and the `/herdr-subagents agents` and `settings` inspection commands.
11
+
1
12
  ## 2026-10-21 - v0.7.2
2
13
 
3
14
  - clarify final answer submission through subagent_report
@@ -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
@@ -119,7 +123,7 @@ A batch containing a write-capable Persona must explicitly or effectively use `c
119
123
 
120
124
  | Action | `runId` | Purpose |
121
125
  | --- | --- | --- |
122
- | `list` | Not accepted | List effective Personas, resolved options, and diagnostics. |
126
+ | `list` | Not accepted | List Persona names and descriptions for task selection, plus configuration diagnostics when present. |
123
127
  | `status` | Required | Show one run and its task states. |
124
128
  | `history` | Not accepted | List durable project-local run history. |
125
129
  | `cancel` | Required | Cancel an active run. |
@@ -127,6 +131,8 @@ A batch containing a write-capable Persona must explicitly or effectively use `c
127
131
 
128
132
  Settled tasks are immutable. Follow-up work starts a new task or run; there is no resume/respond lifecycle.
129
133
 
134
+ The model-facing `list` text does not expand tools, skills, extensions, models, thinking levels, or configuration sources. Describe routing-relevant capabilities and constraints in each Persona's description. Use `/herdr-subagents agents` and `/herdr-subagents settings` for detailed configuration; full resolved data remains in the tool result's `details` metadata.
135
+
130
136
  ### Slash command
131
137
 
132
138
  `/herdr-subagents` supports:
@@ -9,7 +9,8 @@ npm test --workspace @maxiaochao/pi-herdr-subagents
9
9
  It verifies:
10
10
 
11
11
  - global/project `defaultExtensions` replacement, trimming, source attribution, Persona JSON/Markdown precedence, explicit empty-list clearing, invalid-value diagnostics, and per-call reloads;
12
- - resolved defaults in control listings, settings output, and child CLI arguments while preserving mandatory extensions;
12
+ - concise Persona names/descriptions in model-facing listings and unknown-Persona errors, including empty lists and visible configuration diagnostics;
13
+ - resolved defaults in control result metadata, settings output, and child CLI arguments while preserving mandatory extensions;
13
14
  - strict serial start order at concurrency one;
14
15
  - bounded read-only parallelism;
15
16
  - ordered results despite concurrent completion;
@@ -28,6 +29,8 @@ It verifies:
28
29
  - immutable terminal task records;
29
30
  - idempotent duplicate reports and rejection of conflicting replacements;
30
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`;
31
34
  - preserving the configured smoke model, thinking level, and authentication extensions while isolating unrelated settings;
32
35
  - project-over-global smoke configuration, explicit extension clearing, model overrides, and local-source paths after moving to a temporary directory.
33
36
 
@@ -39,6 +42,8 @@ Run extension source loading. The direct-Pi fixture resolves global `defaultExte
39
42
  npm run load-test --workspace @maxiaochao/pi-herdr-subagents
40
43
  ```
41
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
+
42
47
  Run the real Herdr smoke test from a Herdr session:
43
48
 
44
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.2",
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
  }
@@ -60,10 +60,7 @@ export function listText(discovery: SubagentConfiguration): string {
60
60
  const lines = ["Available Herdr Subagents personas:"];
61
61
  if (discovery.personas.length === 0) lines.push("- (none)");
62
62
  for (const agent of discovery.personas) {
63
- const model = agent.model ?? "unresolved";
64
- const tools = agent.tools?.join(", ") ?? "Pi default built-in tools";
65
- const skills = agent.skills.join(", ") || "none";
66
- lines.push(`- ${agent.name}: ${agent.description} (source: ${agent.source}; access: ${agent.access}; model: ${model} [${agent.modelSource}]; thinking: ${agent.thinking ?? "default"}; tools: ${tools}; skills: ${skills}; extensions: ${agent.extensions.join(", ") || "none"} [${agent.extensionsSource}])`);
63
+ lines.push(`- ${agent.name}: ${agent.description}`);
67
64
  }
68
65
  if (discovery.diagnostics.length > 0) {
69
66
  lines.push("", "Persona diagnostics:", ...discovery.diagnostics.map((diagnostic) => `- ${diagnostic}`));
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { loadSubagentConfiguration } from "../src/config.ts";
6
6
  import { childPiArgs } from "../src/live-agent.ts";
7
- import { executeHerdrSubagentsControl } from "../src/tool.ts";
7
+ import { executeHerdrSubagentsControl, listText } from "../src/tool.ts";
8
8
 
9
9
  const root = mkdtempSync(join(tmpdir(), "herdr-default-extensions-"));
10
10
  const agents = join(root, "agents");
@@ -88,7 +88,18 @@ try {
88
88
  const response = await executeHerdrSubagentsControl({ action: "list" }, undefined, undefined, { cwd: root }, { agentsDirectory: agents, configurationPaths: paths });
89
89
  assert.deepEqual(response.details.agents.find(({ name }) => name === "plain").extensions, ["./updated.ts"]);
90
90
  assert.deepEqual(response.details.settings.defaultExtensions, ["./updated.ts"]);
91
- assert.match(response.content[0].text, /extensions: \.\/updated\.ts \[project\]/);
91
+ const expectedListing = [
92
+ "Available Herdr Subagents personas:",
93
+ "- cleared: Configuration fixture",
94
+ "- pinned: Configuration fixture",
95
+ "- plain: Configuration fixture",
96
+ ].join("\n");
97
+ assert.equal(response.content[0].text, expectedListing, "Model-facing listings must contain only Persona names and descriptions");
98
+ assert.equal(listText({ ...configuration, personas: [] }), "Available Herdr Subagents personas:\n- (none)");
99
+ write(paths.projectConfig, { defaultExtensions: "./invalid.ts" });
100
+ const diagnosed = await executeHerdrSubagentsControl({ action: "list" }, undefined, undefined, { cwd: root }, { agentsDirectory: agents, configurationPaths: paths });
101
+ assert.equal(diagnosed.content[0].text, `${expectedListing}\n\nPersona diagnostics:\n- project defaultExtensions must be an array of non-empty strings`, "Concise listings must still surface invalid configuration");
102
+ assert.deepEqual(diagnosed.details.diagnostics, ["project defaultExtensions must be an array of non-empty strings"]);
92
103
  write(paths.projectConfig, { defaultExtensions: [] });
93
104
  const cleared = persona(load());
94
105
  assert.deepEqual(cleared.extensions, []);
@@ -98,6 +109,14 @@ try {
98
109
  assert.ok(!clearedArgs.includes("./updated.ts"));
99
110
  assert.deepEqual(selected.extensions, ["./updated.ts"]);
100
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
+
119
+ console.log("PASS concise Persona listings preserve diagnostics and detailed metadata; defaults reach child arguments and reload per call");
101
120
  } finally {
102
121
  rmSync(root, { recursive: true, force: true });
103
122
  }
@@ -142,7 +142,7 @@ async function run(tasks, concurrency, herdr = fakeHerdr(), signal) {
142
142
  assert.match(laterMissing.content[0].text, /tasks\[1\]\.agent.*required/);
143
143
  const unknown = await execute({ tasks: [{ ...missing, agent: "unknown-persona" }] });
144
144
  assert.equal(unknown.isError, true);
145
- assert.match(unknown.content[0].text, /Unknown persona 'unknown-persona'/);
145
+ assert.equal(unknown.content[0].text, `Unknown persona 'unknown-persona'.\n\n${listed.content[0].text}`, "Unknown-Persona errors must reuse the concise selection list");
146
146
  assert.deepEqual(herdr.starts, [], "Invalid requests must not start an Agent");
147
147
  assert.equal(existsSync(join(root, ".pi", "herdr-subagents", "runs")), false, "Invalid requests must not create run records");
148
148
 
@@ -287,6 +287,44 @@ assert.throws(() => applyTaskStatus(terminal, "running"), /terminal tasks are im
287
287
  const notifications = [];
288
288
  await configuredCommands.get("herdr-subagents").handler("settings", { cwd: lifecycleRoot, ui: { notify(message) { notifications.push(message); } } });
289
289
  assert.match(notifications[0], /defaultExtensions=\[\] \(project\)/, "Settings must show empty defaults and their source");
290
+ await configuredCommands.get("herdr-subagents").handler("agents", { cwd: lifecycleRoot, ui: { notify(message) { notifications.push(message); } } });
291
+ assert.match(notifications[1], /: (?:package|global|project), (?:read|write), model=.*thinking=.*tools=.*skills=.*extensions=/, "User-facing inspection must retain detailed configuration fields");
292
+ }
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
+ }
290
328
  }
291
329
 
292
330
  {
@@ -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 });
@@ -661,7 +661,7 @@ try {
661
661
  const extensionHistory = await listSubagentHistory(extensionRoot);
662
662
  check("history exposes saved extension sources", JSON.stringify(extensionHistory[0]?.tasks[0]?.extensions) === JSON.stringify(extensionSources));
663
663
  const extensionList = await executeHerdrSubagents({ action: "list" }, undefined, undefined, { cwd: extensionRoot }, extensionDependencies);
664
- check("persona listing shows extension sources and their precedence", extensionList.content[0].text.includes("npm:@example/pi-tools@1.0.0") && extensionList.content[0].text.includes("project persona"));
664
+ check("persona listing keeps extension configuration in metadata only", !extensionList.content[0].text.includes("npm:@example/pi-tools@1.0.0") && extensionList.details.agents[0].extensionsSource === "project persona" && JSON.stringify(extensionList.details.agents[0].extensions) === JSON.stringify(extensionSources));
665
665
  writeFileSync(extensionPaths.projectConfig, JSON.stringify({ personas: { routed: { extensions: [] } } }));
666
666
  check("empty project extension list clears lower-precedence sources", extensionPersona()?.extensions?.length === 0 && extensionPersona()?.extensionsSource === "project persona");
667
667
  const extensionResumeHerdr = fakeHerdr();