pi-herdr-spawn 1.0.0
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/LICENSE +21 -0
- package/README.md +106 -0
- package/bin/herdr-spawn.sh +107 -0
- package/extensions/herdr-spawn.ts +310 -0
- package/package.json +19 -0
- package/skills/herdr-spawn/README.md +40 -0
- package/skills/herdr-spawn/SKILL.md +135 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 duskoide
|
|
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/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# pi-herdr-spawn
|
|
2
|
+
|
|
3
|
+
Spawn pi agents in isolated Herdr panes to execute tasks in parallel.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pi install npm:pi-herdr-spawn
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or via git:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pi install git:github.com/duskoide/pi-herdr-spawn
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### Slash Commands
|
|
20
|
+
|
|
21
|
+
After installing, restart pi or run `/reload`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Basic usage - spawn with auto-generated name
|
|
25
|
+
/spawn Run tests and report failures
|
|
26
|
+
|
|
27
|
+
# With custom name
|
|
28
|
+
/spawn Review code pi-reviewer
|
|
29
|
+
|
|
30
|
+
# With custom name and model
|
|
31
|
+
/spawn Review code pi-reviewer claude-sonnet-5
|
|
32
|
+
|
|
33
|
+
# Quick spawn (shorthand)
|
|
34
|
+
/spawnp Run the linter and fix errors
|
|
35
|
+
|
|
36
|
+
# List running agents
|
|
37
|
+
/spawnlist
|
|
38
|
+
|
|
39
|
+
# Kill a specific agent
|
|
40
|
+
/spawnkill pi-test-runner
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Tool Usage
|
|
44
|
+
|
|
45
|
+
You can also use the `spawn_pi` tool directly:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
spawn_pi({
|
|
49
|
+
prompt: "Run tests and report any failures",
|
|
50
|
+
name: "pi-test-runner",
|
|
51
|
+
model: "gpt-4o",
|
|
52
|
+
timeout: 180000,
|
|
53
|
+
direction: "right"
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Command Line
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
~/.pi/agent/bin/herdr-spawn.sh <agent-name> "<prompt>" [--model <model>] [--timeout <ms>]
|
|
61
|
+
|
|
62
|
+
# Example:
|
|
63
|
+
~/.pi/agent/bin/herdr-spawn.sh pi-test "Run npm test and report results" --timeout 180000
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## How It Works
|
|
67
|
+
|
|
68
|
+
1. **Creates a new pane** - Splits the current pane to create an isolated terminal
|
|
69
|
+
2. **Starts pi agent** - Launches a fresh pi instance in the new pane
|
|
70
|
+
3. **Sends your prompt** - Submits the task and waits for completion
|
|
71
|
+
4. **Reads the response** - Gets the agent's output
|
|
72
|
+
5. **Cleans up** - Closes the pane and stops the agent automatically
|
|
73
|
+
|
|
74
|
+
## Requirements
|
|
75
|
+
|
|
76
|
+
- Pi must be running inside a Herdr-managed pane (`HERDR_ENV=1`)
|
|
77
|
+
- Herdr must be installed and running
|
|
78
|
+
|
|
79
|
+
## Use Cases
|
|
80
|
+
|
|
81
|
+
### Parallel Testing
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
/spawn npm test pi-tests
|
|
85
|
+
/spawn npm run lint pi-linter
|
|
86
|
+
/spawn npm run typecheck pi-types
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Code Review
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
spawn_pi({ prompt: "Review the recent git changes and suggest improvements" })
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Long-Running Tasks
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
spawn_pi({
|
|
99
|
+
prompt: "Run the build process and report any errors",
|
|
100
|
+
timeout: 300000
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# herdr-spawn.sh - Spawn a pi agent in a new Herdr pane
|
|
3
|
+
#
|
|
4
|
+
# Usage: herdr-spawn.sh <agent-name> <prompt> [--model <model>] [--timeout <ms>]
|
|
5
|
+
#
|
|
6
|
+
# Example:
|
|
7
|
+
# herdr-spawn.sh pi-test "Run tests and report results"
|
|
8
|
+
# herdr-spawn.sh pi-reviewer "Review the code changes" --model gpt-4o
|
|
9
|
+
# herdr-spawn.sh pi-builder "Build project" --model claude-sonnet-5 --timeout 180000
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
# Check Herdr environment
|
|
14
|
+
if [[ "${HERDR_ENV:-}" != "1" ]]; then
|
|
15
|
+
echo "Error: Not running in Herdr environment" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# Parse required arguments
|
|
20
|
+
AGENT_NAME="${1:?Usage: herdr-spawn.sh <agent-name> <prompt> [--model <model>] [--timeout <ms>]}"
|
|
21
|
+
PROMPT="${2:?Usage: herdr-spawn.sh <agent-name> <prompt> [--model <model>] [--timeout <ms>]}"
|
|
22
|
+
|
|
23
|
+
# Parse optional arguments
|
|
24
|
+
MODEL=""
|
|
25
|
+
TIMEOUT="120000"
|
|
26
|
+
shift 2 || true
|
|
27
|
+
|
|
28
|
+
while [[ $# -gt 0 ]]; do
|
|
29
|
+
case "$1" in
|
|
30
|
+
--model)
|
|
31
|
+
MODEL="$2"
|
|
32
|
+
shift 2
|
|
33
|
+
;;
|
|
34
|
+
--timeout)
|
|
35
|
+
TIMEOUT="$2"
|
|
36
|
+
shift 2
|
|
37
|
+
;;
|
|
38
|
+
*)
|
|
39
|
+
echo "Unknown option: $1" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
|
|
45
|
+
# Validate agent name (lowercase, hyphens, max 31 chars)
|
|
46
|
+
if [[ ! "$AGENT_NAME" =~ ^[a-z][a-z0-9_-]{0,30}$ ]]; then
|
|
47
|
+
echo "Error: Agent name must be lowercase letters, numbers, hyphens; max 31 chars" >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Get current pane context
|
|
52
|
+
CURRENT_PANE=$(herdr pane current --current)
|
|
53
|
+
PANE_ID=$(echo "$CURRENT_PANE" | jq -r '.result.pane.pane_id')
|
|
54
|
+
|
|
55
|
+
if [[ -z "$PANE_ID" ]]; then
|
|
56
|
+
echo "Error: Could not get current pane ID" >&2
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Create new pane
|
|
61
|
+
NEW_PANE=$(herdr pane split --current --direction right --cwd "$PWD" --no-focus)
|
|
62
|
+
NEW_PANE_ID=$(echo "$NEW_PANE" | jq -r '.result.pane.pane_id')
|
|
63
|
+
|
|
64
|
+
if [[ -z "$NEW_PANE_ID" ]]; then
|
|
65
|
+
echo "Error: Could not create new pane" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# Start pi agent (with optional model)
|
|
70
|
+
START_CMD="herdr agent start \"$AGENT_NAME\" --kind pi --pane \"$NEW_PANE_ID\""
|
|
71
|
+
if [[ -n "$MODEL" ]]; then
|
|
72
|
+
START_CMD="$START_CMD -- --model \"$MODEL\""
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
START_RESULT=$(eval "$START_CMD")
|
|
76
|
+
AGENT_STATUS=$(echo "$START_RESULT" | jq -r '.result.agent.agent_status')
|
|
77
|
+
|
|
78
|
+
if [[ "$AGENT_STATUS" == "unknown" ]]; then
|
|
79
|
+
echo "Error: Failed to start agent" >&2
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Send prompt
|
|
84
|
+
PROMPT_RESULT=$(herdr agent prompt "$AGENT_NAME" "$PROMPT" --wait --timeout "$TIMEOUT")
|
|
85
|
+
PROMPT_STATUS=$(echo "$PROMPT_RESULT" | jq -r '.type')
|
|
86
|
+
|
|
87
|
+
if [[ "$PROMPT_STATUS" != "agent_prompted" ]]; then
|
|
88
|
+
echo "Error: Failed to send prompt" >&2
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Read response
|
|
93
|
+
RESPONSE=$(herdr agent read "$AGENT_NAME" --source recent-unwrapped --lines 100)
|
|
94
|
+
|
|
95
|
+
# Output results
|
|
96
|
+
cat <<EOF
|
|
97
|
+
{
|
|
98
|
+
"pane_id": "$NEW_PANE_ID",
|
|
99
|
+
"agent_name": "$AGENT_NAME",
|
|
100
|
+
"model": "${MODEL:-parent}",
|
|
101
|
+
"status": "completed",
|
|
102
|
+
"response": $(echo "$RESPONSE" | jq -Rs .)
|
|
103
|
+
}
|
|
104
|
+
EOF
|
|
105
|
+
|
|
106
|
+
# Cleanup: Close the pane (this also stops the agent)
|
|
107
|
+
herdr pane close "$NEW_PANE_ID" > /dev/null 2>&1 || true
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const SpawnPiInput = z.object({
|
|
4
|
+
prompt: z.string().describe("The prompt to send to the spawned pi agent"),
|
|
5
|
+
name: z
|
|
6
|
+
.string()
|
|
7
|
+
.optional()
|
|
8
|
+
.describe(
|
|
9
|
+
"Unique name for the agent (lowercase, hyphens, max 31 chars). Auto-generated if not provided."
|
|
10
|
+
),
|
|
11
|
+
model: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe(
|
|
15
|
+
"Model to use for the spawned agent (e.g., 'gpt-4o', 'claude-sonnet-5', 'provider/model-id'). Uses parent model if not specified."
|
|
16
|
+
),
|
|
17
|
+
timeout: z
|
|
18
|
+
.number()
|
|
19
|
+
.optional()
|
|
20
|
+
.default(120000)
|
|
21
|
+
.describe("Timeout in milliseconds for the prompt (default: 120000)"),
|
|
22
|
+
direction: z
|
|
23
|
+
.enum(["right", "down"])
|
|
24
|
+
.optional()
|
|
25
|
+
.default("right")
|
|
26
|
+
.describe("Pane split direction (default: right)"),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export default function herdrSpawn() {
|
|
30
|
+
return {
|
|
31
|
+
name: "herdr-spawn",
|
|
32
|
+
description:
|
|
33
|
+
"Spawn a pi agent in a new Herdr pane to execute tasks in parallel. Use when you need to run work in a separate pane without blocking the current session.",
|
|
34
|
+
commands: [
|
|
35
|
+
{
|
|
36
|
+
name: "spawn",
|
|
37
|
+
description:
|
|
38
|
+
"Spawn a pi agent in a new pane to execute a task. Usage: /spawn <prompt> [name] [model] [timeout]",
|
|
39
|
+
execute: async (args: string) => {
|
|
40
|
+
// Parse command args: /spawn <prompt> [name] [model] [timeout]
|
|
41
|
+
const parts = args.trim().split(/\s+/);
|
|
42
|
+
|
|
43
|
+
if (parts.length === 0 || !parts[0]) {
|
|
44
|
+
return {
|
|
45
|
+
message: "Usage: /spawn <prompt> [agent-name] [model] [timeout-ms]\n\nExamples:\n/spawn Run tests and report failures\n/spawn Review code pi-review gpt-4o\n/spawn Build project pi-builder claude-sonnet-5 300000",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const prompt = parts[0];
|
|
50
|
+
const name = parts[1] || `pi-${Date.now().toString(36)}`;
|
|
51
|
+
const model = parts[2] || undefined;
|
|
52
|
+
const timeout = parts[3] ? parseInt(parts[3]) : 120000;
|
|
53
|
+
|
|
54
|
+
// Check Herdr environment
|
|
55
|
+
if (process.env.HERDR_ENV !== "1") {
|
|
56
|
+
return {
|
|
57
|
+
error: "Not running in Herdr environment. Cannot spawn pane.",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const { execSync } = require("child_process");
|
|
63
|
+
|
|
64
|
+
// Create new pane
|
|
65
|
+
const splitResult = execSync(
|
|
66
|
+
`herdr pane split --current --direction right --cwd "${process.env.PWD}" --no-focus`,
|
|
67
|
+
{ encoding: "utf-8" }
|
|
68
|
+
);
|
|
69
|
+
const split = JSON.parse(splitResult);
|
|
70
|
+
const newPaneId = split.result?.pane?.pane_id;
|
|
71
|
+
|
|
72
|
+
if (!newPaneId) {
|
|
73
|
+
return { error: "Could not create new pane" };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Build start command with optional model
|
|
77
|
+
let startCmd = `herdr agent start "${name}" --kind pi --pane "${newPaneId}"`;
|
|
78
|
+
if (model) {
|
|
79
|
+
startCmd += ` -- --model "${model}"`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Start agent
|
|
83
|
+
execSync(startCmd, { encoding: "utf-8" });
|
|
84
|
+
|
|
85
|
+
// Send prompt
|
|
86
|
+
const escapedPrompt = prompt.replace(/"/g, '\\"');
|
|
87
|
+
execSync(
|
|
88
|
+
`herdr agent prompt "${name}" "${escapedPrompt}" --wait --timeout ${timeout}`,
|
|
89
|
+
{ encoding: "utf-8" }
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Read response
|
|
93
|
+
const response = execSync(
|
|
94
|
+
`herdr agent read "${name}" --source recent-unwrapped --lines 100`,
|
|
95
|
+
{ encoding: "utf-8" }
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Cleanup
|
|
99
|
+
execSync(`herdr pane close "${newPaneId}" 2>/dev/null || true`, {
|
|
100
|
+
encoding: "utf-8",
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
message: `✅ Agent \"${name}\" completed in pane ${newPaneId}\n\n${response}`,
|
|
105
|
+
};
|
|
106
|
+
} catch (error: any) {
|
|
107
|
+
return { error: `Failed: ${error.message}` };
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "spawnp",
|
|
113
|
+
description:
|
|
114
|
+
"Quick spawn: /spawnp <prompt> [model] - spawns agent with auto-generated name",
|
|
115
|
+
execute: async (args: string) => {
|
|
116
|
+
if (!args.trim()) {
|
|
117
|
+
return { error: "Usage: /spawnp <prompt> [model]" };
|
|
118
|
+
}
|
|
119
|
+
// Delegate to /spawn with auto-generated name
|
|
120
|
+
const autoName = `pi-${Date.now().toString(36)}`;
|
|
121
|
+
return this.commands!.find((c) => c.name === "spawn")!.execute(
|
|
122
|
+
`${args.trim()} ${autoName} ${args.split(' ').slice(1).join(' ') || ''}`.trim()
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "spawnlist",
|
|
128
|
+
description: "List all currently running spawned agents",
|
|
129
|
+
execute: async () => {
|
|
130
|
+
if (process.env.HERDR_ENV !== "1") {
|
|
131
|
+
return { error: "Not running in Herdr environment" };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const { execSync } = require("child_process");
|
|
136
|
+
const result = execSync("herdr agent list", { encoding: "utf-8" });
|
|
137
|
+
const agents = JSON.parse(result);
|
|
138
|
+
|
|
139
|
+
if (!agents.result?.agents?.length) {
|
|
140
|
+
return { message: "No agents currently running." };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const list = agents.result.agents
|
|
144
|
+
.map((a: any) => `- ${a.name} (${a.agent_status}) in ${a.pane_id}`)
|
|
145
|
+
.join("\n");
|
|
146
|
+
|
|
147
|
+
return { message: `Running agents:\n${list}` };
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
return { error: `Failed: ${error.message}` };
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "spawnkill",
|
|
155
|
+
description: "Kill a spawned agent by name: /spawnkill <agent-name>",
|
|
156
|
+
execute: async (args: string) => {
|
|
157
|
+
const agentName = args.trim();
|
|
158
|
+
if (!agentName) {
|
|
159
|
+
return { error: "Usage: /spawnkill <agent-name>" };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (process.env.HERDR_ENV !== "1") {
|
|
163
|
+
return { error: "Not running in Herdr environment" };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const { execSync } = require("child_process");
|
|
168
|
+
|
|
169
|
+
// Get agent info to find pane ID
|
|
170
|
+
const agentInfo = execSync(`herdr agent get "${agentName}"`, {
|
|
171
|
+
encoding: "utf-8",
|
|
172
|
+
});
|
|
173
|
+
const agent = JSON.parse(agentInfo);
|
|
174
|
+
const paneId = agent.result?.agent?.pane_id;
|
|
175
|
+
|
|
176
|
+
if (!paneId) {
|
|
177
|
+
return { error: `Agent "${agentName}" not found` };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Close the pane (this stops the agent)
|
|
181
|
+
execSync(`herdr pane close "${paneId}"`, { encoding: "utf-8" });
|
|
182
|
+
|
|
183
|
+
return { message: `✅ Killed agent "${agentName}" in pane ${paneId}` };
|
|
184
|
+
} catch (error: any) {
|
|
185
|
+
return { error: `Failed: ${error.message}` };
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
tools: [
|
|
191
|
+
{
|
|
192
|
+
name: "spawn_pi",
|
|
193
|
+
description:
|
|
194
|
+
"Spawn a pi agent in a new Herdr pane, send a prompt, wait for response, and return the result. The pane is automatically closed after completion.",
|
|
195
|
+
inputSchema: SpawnPiInput,
|
|
196
|
+
execute: async (input: z.infer<typeof SpawnPiInput>) => {
|
|
197
|
+
// Check if we're in Herdr
|
|
198
|
+
const herdrEnv = process.env.HERDR_ENV;
|
|
199
|
+
if (herdrEnv !== "1") {
|
|
200
|
+
return {
|
|
201
|
+
error: "Not running in Herdr environment. Cannot spawn pane.",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const { prompt, timeout = 120000, direction = "right" } = input;
|
|
206
|
+
const agentName =
|
|
207
|
+
input.name ||
|
|
208
|
+
`pi-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
209
|
+
|
|
210
|
+
// Validate agent name
|
|
211
|
+
if (!/^[a-z][a-z0-9_-]{0,30}$/.test(agentName)) {
|
|
212
|
+
return {
|
|
213
|
+
error:
|
|
214
|
+
"Agent name must be lowercase letters, numbers, hyphens; max 31 chars",
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
// We'll use bash commands to interact with herdr CLI
|
|
220
|
+
// since this extension runs in the pi process
|
|
221
|
+
|
|
222
|
+
// Get current pane context
|
|
223
|
+
const currentResult = await execCommand(
|
|
224
|
+
"herdr pane current --current"
|
|
225
|
+
);
|
|
226
|
+
const current = JSON.parse(currentResult);
|
|
227
|
+
const currentPaneId = current.result?.pane?.pane_id;
|
|
228
|
+
|
|
229
|
+
if (!currentPaneId) {
|
|
230
|
+
return { error: "Could not get current pane ID" };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Create new pane
|
|
234
|
+
const splitResult = await execCommand(
|
|
235
|
+
`herdr pane split --current --direction ${direction} --cwd "${process.env.PWD}" --no-focus`
|
|
236
|
+
);
|
|
237
|
+
const split = JSON.parse(splitResult);
|
|
238
|
+
const newPaneId = split.result?.pane?.pane_id;
|
|
239
|
+
|
|
240
|
+
if (!newPaneId) {
|
|
241
|
+
return { error: "Could not create new pane" };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Build agent start command with optional model argument
|
|
245
|
+
let startCmd = `herdr agent start "${agentName}" --kind pi --pane "${newPaneId}"`;
|
|
246
|
+
if (model) {
|
|
247
|
+
startCmd += ` -- --model "${model}"`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Start pi agent
|
|
251
|
+
const startResult = await execCommand(startCmd);
|
|
252
|
+
const start = JSON.parse(startResult);
|
|
253
|
+
|
|
254
|
+
if (!start.result?.agent?.agent_status) {
|
|
255
|
+
return { error: "Failed to start agent" };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Send prompt (escape quotes in prompt)
|
|
259
|
+
const escapedPrompt = prompt.replace(/"/g, '\\"');
|
|
260
|
+
const promptResult = await execCommand(
|
|
261
|
+
`herdr agent prompt "${agentName}" "${escapedPrompt}" --wait --timeout ${timeout}`
|
|
262
|
+
);
|
|
263
|
+
const promptRes = JSON.parse(promptResult);
|
|
264
|
+
|
|
265
|
+
if (promptRes.type !== "agent_prompted") {
|
|
266
|
+
// Cleanup on failure
|
|
267
|
+
await execCommand(`herdr pane close "${newPaneId}" 2>/dev/null || true`);
|
|
268
|
+
return { error: "Failed to send prompt" };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Read response
|
|
272
|
+
const readResult = await execCommand(
|
|
273
|
+
`herdr agent read "${agentName}" --source recent-unwrapped --lines 100`
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Cleanup: close the pane
|
|
277
|
+
await execCommand(`herdr pane close "${newPaneId}" 2>/dev/null || true`);
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
pane_id: newPaneId,
|
|
281
|
+
agent_name: agentName,
|
|
282
|
+
status: "completed",
|
|
283
|
+
response: readResult,
|
|
284
|
+
};
|
|
285
|
+
} catch (error) {
|
|
286
|
+
return {
|
|
287
|
+
error: `Failed to spawn pi agent: ${error.message}`,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Helper function to execute bash commands
|
|
297
|
+
async function execCommand(command: string): Promise<string> {
|
|
298
|
+
const { execSync } = require("child_process");
|
|
299
|
+
try {
|
|
300
|
+
return execSync(command, {
|
|
301
|
+
encoding: "utf-8",
|
|
302
|
+
timeout: 30000,
|
|
303
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
304
|
+
});
|
|
305
|
+
} catch (error: any) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Command failed: ${command}\n${error.stderr || error.message}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-herdr-spawn",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Spawn pi agents in isolated Herdr panes for parallel task execution",
|
|
5
|
+
"keywords": ["pi-package", "herdr", "agent", "spawn", "parallel", "multiplexer"],
|
|
6
|
+
"author": "duskoide",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/duskoide/pi-herdr-spawn"
|
|
11
|
+
},
|
|
12
|
+
"pi": {
|
|
13
|
+
"skills": ["./skills"],
|
|
14
|
+
"extensions": ["./extensions"]
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Herdr Spawn Skill
|
|
2
|
+
|
|
3
|
+
Spawn pi agents in isolated Herdr panes to execute tasks in parallel.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
After installing this package with `pi install npm:pi-herdr-spawn`, use the slash commands:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Basic usage - spawn with auto-generated name
|
|
11
|
+
/spawn Run tests and report failures
|
|
12
|
+
|
|
13
|
+
# With custom name
|
|
14
|
+
/spawn Review code pi-reviewer
|
|
15
|
+
|
|
16
|
+
# With custom name and model
|
|
17
|
+
/spawn Review code pi-reviewer claude-sonnet-5
|
|
18
|
+
|
|
19
|
+
# Quick spawn (shorthand)
|
|
20
|
+
/spawnp Run the linter and fix errors
|
|
21
|
+
|
|
22
|
+
# List running agents
|
|
23
|
+
/spawnlist
|
|
24
|
+
|
|
25
|
+
# Kill a specific agent
|
|
26
|
+
/spawnkill pi-test-runner
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## How It Works
|
|
30
|
+
|
|
31
|
+
1. **Creates a new pane** - Splits the current pane to create an isolated terminal
|
|
32
|
+
2. **Starts pi agent** - Launches a fresh pi instance in the new pane
|
|
33
|
+
3. **Sends your prompt** - Submits the task and waits for completion
|
|
34
|
+
4. **Reads the response** - Gets the agent's output
|
|
35
|
+
5. **Cleans up** - Closes the pane and stops the agent automatically
|
|
36
|
+
|
|
37
|
+
## Requirements
|
|
38
|
+
|
|
39
|
+
- Pi must be running inside a Herdr-managed pane (`HERDR_ENV=1`)
|
|
40
|
+
- Herdr must be installed and running
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: herdr-spawn
|
|
3
|
+
description: Spawns a pi agent in a new Herdr pane to execute isolated tasks in parallel. Use when you need to run work in a separate pane without blocking the current session.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Herdr Spawn
|
|
7
|
+
|
|
8
|
+
Spawn pi agents in isolated Herdr panes to execute tasks in parallel.
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
- Running long tasks without blocking the current session
|
|
13
|
+
- Parallel execution of independent tasks
|
|
14
|
+
- Isolating potentially destructive operations
|
|
15
|
+
- Running background monitoring or testing
|
|
16
|
+
|
|
17
|
+
## Workflow
|
|
18
|
+
|
|
19
|
+
### 1. Verify Herdr Environment
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
test "${HERDR_ENV:-}" = 1 && echo "In Herdr" || echo "Not in Herdr"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If not in Herdr, report the error and stop.
|
|
26
|
+
|
|
27
|
+
### 2. Get Current Context
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
herdr pane current --current
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Extract the `pane_id` from the response.
|
|
34
|
+
|
|
35
|
+
### 3. Create New Pane
|
|
36
|
+
|
|
37
|
+
Split to create a sibling pane (preserves working directory and user focus):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
herdr pane split --current --direction right --cwd "$PWD" --no-focus
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Extract the new `pane_id` from `.result.pane.pane_id`.
|
|
44
|
+
|
|
45
|
+
### 4. Start Pi Agent
|
|
46
|
+
|
|
47
|
+
Start a pi agent with a unique, descriptive name. Optionally specify a model:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Without model (uses parent's model)
|
|
51
|
+
herdr agent start <unique-name> --kind pi --pane <new-pane-id>
|
|
52
|
+
|
|
53
|
+
# With specific model
|
|
54
|
+
herdr agent start <unique-name> --kind pi --pane <new-pane-id> -- --model <model-id>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Naming rules:**
|
|
58
|
+
- Lowercase letters, numbers, hyphens only
|
|
59
|
+
- Maximum 31 characters
|
|
60
|
+
- Must be unique among live agents
|
|
61
|
+
- Example: `pi-test-runner`, `pi-refactor-auth`
|
|
62
|
+
|
|
63
|
+
**Model examples:**
|
|
64
|
+
- `gpt-4o` - Fast, good for simple tasks
|
|
65
|
+
- `claude-sonnet-5` - Strong reasoning
|
|
66
|
+
- `provider/model-id` - Full provider specification
|
|
67
|
+
|
|
68
|
+
### 5. Send Prompt and Wait
|
|
69
|
+
|
|
70
|
+
Submit the task and wait for completion:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
herdr agent prompt <agent-name> "<prompt>" --wait --timeout 120000
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For complex tasks, increase the timeout.
|
|
77
|
+
|
|
78
|
+
### 6. Read Response
|
|
79
|
+
|
|
80
|
+
Get the agent's output:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
herdr agent read <agent-name> --source recent-unwrapped --lines 100
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 7. Report Results
|
|
87
|
+
|
|
88
|
+
Provide:
|
|
89
|
+
- Pane ID where the agent ran
|
|
90
|
+
- Agent name
|
|
91
|
+
- Complete response from the agent
|
|
92
|
+
- Any errors encountered
|
|
93
|
+
|
|
94
|
+
### 8. Cleanup (Important!)
|
|
95
|
+
|
|
96
|
+
After getting the response, close the agent and pane to free resources:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Close the pane (this also stops the agent)
|
|
100
|
+
herdr pane close <pane-id>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Note:** Closing the pane automatically stops any agent running in it. Only close panes you created!
|
|
104
|
+
|
|
105
|
+
## Example Usage
|
|
106
|
+
|
|
107
|
+
**User request:** "Run tests in a separate pane using a fast model"
|
|
108
|
+
|
|
109
|
+
**Agent execution:**
|
|
110
|
+
1. Verify Herdr environment
|
|
111
|
+
2. Create new pane: `herdr pane split --current --direction right --cwd /home/pn --no-focus`
|
|
112
|
+
3. Start agent with model: `herdr agent start pi-test-run --kind pi --pane w7:p2 -- --model gpt-4o`
|
|
113
|
+
4. Send prompt: `herdr agent prompt pi-test-run "Run the test suite with 'npm test' and report any failures" --wait`
|
|
114
|
+
5. Read output: `herdr agent read pi-test-run --source recent-unwrapped --lines 150`
|
|
115
|
+
6. Report: "Tests completed in pane w7:p2. 15 tests passed, 2 failed. Here are the failures: ..."
|
|
116
|
+
7. Cleanup: `herdr pane close w7:p2`
|
|
117
|
+
|
|
118
|
+
**User request:** "Review code with claude-sonnet-5"
|
|
119
|
+
|
|
120
|
+
**Agent execution:**
|
|
121
|
+
1. Verify Herdr environment
|
|
122
|
+
2. Create new pane
|
|
123
|
+
3. Start agent with model: `herdr agent start pi-review --kind pi --pane w7:p3 -- --model claude-sonnet-5`
|
|
124
|
+
4. Send prompt: `herdr agent prompt pi-review "Review the recent code changes" --wait`
|
|
125
|
+
5. Read output
|
|
126
|
+
6. Report results
|
|
127
|
+
7. Cleanup
|
|
128
|
+
|
|
129
|
+
## Important Notes
|
|
130
|
+
|
|
131
|
+
- Always use `--no-focus` to keep the user's focus on the original pane
|
|
132
|
+
- Use unique agent names to avoid conflicts
|
|
133
|
+
- The spawned agent has access to the same tools and context as the parent
|
|
134
|
+
- For very long tasks, consider using `--timeout` with appropriate values
|
|
135
|
+
- If reading output fails (alternate screen), ask the agent to write results to a file and read that instead
|