beadboard/tests/components/shared/dispatch-button.test.tsx

15 lines
666 B
TypeScript
Raw Permalink Normal View History

[beadboard] Add Dispatch-to-Agent button and prompt template ## Context We want a one-click path from the right-panel task detail into the claude-agent-service runner, without the user copy-pasting the bead id into a CLI or another tab. The runner expects a self-contained prompt that restates the bead id, title, description, acceptance criteria, and the guard rails the agent must operate under (no push, no file edits, no terraform/kubectl/helm). The prompt template lives in `src/lib/` so it can be tested and reused from the server-side dispatch route. The right-panel button needs to: - Only appear when the bead is actionable (`open` or `in_progress`). - Disable itself while the claude-agent-service is already busy (the service has a global `asyncio.Lock` — parallel dispatches 409). - Disable itself when the bead lacks acceptance criteria. An agent that doesn't know what "done" looks like burns budget and closes nothing. - Surface the resulting `job_id` or any 409/error back to the user. The project has no toast library (no `sonner`, no `react-hot-toast`), so we render status inline under the button rather than pulling in a new dependency for this single surface. ## This change - `src/lib/dispatch-prompt.ts` exports `buildDispatchPrompt(bead)` which produces the exact prompt the agent runner expects. Bead id, priority (`P<n>`), issue type, description, and acceptance criteria are interpolated; `<job_id>` stays a literal placeholder because the agent only learns its own id at runtime (env var). - `src/components/shared/dispatch-button.tsx` is a focused client component with three responsibilities: 1. Poll `GET /api/agent-status` every 5 s while the panel is open (plus an initial fetch on mount), mirror `busy` into local state. 2. On click, `POST /api/agent-dispatch` with `{taskId}`; branch on 200 / 409 / other. 3. Render an inline status line under the button (`text-xs`, tone driven by `ok | info | error`) — no toast dep required. The poll interval self-clears on unmount so closing the panel stops network traffic. - `src/components/shared/thread-drawer.tsx` renders `<DispatchButton>` alongside the existing "Edit task" button in the summary section, wrapped in a `flex-wrap` so the two controls reflow on narrow panes. - Registers two new tests in `package.json`'s enumerated test script. ## What is NOT in this change - The `/api/agent-dispatch` and `/api/agent-status` routes themselves — those land in the next commit. The button calls them but the server side is intentionally a separate step so each commit can be reviewed in isolation. - No real toast system is introduced; inline status is sufficient. - No change to how task state transitions on dispatch. The agent itself is expected to run `bd update --claim` / `bd close` via the prompt's operating rules. ## Test Plan ### Automated ``` $ node --import tsx --test tests/lib/dispatch-prompt.test.ts \ tests/components/shared/dispatch-button.test.tsx # tests 7 pass 7 fail 0 ``` Covers: - Bead id appears in opening paragraph and in both `bd note` / `bd close` commands. - Priority rendered as `P<n>`, issue type echoed. - Description and acceptance criteria quoted verbatim when present. - `(no description)` / `(no acceptance criteria)` fallbacks when null. - Guard rails block present (no terraform/kubectl/helm, workspace bd path, `bd update … --status blocked` fallback). - DispatchButton module loads and exports both named and default. `npm run typecheck` shows only the pre-existing `OrchestratorChatMessage` type gap in `left-panel.tsx` that reproduces on untouched `main`. ### Manual Verification 1. `npm install` 2. `npm run dev` 3. Open `http://localhost:3000/?task=<some-open-bead-id>` 4. Expected: "Dispatch to Agent" button next to "Edit task" in the right-panel summary section. 5. Button disabled on beads with `status in {closed, blocked, deferred}` (they don't render the button at all). 6. Button disabled on beads missing acceptance criteria, with tooltip "Task is missing acceptance criteria — cannot dispatch.". 7. Click: UI flips to "Dispatching…"; once the next commit is merged, the agent-dispatch route will surface a `job_id` (today it returns 404 which renders as "Dispatch failed (HTTP 404)"). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:04:34 +00:00
import test from 'node:test';
import assert from 'node:assert/strict';
test('DispatchButton module exports component', async () => {
const mod = await import('../../../src/components/shared/dispatch-button');
assert.ok(mod.DispatchButton, 'DispatchButton should be exported');
assert.equal(typeof mod.DispatchButton, 'function', 'DispatchButton should be a function');
});
test('DispatchButton module exports default', async () => {
const mod = await import('../../../src/components/shared/dispatch-button');
assert.ok(mod.default, 'default export should exist');
assert.equal(mod.default, mod.DispatchButton, 'default should match named export');
});