diff options
| author | Adam Malczewski <[email protected]> | 2026-06-02 15:54:39 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-02 15:54:39 +0900 |
| commit | 3ff2db698c2633023934d8477a9e995f78fa011e (patch) | |
| tree | 1797ca96cb8fbc8df23a2667037e3a6ddeec2f35 /packages/api/src | |
| parent | c0c08720cceb75b5e635e71190ae1f956f535133 (diff) | |
| download | dispatch-3ff2db698c2633023934d8477a9e995f78fa011e.tar.gz dispatch-3ff2db698c2633023934d8477a9e995f78fa011e.zip | |
fix(perm): decouple perm_user_agent from perm_summon for spawning user agents
Granting only the user-agent (top-level) permission without the
subagent-summon permission left the agent unable to summon user agents:
the whole summon tool was gated behind perm_summon, so perm_user_agent
alone produced no summon tool.
Register summon when EITHER perm_summon OR perm_user_agent is granted.
createSummonTool now takes an independent subagentEnabled flag (mirrors
perm_summon) alongside userAgentEnabled (mirrors perm_user_agent):
- subagent-only -> ordinary subagents, no top_level
- user-agent-only -> spawns ONLY top-level user agents (top_level
forced, background/top_level params dropped, user-agent catalog only)
- both -> unchanged full behavior
retrieve stays bundled with perm_summon (user agents are fire-and-forget).
Adds core summon tests (user-agent-only mode + legacy-default regression)
and an agent-manager summon/user_agent permission-split suite.
Diffstat (limited to 'packages/api/src')
| -rw-r--r-- | packages/api/src/agent-manager.ts | 36 |
1 files changed, 24 insertions, 12 deletions
diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts index 85dd160..9499ce5 100644 --- a/packages/api/src/agent-manager.ts +++ b/packages/api/src/agent-manager.ts @@ -575,7 +575,13 @@ export class AgentManager { }); } toolEntries.push({ name: "todo", tool: createTaskListTool(tabAgent.taskList) }); - if (permSummon) { + // The `summon` tool is registered when EITHER the subagent + // permission (`perm_summon`) OR the user-agent permission + // (`perm_user_agent`) is granted — the two are independent. + // `perm_summon` enables ordinary subagent spawning; granting + // only `perm_user_agent` exposes summon in user-agent-only mode + // (spawns top-level user agents exclusively). + if (permSummon || permUserAgent) { // Capture parent's allowed tool names for child permission enforcement const parentAllowedTools = new Set(toolEntries.map((e) => e.name)); const allAgentDefs = loadAgents(workingDirectory); @@ -609,19 +615,25 @@ export class AgentManager { availableUserAgents, agentDirPaths, permUserAgent, + permSummon, ), }); - toolEntries.push({ - name: "retrieve", - tool: createRetrieveTool({ - getResult: (id) => - tabAgent.shellStore.has(id) - ? tabAgent.shellStore.getResult(id) - : tabAgent.transcriptStore.has(id) - ? tabAgent.transcriptStore.getResult(id) - : this.getChildResult(id), - }), - }); + // `retrieve` collects subagent results. User agents are + // fire-and-forget, so it is bundled with the subagent + // permission only — a user-agent-only grant doesn't get it. + if (permSummon) { + toolEntries.push({ + name: "retrieve", + tool: createRetrieveTool({ + getResult: (id) => + tabAgent.shellStore.has(id) + ? tabAgent.shellStore.getResult(id) + : tabAgent.transcriptStore.has(id) + ? tabAgent.transcriptStore.getResult(id) + : this.getChildResult(id), + }), + }); + } } if (permSendToTab || permReadTab) { const tabCommAllowed = new Set<string>(); |
