Files
kandev/docs/plans/message-queue-send-now/plan.md
T
2026-08-24 11:09:03 +00:00

300 lines
15 KiB
Markdown

---
spec: docs/specs/ui/requirements/message-queue-send-now.md
created: 2026-08-05
status: 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](../../decisions/2026-08-05-queue-send-now-replaces-turn.md)
- [Keep Cancellation Progress Backend Owned](../../decisions/2026-08-03-backend-owned-cancellation-progress.md)
- [Explicit User Cancellation May Complete a Workflow Step](../../decisions/2026-08-02-explicit-user-cancel-completion.md)
- [Version Agent-Ready Events by Prompt Generation](../../decisions/0035-version-agent-ready-events-by-prompt-generation.md)
---
## Backend
### Exact queue dispatch claims
Files:
- `apps/backend/internal/orchestrator/messagequeue/types.go`
- `apps/backend/internal/orchestrator/messagequeue/repository.go`
- `apps/backend/internal/orchestrator/messagequeue/repository_memory.go`
- `apps/backend/internal/orchestrator/messagequeue/repository_sqlite.go`
- `apps/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.go`
- `apps/backend/internal/orchestrator/event_handlers_agent.go`
- `apps/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.go`
- `apps/backend/internal/orchestrator/handlers/queue_handlers.go`
- `apps/backend/internal/backendapp/gateway.go` only 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.ts`
- `apps/web/lib/api/domains/queue-api.test.ts`
- `apps/web/hooks/domains/session/use-queue.ts`
- `apps/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.tsx`
- `apps/web/components/task/chat/queued-ghost-list.test.tsx`
- `apps/web/components/task/chat/queued-ghost-message.tsx`
- `apps/web/components/task/chat/queued-ghost-message.test.tsx`
- `apps/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.ts` supplies 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-region` remains 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-chrome` test 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.go` coverage for memory, SQLite,
and env-gated PostgreSQL plus `service_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.go` and 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.md`
- `docs/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 because `KANDEV_TEST_POSTGRES_DSN` is 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 typecheck` passed; full web lint passed with `pnpm --filter @kandev/web run lint`; `i18n:pseudo`, `i18n:check`, and `i18n:ratchet` all 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 --check` passed.
- E2E discovery found 12 desktop queue tests and 2 mobile queue-management tests. After rebuilding and syncing the embedded web assets with `make build-web` and `make 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 matching `mobile-chrome` command for `mobile-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:
- [x] [Task 01: Add exact send-now queue claims](task-01-queue-send-now-claims.md)
Wave 2:
- [x] [Task 02: Orchestrate replacement-turn dispatch](task-02-replacement-turn-dispatch.md)
Wave 3:
- [x] [Task 03: Add Send Now queue controls](task-03-send-now-queue-controls.md)
Wave 4:
- [x] [Task 04: Prove Send Now end to end](task-04-send-now-e2e.md)
- [x] [Task 05: Document Send Now behavior](task-05-document-send-now.md)
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.go` and 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.