15 KiB
spec, created, status
| spec | created | status |
|---|---|---|
| docs/specs/ui/message-queue-send-now.md | 2026-08-05 | implemented |
Implementation Plan: Send Queued Messages Now
Overview
Add one explicit WebSocket operation that asks the orchestrator to replace the active turn with either one queued entry or the click-time snapshot of the whole visible queue. Build the exact-entry/batch claim and restoration contract first, then compose it with the existing cancellation coordinator and expose it through the frontend queue hook and inline panel. Finish with desktop/mobile Playwright coverage and the public queue-control guides.
Decision inputs:
- Queue Send Now Replaces the Active Turn
- Keep Cancellation Progress Backend Owned
- Explicit User Cancellation May Complete a Workflow Step
- Version Agent-Ready Events by Prompt Generation
Backend
Exact queue dispatch claims
Files:
apps/backend/internal/orchestrator/messagequeue/types.goapps/backend/internal/orchestrator/messagequeue/repository.goapps/backend/internal/orchestrator/messagequeue/repository_memory.goapps/backend/internal/orchestrator/messagequeue/repository_sqlite.goapps/backend/internal/orchestrator/messagequeue/service.go- new focused helpers/tests under
apps/backend/internal/orchestrator/messagequeue/
Add a SendNowClaim value that retains the ordered original entries plus the
synthetic dispatch envelope. Add repository/service operations to atomically
claim an exact ordered list of entry IDs, restore the claim, and acknowledge
its durable lifecycle rows.
The claim transaction must fail without partial mutation when any requested row is missing, belongs to another session, or is already reserved. Ordinary rows are removed; durable lifecycle rows receive the existing in-flight reservation and remain until acknowledgement. Restoration reinserts ordinary rows at their original positions and clears durable reservations.
Build the bulk envelope from the claimed rows: FIFO content joined by \n\n
between non-empty bodies, FIFO attachments, canonical reference union, oldest
entry model/plan mode/attribution, and source identity/provenance metadata.
Validate attachment count/bytes and reference count before cancellation using
the same constants as ordinary message admission.
Non-completing cancel-and-dispatch
Files:
- new
apps/backend/internal/orchestrator/queue_send_now.go apps/backend/internal/orchestrator/task_operations.goapps/backend/internal/orchestrator/event_handlers_agent.goapps/backend/internal/orchestrator/service.go
Add Service.SendQueuedNow(ctx, sessionID, scope, entryID) and a distinct
cancellationKindQueueSendNow. Authorize the session first, snapshot the active
turn and exact queue selection, and validate the aggregate before cancellation.
Reuse cancelInFlightGuard, the backend cancellation projection, captured turn
identity, and silent cancellation reconciliation. Do not call the explicit
CancelAgent path: Send Now must not write the ordinary cancellation message,
move the task to review, or evaluate cancelled-turn completion. Reject an
existing explicit cancellation or Send Now owner. If the observed turn changes,
leave the successor and queue untouched; if the session is already promptable,
claim and dispatch without cancellation.
Refactor the queued execution handoff only as far as necessary to accept a
single- or multi-entry SendNowClaim. A successful prompt acknowledges every
durable source; retryable admission/execution failures restore every original
source and publish authoritative queue status. Never fall back to the FIFO head
when the selected entry or bulk snapshot cannot be claimed.
WebSocket action
Files:
apps/backend/pkg/websocket/actions.goapps/backend/internal/orchestrator/handlers/queue_handlers.goapps/backend/internal/backendapp/gateway.goonly if constructor wiring must name a broader dispatcher interface
Register message.queue.send_now with request fields session_id, scope, and
conditional entry_id. Extend the existing drainer dependency into a queue
dispatcher interface implemented by orchestrator.Service.
Authorize before all queue reads/cancellation, validate the scope combination,
call SendQueuedNow, map the spec's stable error codes, publish status, and
return {session_id, dispatched: true, sent_count}. Add the action to the
gateway authorization pinning tests so session_id receives the existing
backstop.
Frontend
Queue API and hook
Files:
apps/web/lib/api/domains/queue-api.tsapps/web/lib/api/domains/queue-api.test.tsapps/web/hooks/domains/session/use-queue.tsapps/web/hooks/domains/session/use-queue.test.ts
Add sendQueuedNow plus typed error mapping for the stable send-now codes. Add
sendEntryNow(entryId) and sendAllNow() to useQueue; both set queue loading,
invoke the action, and refetch authoritative status on success or failure. A
missing/raced entry is a reconciled result plus focused feedback, not a stale
optimistic removal.
Queue controls
Files:
apps/web/components/task/chat/queued-ghost-list.tsxapps/web/components/task/chat/queued-ghost-list.test.tsxapps/web/components/task/chat/queued-ghost-message.tsxapps/web/components/task/chat/queued-ghost-message.test.tsxapps/web/src/locales/en/chat.json- generated pseudo-locale output if the catalog workflow changes it
Add a per-row Send Now button to the existing RowActions hover/focus
group and thread the selected entry ID through the queue hook. Add header
Send Now immediately before Clear all and bind it to sendAllNow.
Disable both controls while the queue request or authoritative session
cancellation_pending state is true, use the existing spinner/toast patterns,
and localize all labels and failures.
Keep Run next unchanged for promptable FIFO-head dispatch. When present, the header order is Run next, Send Now, Clear all, collapse. Let the action group wrap on narrow viewports rather than shrink or overflow.
Responsive and Mobile Contract
- Desktop outcome and mobile entry point: desktop opens the existing queue chip and uses hover/focus row actions or the header; mobile opens the same chip and sees both Send Now controls without hover.
- Nearest shipped exemplar:
apps/web/e2e/tests/chat/mobile-message-queue-management.spec.tssupplies the inline panel, single scroll owner, visible touch controls, and 44px geometry. - Hierarchy and primary action: queue content remains primary; per-row Send Now sits with row actions, while bulk Send Now is the first bulk mutation immediately before Clear all.
- Presentation choice: retain the inline queue panel because the action is a frequent, short mutation of already visible content; no drawer or navigation layer is introduced.
- Geometry and scrolling:
queue-scroll-regionremains the only internal scroll owner. The header action group may wrap, controls are at least 44px on coarse pointers, the composer remains visible, and document horizontal overflow stays zero. - Shared logic: API calls, loading/cancellation state, selection, aggregation, and errors are shared. Only the existing pointer-responsive action visibility differs.
- Mobile proof: a
mobile-chrometest taps row/header Send Now, asserts the replacement prompt outcome, verifies touch geometry, and checks horizontal containment.
Tests
- What: exact-entry and exact-snapshot batch claims are atomic, preserve FIFO
source order, reserve durable rows, restore originals, and acknowledge every
durable source.
Files: new
repository_*_send_now_test.gocoverage for memory, SQLite, and env-gated PostgreSQL plusservice_test.go. How: table-driven repository tests for selected/all claims, missing or changed snapshots, mixed ordinary/durable rows, restoration, and no cross-session mutation. - What: bulk aggregation joins content correctly, preserves FIFO attachments, deduplicates references, uses the oldest envelope, and rejects attachment/reference overflow before mutation. Files: new messagequeue send-now helper tests and orchestrator tests where task-model attachment limits are applied. How: deterministic unit tests with attachment-only messages, repeated references, mixed provenance, and over-limit inputs.
- What: busy Send Now cancels the captured turn and dispatches the exact
selection without explicit-cancel workflow side effects; promptable Send Now
skips cancellation.
File:
apps/backend/internal/orchestrator/queue_send_now_test.go. How: channel-synchronized lifecycle mocks assert one cancel, no review or cancelled-turn completion, exact prompt content, and one successor turn. - What: successor-turn, explicit-cancel, duplicate-click, queue-change, and
prompt-failure races fail closed or restore the original queue.
Files:
queue_send_now_test.goand focused existing cancellation tests if shared coordinator behavior changes. How: captured-turn replacement and cancellation-owner race tests without sleeps; assert no substitute FIFO dispatch. - What: the WS action validates scope, authorizes before dependencies, maps
stable errors, and publishes the post-action status.
File:
apps/backend/internal/orchestrator/handlers/queue_handlers_send_now_test.go. - What: frontend API/hook calls and both controls use the correct scope, refetch after every outcome, show localized failures, and disable during loading/cancellation. Files: existing queue API, hook, list, and row component test files.
E2E Tests
- Scenario: while a desktop task agent runs a cancellable slow turn, queue
three distinct messages and activate row Send Now on the second. Verify the
replacement transcript contains the second prompt, the cancelled turn does
not advance the workflow, and the first/third rows remain ordered.
File:
apps/web/e2e/tests/chat/message-queue.spec.ts. - Scenario: while a desktop task agent is busy, queue several messages and
activate header Send Now. Verify one replacement prompt contains all bodies
in FIFO order and the queue disappears.
File:
apps/web/e2e/tests/chat/message-queue.spec.ts. - Scenario: on Pixel 5, open the busy queue panel and tap Send Now. Verify
the row and header controls are 44px, require no hover, remain inside the
viewport with no document horizontal overflow, and complete a replacement
turn.
File:
apps/web/e2e/tests/chat/mobile-message-queue-management.spec.ts.
Public Documentation
Update the how-to/explanation sections in:
docs/public/coordination.mddocs/public/sessions-and-review.md
Explain the difference between Run next (promptable FIFO head), Send Now (interrupt and replace without workflow completion), Clear all, and the normal Cancel control. No new page or navigation entry is required.
Verification Results
- Backend queue claims and envelope aggregation:
cd apps/backend && go test -race ./internal/orchestrator/messagequeue -count=1 -v— 116 tests passed. The memory and SQLite claim suites cover exact atomic selection, click-time snapshot changes, FIFO restoration, durable reservation, acknowledgement, aggregate attachments, and reference limits. The env-gated PostgreSQL cases were included but skipped becauseKANDEV_TEST_POSTGRES_DSNis unset. - Backend replacement orchestration:
cd apps/backend && go test -race ./internal/orchestrator -run 'SendNow|ExplicitCancellationDoesNotJoin' -count=1 -v— 6 tests passed;cd apps/backend && go test -race ./internal/orchestrator/handlers -run 'SendNow|QueueHandler' -count=1 -v— 24 tests passed; existing cancellation/peer-interrupt regressions — 56 tests passed. The retry regression also proves restored ordinary and durable sources retain the recorded-user-message marker. - Backend compile sweep:
cd apps/backend && go test ./... -run '^$'— completed with no test bodies selected, compiling all packages successfully. - Backend lint:
make -C apps/backend lint— completed with 0 issues after extracting the repository, handler, and orchestration validation helpers. - Frontend queue API, hook, and component suite: 4 files, 97 tests passed.
cd apps/web && pnpm run typecheckpassed; full web lint passed withpnpm --filter @kandev/web run lint;i18n:pseudo,i18n:check, andi18n:ratchetall passed. - Public documentation:
node --test scripts/validate-public-docs.test.mjs— 58 tests passed;node scripts/validate-public-docs.mjs— 41 published docs pages validated;git diff --checkpassed. - E2E discovery found 12 desktop queue tests and 2 mobile queue-management tests. After rebuilding and syncing the embedded web assets with
make build-webandmake sync-embedded-web && make build-backend,cd apps/web && pnpm e2e --project=chromium e2e/tests/chat/message-queue.spec.ts --grep 'Send Now' --retries=0— 2 passed in 33.4s; the matchingmobile-chromecommand formobile-message-queue-management.spec.ts— 1 passed in 13.1s. The desktop row scenario verifies workflow-step preservation and neighboring FIFO entries; the mobile scenario verifies touch geometry and zero horizontal overflow.
Implementation Waves and Parallel Candidates
The default is sequential execution in the primary conversation. The files are ordered by dependency; these waves do not authorize subagents.
Wave 1:
Wave 2:
Wave 3:
Wave 4:
Tasks 04 and 05 own disjoint E2E and public-doc files, but the final plan/status and verification-results update is serialized after both tasks complete so the durable implementation record cannot be overwritten by parallel edits.
Risks and Boundaries
task_operations.goand cancellation coordination are high-contention concurrency surfaces. Keep new orchestration in a focused file and preserve captured-turn and prompt-generation checks.- Explicit Cancel can complete/advance a workflow step; Send Now must never reuse or join that semantic path.
- Bulk delivery can mix provenance and durable lifecycle rows. The batch claim must preserve originals for retry and acknowledge every durable source only after prompt acceptance.
- Aggregate attachment/reference limits must be checked before cancellation so a validation error cannot stop useful active work.
- New queue entries accepted after the click-time snapshot remain pending and must not leak into the replacement prompt.
- No schema migration, feature flag, queue reordering, arbitrary subset selection, model picker, or change to ordinary FIFO drain is planned.