summaryrefslogtreecommitdiffhomepage
path: root/HANDOFF.md
diff options
context:
space:
mode:
Diffstat (limited to 'HANDOFF.md')
-rw-r--r--HANDOFF.md143
1 files changed, 87 insertions, 56 deletions
diff --git a/HANDOFF.md b/HANDOFF.md
index dbabee1..cfaf89d 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -1,68 +1,99 @@
-# Handoff — td/todo-fix: declarative todo/task system
+# Handoff — perm/fix-user-agent-summon-permission
## Summary
-Replaced Dispatch's imperative, id-based `todo` tool (actions `add`/`update`/`list`/`get`/`remove`)
-with opencode's **declarative whole-list** design, and fixed the panel blanking on reload. The tool
-name (`todo`), the `task-list-update` event, the per-tab `TaskList` store, and the sidebar **Tasks**
-panel are all preserved — only the interface, status model, and UI rendering changed.
+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.
-## What changed (and why it's better)
-- **Declarative whole-list write** (from opencode's `todowrite`): the model sends the *entire*
- desired list in one `todos` param each call; the store replaces its list. No model-visible ids,
- no delta reasoning, no "task not found" spirals, no multi-call churn — the failure modes that made
- the old CRUD tool confuse weaker models.
-- **Status lifecycle:** `pending | in_progress | completed | cancelled` (was `pending | in_progress |
- done | blocked`; `blocked` was dead/unrendered state).
-- **No `priority`** (deliberately dropped per product decision; opencode has it, we don't).
-- **Reload reliability:** todos used to blank on page reload (broadcast only on change, absent from
- the reconnect snapshot). Now `TabStatusSnapshot` carries per-tab `tasks`, so the panel rehydrates
- from the backend on reload/reconnect. Still **in-memory per-tab** (no DB; does not survive a server
- restart).
+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/types/index.ts` — `TaskStatus` union; `TaskItem = { id, content, status }`
- (`id` internal/positional, never shown to the model); `TabStatusSnapshot.tasks?`.
-- `packages/core/src/tools/task-list.ts` — rewrote `TaskList` (declarative `setTasks`/`getTasks`/
- `onChange`); `createTaskListTool` with a single `todos` param that echoes the stored list without
- ids; new exported `TODO_DESCRIPTION` (adapted from opencode `todowrite.txt`).
-- `packages/core/src/index.ts` — export `TODO_DESCRIPTION`.
-- `packages/api/src/agent-manager.ts` — `TODO_GUIDANCE` → `TASK_MANAGEMENT_GUIDANCE` (system-prompt
- section adapted from opencode `anthropic.txt`); updated `TOOL_DESCRIPTIONS.todo`; `getAllStatuses()`
- now includes each tab's `tasks` (all tabs, omitted when empty).
-- `packages/frontend/src/lib/types.ts` — mirror `TaskItem` + `TabStatusSnapshot.tasks`.
-- `packages/frontend/src/lib/tabs.svelte.ts` — hydrate `tasks` from the snapshot in both restore
- paths (initial `GET /status` map + `statuses` WS handler); updated debug-dump label.
-- `packages/frontend/src/lib/components/TaskListPanel.svelte` — render `content`; all four statuses
- (completed→checked+strikethrough, in_progress→indeterminate+bold, cancelled→dim+strikethrough,
- pending→empty); `completed/active` progress counter. Sidebar panel only — nothing relocated.
-- `packages/core/tests/tools/task-list.test.ts` — new (15 tests).
-- `packages/api/tests/agent-manager.test.ts`, `packages/api/tests/routes.test.ts` — updated
- `TaskList` mocks to the declarative shape; added `getAllStatuses` task-snapshot coverage.
-- `notes/todo-tool-redesign-plan.md` — appended an "As-built" section.
+- `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
-- **Tool `todo`**: parameters changed from `{ action, title, description, task_id, status }` to a
- single `{ todos: Array<{ content, status }> }`. Statuses `pending|in_progress|completed|cancelled`.
-- **`@dispatch/core` exports**: added `TODO_DESCRIPTION`. `TaskItem` shape changed (`title`+
- `description` → `content`; status union changed). `TaskList` methods changed (`addTask`/`updateTask`/
- `removeTask`/`getTask` removed; `setTasks` added).
-- **`TabStatusSnapshot`** (wire format, core + frontend mirror) gained optional `tasks`.
-- Tool name, allowlist/loader/summon/permission wiring, agent TOMLs: **unchanged**.
+- `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.
-## Verification status
-- `bun run check` (biome): clean.
-- `bun run test`: **585 passing** (37 files).
-- `tsc --noEmit` (core, api) + `svelte-check` (frontend): 0 errors.
-- Verified post-merge of `dev`.
+## 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 (no conflicts), re-verified all-green, fast-forwarded
-`dev` → `9d6b7a9`. User confirmed the task system works before merge.
+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
-- No DB persistence: todos are in-memory per-tab and do not survive a server restart (matches scope;
- opencode persists to SQLite — intentionally not ported).
-- No `priority` field (dropped per decision).
-- No new UI surfaces — the existing sidebar Tasks panel only.
-- An unrelated untracked `bookmark-manager/` directory exists in the worktree root; it is not part of
- this feature and was left untouched (never staged/committed).
+- **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.