@maxiaochao/pi-toolkit 0.10.0 → 0.10.2
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 +16 -0
- package/README.md +0 -1
- package/global/AGENTS.md +33 -50
- package/package.json +1 -1
- package/packages/herdr-subagents/CHANGELOG.md +15 -0
- package/packages/herdr-subagents/README.md +166 -37
- package/packages/herdr-subagents/TESTING.md +5 -4
- package/packages/herdr-subagents/docs/demo.webm +0 -0
- package/packages/herdr-subagents/docs/screenshot.png +0 -0
- package/packages/herdr-subagents/extensions/herdr-subagents.ts +89 -50
- package/packages/herdr-subagents/package.json +1 -1
- package/packages/herdr-subagents/src/config.ts +9 -5
- package/packages/herdr-subagents/src/parameters.ts +16 -13
- package/packages/herdr-subagents/src/queued-runs.ts +3 -3
- package/packages/herdr-subagents/src/tool.ts +39 -6
- package/packages/herdr-subagents/src/validation.ts +16 -29
- package/packages/herdr-subagents/tests/extension-contract.ts +27 -9
- package/packages/herdr-subagents/tests/run-tests.mjs +21 -19
- package/packages/herdr-subagents/docs/demo.gif +0 -0
- package/skills/calldiff/LICENSE +0 -21
- package/skills/calldiff/NOTICE +0 -19
- package/skills/calldiff/SKILL.md +0 -121
|
@@ -15,7 +15,7 @@ import { cancelActiveSubagentRuns, reconcileSubagentRuns } from "../src/reconcil
|
|
|
15
15
|
import { continueQueuedRun } from "../src/continue-run.ts";
|
|
16
16
|
import { CliHerdrAutomation } from "../src/herdr.ts";
|
|
17
17
|
import { fleetEditorHasFocus, FleetSelection, fleetLines } from "../src/fleet.ts";
|
|
18
|
-
import { HERDR_ACTIONS,
|
|
18
|
+
import { HERDR_ACTIONS, validateControlParams, validateRunParams } from "../src/validation.ts";
|
|
19
19
|
import { resultPathForAttempt } from "../src/result-path.ts";
|
|
20
20
|
import { isSettledRunStatus, readTaskRecord } from "../src/records.ts";
|
|
21
21
|
import { RunPaneAllocator } from "../src/run-pane-allocator.ts";
|
|
@@ -136,22 +136,18 @@ function latestRunDirectory() {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
try {
|
|
139
|
-
check("
|
|
139
|
+
check("run validation accepts one or more tasks", validateRunParams({ tasks: [{ name: "one", prompt: "do it" }], concurrency: 2, background: true }) === undefined);
|
|
140
|
+
check("control validation accepts supported calls", [
|
|
140
141
|
{ action: "list" }, { action: "history" }, { action: "cleanup", runId: "run" },
|
|
141
142
|
{ action: "respond", runId: "run", answer: "yes", task: 1 },
|
|
142
143
|
{ action: "resume", runId: "run", task: 1, prompt: "continue" },
|
|
143
|
-
|
|
144
|
-
{ tasks: [{ name: "one", prompt: "do it" }], concurrency: 2, background: true },
|
|
145
|
-
].every((params) => validateToolParams(params) === undefined));
|
|
144
|
+
].every((params) => validateControlParams(params) === undefined));
|
|
146
145
|
check("all supported actions have shared validation and rendering metadata", ["list", "history", "cleanup", "respond", "resume"].every((action) => action in HERDR_ACTIONS));
|
|
147
|
-
check("
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
validateToolParams({ prompt: "one", runId: "run" }),
|
|
153
|
-
validateToolParams({ prompt: "one", task: 1 }),
|
|
154
|
-
validateToolParams({ tasks: [{ name: "one", prompt: "one" }], answer: "extra" }),
|
|
146
|
+
check("split validation rejects missing tasks and invalid control fields", [
|
|
147
|
+
validateRunParams({ tasks: [] }),
|
|
148
|
+
validateControlParams({ action: "list", prompt: "not allowed" }),
|
|
149
|
+
validateControlParams({ action: "respond", runId: "run" }),
|
|
150
|
+
validateControlParams({ action: "resume", runId: "run", task: 1 }),
|
|
155
151
|
].every((message) => typeof message === "string" && message.length > 0));
|
|
156
152
|
const fleet = new FleetSelection();
|
|
157
153
|
const fleetBatch = { label: "reviews", activity: [
|
|
@@ -257,24 +253,30 @@ try {
|
|
|
257
253
|
mkdirSync(projectAgents, { recursive: true });
|
|
258
254
|
writeFileSync(join(packageAgents, "worker.md"), `---\nname: worker\ndescription: Package worker\naccess: write\nmodel: package/model\nskills: package-skill\n---\nPackage prompt.`);
|
|
259
255
|
writeFileSync(join(packageAgents, "explorer.md"), `---\nname: explorer\ndescription: Package explorer\naccess: read\n---\nExplore.`);
|
|
256
|
+
writeFileSync(join(packageAgents, "thinker.md"), `---\nname: thinker\ndescription: Package thinker\nthinking: minimal\n---\nThink.`);
|
|
260
257
|
writeFileSync(join(globalAgents, "worker.md"), `---\nname: worker\ndescription: Global worker\naccess: read\n---\nGlobal prompt.`);
|
|
261
258
|
writeFileSync(join(projectAgents, "worker.md"), `---\nname: worker\ndescription: Project worker\n---\nProject prompt.`);
|
|
262
259
|
const globalConfig = join(configRoot, "global.json");
|
|
263
260
|
const projectConfig = join(configRoot, "project.json");
|
|
264
|
-
writeFileSync(globalConfig, JSON.stringify({ maxConcurrency: 8, defaultModel: "global/default", personas: { worker: { model: "global/worker", thinking: "low", skills: ["global-skill"] } } }));
|
|
265
|
-
writeFileSync(projectConfig, JSON.stringify({ maxConcurrency: 3, personas: { worker: { model: "project/worker", thinking: "high", skills: ["project-skill"] } } }));
|
|
261
|
+
writeFileSync(globalConfig, JSON.stringify({ maxConcurrency: 8, defaultModel: "global/default", defaultThinking: "medium", personas: { worker: { model: "global/worker", thinking: "low", skills: ["global-skill"] } } }));
|
|
262
|
+
writeFileSync(projectConfig, JSON.stringify({ maxConcurrency: 3, defaultThinking: "high", personas: { worker: { model: "project/worker", thinking: "high", skills: ["project-skill"] } } }));
|
|
266
263
|
const configurationPaths = { globalConfig, projectConfig, globalAgents, projectAgents };
|
|
267
264
|
const configuration = loadSubagentConfiguration(configRoot, packageAgents, "parent/model", configurationPaths);
|
|
268
265
|
const configuredWorker = configuration.personas.find((persona) => persona.name === "worker");
|
|
269
266
|
const configuredExplorer = configuration.personas.find((persona) => persona.name === "explorer");
|
|
270
|
-
|
|
267
|
+
const configuredThinker = configuration.personas.find((persona) => persona.name === "thinker");
|
|
268
|
+
check("built-in settings recursively merge global then project", configuration.settings.defaultConcurrency === 1 && configuration.settings.maxConcurrency === 3 && configuration.settings.defaultModel === "global/default" && configuration.settings.defaultThinking === "high" && configuration.settingSources.maxConcurrency === "project" && configuration.settingSources.defaultThinking === "project");
|
|
271
269
|
check("persona definitions use project global package precedence", configuredWorker?.source === "project" && configuredWorker.description === "Project worker" && configuredWorker.systemPrompt === "Project prompt." && configuredWorker.access === "write" && configuredExplorer?.source === "package" && configuredExplorer.access === "read");
|
|
272
270
|
check("persona configuration resolves model thinking and skills", configuredWorker?.model === "project/worker" && configuredWorker.modelSource === "project persona" && configuredWorker.thinking === "high" && configuredWorker.skills.join(",") === "project-skill");
|
|
273
|
-
|
|
271
|
+
check("project default thinking applies below persona settings", configuredExplorer?.thinking === "high" && configuredExplorer.thinkingSource === "project");
|
|
272
|
+
check("persona Markdown thinking overrides default thinking", configuredThinker?.thinking === "minimal" && configuredThinker.thinkingSource === "package persona");
|
|
273
|
+
writeFileSync(projectConfig, JSON.stringify({ defaultThinking: [], personas: { worker: { model: 42, thinking: [], skills: "invalid" } } }));
|
|
274
274
|
const invalidConfiguration = loadSubagentConfiguration(configRoot, packageAgents, "parent/model", configurationPaths);
|
|
275
275
|
const invalidWorker = invalidConfiguration.personas.find((persona) => persona.name === "worker");
|
|
276
276
|
check("invalid persona fields are diagnosed and ignored", invalidWorker?.model === "global/worker" && invalidWorker.thinking === "low" && invalidWorker.skills.join(",") === "global-skill" && invalidConfiguration.diagnostics.filter((message) => message.includes("persona 'worker'")).length === 3);
|
|
277
|
-
|
|
277
|
+
const invalidExplorer = invalidConfiguration.personas.find((persona) => persona.name === "explorer");
|
|
278
|
+
check("invalid project default thinking is diagnosed and global default remains effective", invalidExplorer?.thinking === "medium" && invalidExplorer.thinkingSource === "global" && invalidConfiguration.diagnostics.some((message) => message === "project defaultThinking must be a non-empty string"));
|
|
279
|
+
writeFileSync(projectConfig, JSON.stringify({ maxConcurrency: 3, defaultThinking: "high", personas: { worker: { model: "project/worker", thinking: "high", skills: ["project-skill"] } } }));
|
|
278
280
|
const configuredHerdr = fakeHerdr();
|
|
279
281
|
const configuredRun = await executeHerdrSubagents({ agent: "worker", model: "task/model", prompt: "Use configured values" }, undefined, undefined, { cwd: configRoot, model: { provider: "parent", id: "model" }, thinkingLevel: "medium" }, { agentsDirectory: packageAgents, configurationPaths, herdr: configuredHerdr });
|
|
280
282
|
const configuredStart = configuredHerdr.calls.find(([name]) => name === "startAgent")[1];
|
|
@@ -548,7 +550,7 @@ try {
|
|
|
548
550
|
const historyHerdr = fakeHerdr();
|
|
549
551
|
const historicalResume = await executeHerdrSubagents({ action: "resume", runId: backgroundId, task: 1, prompt: "follow up" }, undefined, undefined, { cwd: root, model: { provider: "fake", id: "model" } }, { agentsDirectory: join(process.cwd(), "agents"), herdr: historyHerdr });
|
|
550
552
|
const historyStartRequest = historyHerdr.calls.find(([name]) => name === "startAgent")[1];
|
|
551
|
-
check("historical resume preserves the saved Pi session and persona prompt", !historicalResume.isError && historyHerdr.calls[0][0] === "createTab" && historyStartRequest.args.includes(stableHistory.tasks[0].sessionFile) && historyStartRequest.args.includes(
|
|
553
|
+
check("historical resume preserves the saved Pi session and persona prompt", !historicalResume.isError && historyHerdr.calls[0][0] === "createTab" && historyStartRequest.args.includes(stableHistory.tasks[0].sessionFile) && historyStartRequest.args.includes(stableHistory.tasks[0].model) && historyStartRequest.args.some((value) => String(value).includes("subagent_report")) && existsSync(join(stableHistory.tasks[0].recordDirectory, "turns", "02.json")) && readFileSync(join(stableHistory.tasks[0].recordDirectory, "followup-system-prompt.md"), "utf8").includes("Investigate the assigned question without modifying files"));
|
|
552
554
|
const historicalTaskRecord = JSON.parse(readFileSync(join(stableHistory.tasks[0].recordDirectory, "task.json"), "utf8"));
|
|
553
555
|
const historicalTurnRecord = JSON.parse(readFileSync(join(stableHistory.tasks[0].recordDirectory, "turns", "02.json"), "utf8"));
|
|
554
556
|
check("historical task and turn retain their tab identity", historicalTaskRecord.tabId === historicalResume.details.tabId && historicalTurnRecord.tabId === historicalResume.details.tabId);
|
|
Binary file
|
package/skills/calldiff/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Tanishq Kancharla
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/skills/calldiff/NOTICE
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
calldiff Skill
|
|
2
|
-
|
|
3
|
-
This directory is copied verbatim from the calldiff repository:
|
|
4
|
-
https://github.com/tanishqkancharla/calldiff
|
|
5
|
-
skills/calldiff/SKILL.md at commit 2086f6e4e9ed3c410bba7c94ca3dde77791b1956 (main)
|
|
6
|
-
|
|
7
|
-
The upstream repository is licensed under the MIT License. A copy of that
|
|
8
|
-
license is included in LICENSE.
|
|
9
|
-
|
|
10
|
-
Upstream publishes no release tags, so this file is pinned to a main commit
|
|
11
|
-
instead of a tag. The CLI can also write its own copy with `calldiff skills
|
|
12
|
-
add` (to ~/.agents/skills/calldiff/SKILL.md), but that generated file is terser
|
|
13
|
-
than the committed one — it leaves the --entry/--file descriptions empty and
|
|
14
|
-
says 22 languages — so pi-toolkit ships the committed file.
|
|
15
|
-
|
|
16
|
-
To refresh, copy skills/calldiff/SKILL.md and LICENSE from a newer upstream
|
|
17
|
-
commit and update the commit above. The bundled commands were smoke-tested
|
|
18
|
-
against calldiff 0.5.0 (`diff`, `tree`, `reach`, `--entry`, `--file`,
|
|
19
|
-
`--maxDepth`/`--max-depth`, `--locs`, `--format json`).
|
package/skills/calldiff/SKILL.md
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: calldiff
|
|
3
|
-
description: Diff call stacks across git commits — like git diff, but for who-calls-whom. Shows which callees appeared, disappeared, or moved under an entrypoint across 23 languages (diff, tree, and reach). Use for agentic code review when call flow changed and line diffs bury the shape of the change.
|
|
4
|
-
requires_bin: calldiff
|
|
5
|
-
command: calldiff
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# calldiff diff
|
|
9
|
-
|
|
10
|
-
Diff call stacks between two git trees
|
|
11
|
-
|
|
12
|
-
## Arguments
|
|
13
|
-
|
|
14
|
-
| Name | Type | Required | Description |
|
|
15
|
-
|------|------|----------|-------------|
|
|
16
|
-
| `from` | `string` | no | Before ref (default: HEAD) |
|
|
17
|
-
| `to` | `string` | no | After ref (default: working tree) |
|
|
18
|
-
| `paths` | `array` | no | Limit to these path prefixes |
|
|
19
|
-
|
|
20
|
-
## Options
|
|
21
|
-
|
|
22
|
-
| Flag | Type | Default | Description |
|
|
23
|
-
|------|------|---------|-------------|
|
|
24
|
-
| `--entry` | `unknown` | | Entrypoint symbol(s): functionName or ClassName.method |
|
|
25
|
-
| `--file` | `unknown` | | Entrypoint file(s): indexed source path; expands to that file's exports |
|
|
26
|
-
| `--maxDepth` | `number` | `12` | Max call-tree depth |
|
|
27
|
-
| `--locs` | `boolean` | `false` | Show call-site source locations (file:line) |
|
|
28
|
-
| `--from` | `string` | | Left / "before" tree |
|
|
29
|
-
| `--to` | `string` | | Right / "after" tree |
|
|
30
|
-
|
|
31
|
-
## Examples
|
|
32
|
-
|
|
33
|
-
```sh
|
|
34
|
-
# HEAD vs working tree
|
|
35
|
-
calldiff diff
|
|
36
|
-
|
|
37
|
-
# One ref vs working tree
|
|
38
|
-
calldiff diff main
|
|
39
|
-
|
|
40
|
-
# Two commits / branches
|
|
41
|
-
calldiff diff abc123 def456
|
|
42
|
-
|
|
43
|
-
# Force entrypoints
|
|
44
|
-
calldiff diff main feature --entry createAgentSession
|
|
45
|
-
|
|
46
|
-
# File expands to that file's exports
|
|
47
|
-
calldiff diff main feature --file src/routes.ts
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
> Semantics match git diff: no refs → HEAD vs worktree; one ref → that vs worktree; two refs → compare those trees.
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
# calldiff reach
|
|
55
|
-
|
|
56
|
-
Find all call paths from an entrypoint to a target symbol
|
|
57
|
-
|
|
58
|
-
## Arguments
|
|
59
|
-
|
|
60
|
-
| Name | Type | Required | Description |
|
|
61
|
-
|------|------|----------|-------------|
|
|
62
|
-
| `ref` | `string` | no | Git ref (default: working tree) |
|
|
63
|
-
| `paths` | `array` | no | Limit to these path prefixes |
|
|
64
|
-
|
|
65
|
-
## Options
|
|
66
|
-
|
|
67
|
-
| Flag | Type | Default | Description |
|
|
68
|
-
|------|------|---------|-------------|
|
|
69
|
-
| `--entry` | `unknown` | | Entrypoint symbol(s): functionName or ClassName.method |
|
|
70
|
-
| `--file` | `unknown` | | Entrypoint file(s): indexed source path; expands to that file's exports |
|
|
71
|
-
| `--to` | `string` | | Target symbol to reach (functionName or ClassName.method) |
|
|
72
|
-
| `--maxDepth` | `number` | `12` | Max call-tree depth |
|
|
73
|
-
| `--locs` | `boolean` | `false` | Show call-site source locations (file:line) |
|
|
74
|
-
|
|
75
|
-
## Examples
|
|
76
|
-
|
|
77
|
-
```sh
|
|
78
|
-
# Paths in the working tree
|
|
79
|
-
calldiff reach --entry runCheckout --to sendEmail
|
|
80
|
-
|
|
81
|
-
# Paths at a commit, limited to a directory
|
|
82
|
-
calldiff reach HEAD examples/checkout --entry runCheckout --to sendEmail
|
|
83
|
-
|
|
84
|
-
# Paths from every export in a file
|
|
85
|
-
calldiff reach --file packages/api/src/flow.ts --to notify
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
# calldiff tree
|
|
91
|
-
|
|
92
|
-
View a call tree (no diff) for one or more entrypoints
|
|
93
|
-
|
|
94
|
-
## Arguments
|
|
95
|
-
|
|
96
|
-
| Name | Type | Required | Description |
|
|
97
|
-
|------|------|----------|-------------|
|
|
98
|
-
| `ref` | `string` | no | Git ref (default: working tree) |
|
|
99
|
-
| `paths` | `array` | no | Limit to these path prefixes |
|
|
100
|
-
|
|
101
|
-
## Options
|
|
102
|
-
|
|
103
|
-
| Flag | Type | Default | Description |
|
|
104
|
-
|------|------|---------|-------------|
|
|
105
|
-
| `--entry` | `unknown` | | Entrypoint symbol(s): functionName or ClassName.method |
|
|
106
|
-
| `--file` | `unknown` | | Entrypoint file(s): indexed source path; expands to that file's exports |
|
|
107
|
-
| `--maxDepth` | `number` | `12` | Max call-tree depth |
|
|
108
|
-
| `--locs` | `boolean` | `false` | Show call-site source locations (file:line) |
|
|
109
|
-
|
|
110
|
-
## Examples
|
|
111
|
-
|
|
112
|
-
```sh
|
|
113
|
-
# Tree from working tree
|
|
114
|
-
calldiff tree --entry createAgentSession
|
|
115
|
-
|
|
116
|
-
# Tree from a commit
|
|
117
|
-
calldiff tree HEAD --entry PiService.createAgentSession
|
|
118
|
-
|
|
119
|
-
# Every export in a file
|
|
120
|
-
calldiff tree --file packages/api/src/routes.ts
|
|
121
|
-
```
|