Files
kandev/docs/plans/message-queue-send-now/plan.md
T
2026-08-06 13:45:55 +01:00

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:


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:

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.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.