@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 +11 -0
- package/README.md +3 -1
- package/package.json +1 -1
- package/packages/herdr-subagents/CHANGELOG.md +11 -0
- package/packages/herdr-subagents/README.md +7 -1
- package/packages/herdr-subagents/TESTING.md +6 -1
- package/packages/herdr-subagents/extensions/subagent-report.ts +2 -1
- package/packages/herdr-subagents/package.json +1 -1
- package/packages/herdr-subagents/src/live-agent.ts +5 -2
- package/packages/herdr-subagents/src/tool.ts +1 -4
- package/packages/herdr-subagents/tests/config.mjs +21 -2
- package/packages/herdr-subagents/tests/core-state-machine.mjs +39 -1
- package/packages/herdr-subagents/tests/extension-sources-load.mjs +25 -0
- package/packages/herdr-subagents/tests/run-tests.mjs +2 -2
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
|
-
|
|
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
|
+
"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
|
|
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
|
-
-
|
|
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
|
-
|
|
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",
|
|
@@ -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)
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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();
|