1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
# Handoff — perm/fix-user-agent-summon-permission
## Summary
Fixed a permissions bug: granting **only** the user-agent (top-level) permission
(`perm_user_agent`) without the subagent-summon permission (`perm_summon`) 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 at all.
The two permissions are now fully independent in **both** directions:
- **`perm_summon` only** → spawn ordinary subagents (unchanged; no `top_level`).
- **`perm_user_agent` only** → `summon` is registered in *user-agent-only* mode:
it spawns **only** top-level user agents (`top_level` forced on; the
`top_level`/`background` params are dropped; the catalog lists user agents only;
`retrieve` is NOT granted since user agents are fire-and-forget). This prevents
the inverse leak (a user-agent-only grant cannot spawn plain subagents).
- **both** → full behavior, byte-for-byte identical to before.
- **neither** → no `summon` tool (unchanged).
## Root cause
`packages/api/src/agent-manager.ts`, parent tool-build path: `if (permSummon) { … }`
built the entire `summon` (+`retrieve`) tool. `perm_user_agent` only flipped the
`userAgentEnabled` flag *inside* that block, so without `perm_summon` the tool was
never created.
## Files changed
- `packages/core/src/tools/summon.ts`
- `createSummonTool(...)` gained a trailing `subagentEnabled = true` param
(mirrors `perm_summon`) alongside `userAgentEnabled` (mirrors `perm_user_agent`).
Default `true` keeps every existing call site / mock behaving as before.
- New internal `userAgentOnly = userAgentEnabled && !subagentEnabled` mode:
description leads with user-agent spawning and omits subagent/parallel-work
prose; `top_level` and `background` params are omitted; `execute()` forces
`topLevel: true`; `agent` param lists only user-agent slugs.
- `buildAgentsCatalog(...)` gained a `subagentEnabled` param and a user-agent-only
branch ("User agents (spawned as independent top-level tabs):", no
`requires top_level=true` suffix since it is implied).
- `packages/api/src/agent-manager.ts`
- Parent path: `if (permSummon)` → `if (permSummon || permUserAgent)`.
- Passes `permSummon` as the new `subagentEnabled` arg to `createSummonTool`.
- `retrieve` is now only registered when `permSummon` is granted (bundled with
the subagent capability; user agents are fire-and-forget).
- Child/subagent path (`toolsOverride`, whitelist-driven) left untouched — out of
scope per agreement.
- `packages/core/tests/tools/summon.test.ts`
- New `user-agent-only mode` describe block (description content, catalog groups,
`agent` slug list, omitted `top_level`/`background` params, forced
`topLevel: true` on spawn).
- New regression block asserting the `subagentEnabled` default keeps legacy
subagent spawning unchanged.
- `packages/api/tests/agent-manager.test.ts`
- New `summon / user_agent permission split` describe block: summon+retrieve when
only `perm_summon`; **summon WITHOUT retrieve** when only `perm_user_agent`
(the bug-fix regression); both → summon+retrieve; neither → neither.
- `@dispatch/core` test mock gained `loadAgents`, `toAvailableSubagents`,
`toAvailableUserAgents`, `getAgentDirPaths`, `GLOBAL_AGENTS_DIR` (the summon
parent-branch was never exercised before, so these were missing).
## Public surface changed
- `createSummonTool(defaultWorkingDirectory, callbacks, availableSubagents?,
availableUserAgents?, agentDirs?, userAgentEnabled?, subagentEnabled?)` — added a
final optional `subagentEnabled` param (default `true`). Backward compatible:
all existing callers omit it and keep prior behavior.
- No DB/schema/migration changes; both settings (`perm_summon`, `perm_user_agent`)
already existed. No frontend changes (the "Spawn user agents" checkbox and
independent `perm_user_agent` persistence already existed).
## Verification (post-merge with `dev`, all green)
- `bun run test` → **605 passed** (37 files). +15 net new tests on this branch
(the +9 over the pre-merge 596 are from `dev`'s send_to_tab/read_tab prompt suite).
- `bun run check` (biome) → clean, "No fixes applied."
- `bun run --cwd packages/core typecheck` → clean.
- `bun run --cwd packages/api typecheck` → clean.
- `bun run --cwd packages/frontend typecheck` → 0 errors, 0 warnings.
## User test
Confirmed by the user: with only "Spawn user agents" granted (Summon agents OFF),
the agent receives the `summon` tool and can spawn a top-level user agent. ✅
## Published
Yes. Merged `dev` down into `perm/fix-user-agent-summon-permission` (resolved one
test-file conflict where this branch's new describe block and `dev`'s new
send_to_tab/read_tab system-prompt block landed at the same location — kept both),
re-ran all verification (green), and fast-forwarded:
`git push . HEAD:dev` → `e0b63c0..a243976 HEAD -> dev`.
Commits:
- `3ff2db6` fix(perm): decouple perm_user_agent from perm_summon for spawning user agents
- `a243976` Merge branch 'dev' into perm/fix-user-agent-summon-permission
## Assumptions / known gaps
- **Child/nested summon path unchanged** (per agreement #3): a spawned subagent gets
`summon` only if `"summon"` is in its tool whitelist, and `userAgentEnabled` there
still tracks the `perm_user_agent` DB setting. Decoupling nested user-agent
spawning was deliberately out of scope.
- **`hasSummon` system-prompt note** (agent-manager ~line 163) still says "You have
pre-configured subagent types… delegate to a subagent." In user-agent-only mode
this wording is slightly off, but the `summon` tool's own (mode-correct)
description carries the authoritative instructions. Left as-is to limit scope —
flag if you want it tailored.
|