pi-open-agents 0.1.13 → 0.1.15
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/README.md +17 -3
- package/package.json +2 -1
- package/src/config/schema.ts +6 -2
- package/src/permission/evaluator.ts +8 -4
- package/src/subagent/env.ts +0 -1
- package/src/subagent/executor.ts +38 -2
- package/src/tui/tools.ts +9 -0
package/README.md
CHANGED
|
@@ -91,8 +91,10 @@ permission:
|
|
|
91
91
|
"subagent": deny # no delegation
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
Tool names are normalized automatically: `
|
|
95
|
-
`
|
|
94
|
+
Tool names are normalized automatically: `task` → `subagent`, `vscode` → `read`,
|
|
95
|
+
`apply_patch` → `edit` (OpenCode-only tool). In pi, `write` and `edit` are
|
|
96
|
+
**separate** tools — `write` creates/overwrites files, `edit` does search-and-
|
|
97
|
+
replace — so they have separate permission categories.
|
|
96
98
|
|
|
97
99
|
### Auto-injected delegation guidance
|
|
98
100
|
|
|
@@ -118,6 +120,11 @@ This means:
|
|
|
118
120
|
- Each subagent runs with exactly the model and tools it declares
|
|
119
121
|
- Skills load per-agent, with wildcard support (`security-*`, `git-*`)
|
|
120
122
|
|
|
123
|
+
The `--tools` whitelist is derived from both explicit `tools:` arrays and
|
|
124
|
+
permission allow-lists. An agent with `permission: { read: allow, edit: allow }`
|
|
125
|
+
will only get `read` and `edit` in the child process. Wildcard permissions
|
|
126
|
+
(`*: allow`) cannot produce a finite whitelist — the child gets all tools.
|
|
127
|
+
|
|
121
128
|
### OpenCode compatibility
|
|
122
129
|
|
|
123
130
|
Your `.opencode/agent/` files work as-is. The `tools` map format is auto-converted
|
|
@@ -133,6 +140,10 @@ tools:
|
|
|
133
140
|
bash: false
|
|
134
141
|
```
|
|
135
142
|
|
|
143
|
+
The `tools` map is converted to permission rules and **also restricts the
|
|
144
|
+
subagent child process** — a subagent with `tools: { read: true, bash: false }`
|
|
145
|
+
will only have access to the `read` tool when spawned.
|
|
146
|
+
|
|
136
147
|
### Mode-based visibility
|
|
137
148
|
|
|
138
149
|
| `mode` | TUI selector | `subagent` tool | `set_agent` tool |
|
|
@@ -249,9 +260,12 @@ Optional cleanup:
|
|
|
249
260
|
|
|
250
261
|
## Development
|
|
251
262
|
|
|
263
|
+
**Contributing?** Please read the [Contributing Guide](./CONTRIBUTING.md) before
|
|
264
|
+
opening an issue or pull request.
|
|
265
|
+
|
|
252
266
|
```bash
|
|
253
267
|
npm install
|
|
254
|
-
npm test #
|
|
268
|
+
npm test # 117 tests
|
|
255
269
|
npm run typecheck # tsc --noEmit
|
|
256
270
|
```
|
|
257
271
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-open-agents",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Unified agent and subagent management for pi coding agent, with OpenCode-compatible agent definitions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/node": "^24.0.0",
|
|
66
66
|
"tsx": "^4.19.0",
|
|
67
|
+
"typebox": "^1.3.10",
|
|
67
68
|
"typescript": "^5.6.0"
|
|
68
69
|
}
|
|
69
70
|
}
|
package/src/config/schema.ts
CHANGED
|
@@ -183,6 +183,10 @@ function parseToolsAndPermission(
|
|
|
183
183
|
* Convert OpenCode-style tools map to PermissionConfig.
|
|
184
184
|
*
|
|
185
185
|
* { "read": true, "write": false } → { "read": "allow", "write": "deny" }
|
|
186
|
+
*
|
|
187
|
+
* `write` and `edit` are kept as separate keys — in pi they are separate
|
|
188
|
+
* tools (create/overwrite vs search-and-replace). Only `apply_patch`
|
|
189
|
+
* (OpenCode-only) is mapped to `edit`.
|
|
186
190
|
*/
|
|
187
191
|
function toolsMapToPermission(toolsMap: Record<string, unknown>): PermissionConfig {
|
|
188
192
|
const config: PermissionConfig = {};
|
|
@@ -191,8 +195,8 @@ function toolsMapToPermission(toolsMap: Record<string, unknown>): PermissionConf
|
|
|
191
195
|
// Skip non-boolean values
|
|
192
196
|
if (typeof enabled !== "boolean") continue;
|
|
193
197
|
|
|
194
|
-
// Map
|
|
195
|
-
const permKey = toolName === "
|
|
198
|
+
// Map OpenCode-only tools to their pi equivalent
|
|
199
|
+
const permKey = toolName === "apply_patch"
|
|
196
200
|
? "edit"
|
|
197
201
|
: toolName;
|
|
198
202
|
|
|
@@ -20,15 +20,19 @@ import { matchPattern } from "./matcher.ts";
|
|
|
20
20
|
/**
|
|
21
21
|
* Map a tool name to its permission category.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Some tools share a permission category with another tool:
|
|
24
|
+
* apply_patch → "edit" (OpenCode-only tool, functionally equivalent to edit)
|
|
25
25
|
* list_mcp_resources, read_mcp_resource → "read"
|
|
26
26
|
*
|
|
27
|
+
* In pi, `write` and `edit` are SEPARATE tools (create/overwrite vs
|
|
28
|
+
* search-and-replace). They must stay as separate permission categories so
|
|
29
|
+
* that `{edit: true, write: false}` works as expected.
|
|
30
|
+
*
|
|
27
31
|
* Everything else maps by tool name directly.
|
|
28
32
|
*/
|
|
29
33
|
export function toolToPermission(toolName: string): string {
|
|
30
|
-
//
|
|
31
|
-
if (toolName === "
|
|
34
|
+
// OpenCode's apply_patch maps to pi's edit (no apply_patch tool in pi)
|
|
35
|
+
if (toolName === "apply_patch") {
|
|
32
36
|
return "edit";
|
|
33
37
|
}
|
|
34
38
|
|
package/src/subagent/env.ts
CHANGED
package/src/subagent/executor.ts
CHANGED
|
@@ -143,6 +143,41 @@ const defaultExecutorFs: ExecutorFs = {
|
|
|
143
143
|
|
|
144
144
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Derive a tools whitelist from the agent definition for the child process.
|
|
148
|
+
*
|
|
149
|
+
* The `--tools` CLI flag is a whitelist (include only these). It works with:
|
|
150
|
+
* - Explicit `agent.tools` array (pi-style CSV)
|
|
151
|
+
* - Simple permission allow-lists (keys with "allow", no wildcard)
|
|
152
|
+
*
|
|
153
|
+
* Does NOT work with:
|
|
154
|
+
* - Wildcard permissions (`*: allow` + specific denies) — can't derive a
|
|
155
|
+
* finite whitelist. The child gets all tools. This is a known limitation
|
|
156
|
+
* of the `--tools` flag (it's a whitelist, not a deny-list).
|
|
157
|
+
* - Pattern-based permissions (`edit: { "*.env": deny }`) — patterns can't
|
|
158
|
+
* be expressed as a flat tool whitelist.
|
|
159
|
+
*/
|
|
160
|
+
function deriveToolsWhitelist(agent: AgentDefinition): string[] | undefined {
|
|
161
|
+
// Explicit tools array — use directly
|
|
162
|
+
if (agent.tools && agent.tools.length > 0) {
|
|
163
|
+
return agent.tools;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Derive from permission config
|
|
167
|
+
if (agent.permission) {
|
|
168
|
+
// Can't derive a finite whitelist when wildcard is present
|
|
169
|
+
if ("*" in agent.permission) return undefined;
|
|
170
|
+
|
|
171
|
+
const allowed = Object.entries(agent.permission)
|
|
172
|
+
.filter(([, value]) => value === "allow")
|
|
173
|
+
.map(([key]) => key);
|
|
174
|
+
|
|
175
|
+
return allowed.length > 0 ? allowed : undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
146
181
|
function emptyUsage(): AgentUsage {
|
|
147
182
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0 };
|
|
148
183
|
}
|
|
@@ -478,8 +513,9 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
478
513
|
args.push("--thinking", options.agent.thinking);
|
|
479
514
|
|
|
480
515
|
// Tools whitelist
|
|
481
|
-
|
|
482
|
-
|
|
516
|
+
const toolsWhitelist = deriveToolsWhitelist(options.agent);
|
|
517
|
+
if (toolsWhitelist && toolsWhitelist.length > 0) {
|
|
518
|
+
args.push("--tools", toolsWhitelist.join(","));
|
|
483
519
|
}
|
|
484
520
|
|
|
485
521
|
// System prompt file (append or replace)
|
package/src/tui/tools.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { Type } from "typebox";
|
|
|
14
14
|
import type { AgentDefinition } from "../types.ts";
|
|
15
15
|
import { selectableAgents } from "../discovery/loader.ts";
|
|
16
16
|
import type { AgentManager } from "../primary/manager.ts";
|
|
17
|
+
import { isSubagentProcess } from "../subagent/env.ts";
|
|
17
18
|
import { updateBanner } from "./banner.ts";
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -32,6 +33,10 @@ export function registerAgentTools(manager: AgentManager, pi: ExtensionAPI): voi
|
|
|
32
33
|
reason: Type.Optional(Type.String({ description: "Reason for switching agents" })),
|
|
33
34
|
}),
|
|
34
35
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx: ExtensionContext) {
|
|
36
|
+
if (isSubagentProcess(process.env)) {
|
|
37
|
+
throw new Error("set_agent is only available to the primary agent, not subagents.");
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
const { agent: name, reason } = params;
|
|
36
41
|
|
|
37
42
|
const agent = manager.getAgent(name);
|
|
@@ -40,6 +45,10 @@ export function registerAgentTools(manager: AgentManager, pi: ExtensionAPI): voi
|
|
|
40
45
|
throw new Error(`Unknown agent "${name}". Available: ${available}`);
|
|
41
46
|
}
|
|
42
47
|
|
|
48
|
+
if (agent.mode === "subagent") {
|
|
49
|
+
throw new Error(`Agent "${name}" is a subagent and cannot be set as the primary agent.`);
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
const success = await manager.apply(name, pi, ctx);
|
|
44
53
|
if (!success) {
|
|
45
54
|
throw new Error(`Failed to apply agent "${name}"`);
|