feat: show task attachments during workspace preparation (#3594)

This commit is contained in:
Carlos Florêncio
2026-09-12 16:30:15 +01:00
committed by GitHub
parent 61e86b40e7
commit f5c73db63c
21 changed files with 1203 additions and 40 deletions
@@ -0,0 +1,136 @@
---
created: 2026-09-10
status: done
requirements:
- REQ-TASKS-PROMPT-ATTACHMENTS-001
system_design:
- ../../specs/tasks/system-design/prompt-attachments.md
legacy_specs: []
---
# Implementation Plan: Preparation attachment previews
## Overview
Show submitted screenshots and file labels while a new task prepares its workspace.
One sequential work order carries a session preview from task creation to chat.
The task system owns this change because it owns submitted attachments and session identity.
## Evidence and root cause
The reported task `7a8da962-d5ba-4b6c-b9ee-92691f21e3b4` has two PNG descriptors
in its first stored user message. The supplied screenshot shows its text-only
row above worktree preparation. A read-only source trace confirms:
- `prepareStartAgentSession` creates the session without passing attachment display data.
- `postLaunchCreated` / `postLaunchStart` call `recordInitialMessage` after launch.
- `buildTaskDescriptionMessage` creates a synthetic row without attachment metadata.
- `chat-message.tsx` renders images and file labels from that missing metadata.
Reproduce with a new task containing text and two images while workspace setup
is held pending. The fallback has text, but no attachments. No live instance
was mutated, and no runtime reproduction or permanent test has been run yet.
## Scope
In scope: file-backed task-create attachments during session preparation,
prepare-only creation, reload, failure, stored-message replacement, and phone parity.
Out of scope: upload limits, new delivery modes, queue changes, additional-session
prompt redesign, legacy inline preview backfill, and changing when the backend
records a delivered initial message. A queued task without a session is outside
this workspace-preparation view.
## Technical approach
Follow the existing [attachment design](../../specs/tasks/system-design/prompt-attachments.md#initial-preview-during-workspace-preparation).
Extend the internal preparation options in `session_launch.go` and
`task_operations.go`; forward display data from both task-create prepare branches
in `task_http_handlers.go`. Persist bounded `initial_prompt_preview` metadata
after attachment admission and before session publication/workspace work.
Read the resolved session through `useSessionState` / `useSessionData`, then
normalize its preview in `useProcessedMessages`. Render with the existing
attachment components. Preserve all current history-exhaustion guards and
stored-message precedence. Keep snapshot data out of agent dispatch and turn accounting.
## ASCII UI preview
UI-01: Task Chat during workspace preparation, shared desktop/phone composition.
```text
Before After
+-------------------------+ +-------------------------+
| Submitted text | | [image 1] [image 2] |
+-------------------------+ | [notes.txt] |
Preparing environment... | Submitted text |
Create worktree +-------------------------+
Preparing environment...
Create worktree
```
The user row stays above progress. Phone attachments wrap within the existing
single-column Chat view; Chat owns vertical scrolling. Image taps open the
existing viewer. Files retain their existing compact labels. Grouping and
order are required; spacing is illustrative. Reuse localized controls.
Attachment-only: omit the text area and keep the attachment row. No attachments:
keep the current text row. Failure: keep the preview above the existing error.
After launch: show only the stored user message with its attachments.
These states cover AC-TASKS-PROMPT-ATTACHMENTS-001.8 through .11.
## Tests
- `.8`, `.9`: new `task_http_handlers_initial_preview_test.go` and
`initial_prompt_preview_test.go` verify the snapshot exists before background
preparation, survives a fresh session read, and cannot start an agent itself.
- `.8`, `.10`: `use-processed-messages-fallback.test.ts` covers descriptor-only
images/files, empty text, late metadata, stored-row replacement, unknown
history, older history, malformed entries, and session switches.
- `.11`: rendered checks use existing image/file controls and verify unavailable
content does not remove other content.
- Preserve claim-denial and initial-message dedup tests; a preview is not a turn.
## E2E tests
Add `e2e/tests/task/preparation-attachments.spec.ts` (`chromium`) and
`e2e/tests/task/mobile-preparation-attachments.spec.ts` (`mobile-chrome`).
Use shared fixtures and a controlled preparation barrier, not a fixed sleep.
On a disposable repository, a setup script can wait for a test-owned release
file; always release the barrier and remove owned fixture data in `finally`.
Verify the selected executor actually runs the barrier before asserting UI.
Create a task with two uploaded images and a resource file through the UI.
While preparation is pending, open an image, close it, inspect the file label,
reload, and verify the same attachments. Release preparation and assert exactly
one stored initial message. Add preparation-failure and inaccessible-content
cases. Phone checks use taps and assert zero document horizontal overflow.
These scenarios cover `.8` through `.11`; capture the preparation view for comparison to UI-01.
## Work orders
- [x] [Task 01: Show initial attachment previews](task-01-initial-attachment-previews.md)
## Verification results
- `python3 scripts/lint-spec-files.py --all`: passed.
- `python3 scripts/lint-spec-files.test.py`: passed, 36 tests.
- `git diff --check`: passed.
- Worktree status confirms the requirement/design edits and new plan/work order.
- Implementation, runtime reproduction, and browser checks: not run in this design turn.
Implementation verification: targeted backend suites passed; hook tests passed
(12); TypeScript, native builds, and the E2E web build passed. Managed desktop
tests passed (2), and managed phone tests passed (1), with retries disabled.
Both captured views show the images and file label during preparation. See the
work order for commands, fixture corrections, and detailed results.
## Risks
- Preview data could accidentally dispatch twice if reused as launch input.
- Session reuse or stale hydration could show another session's attachments.
- Relaxing fallback history guards could invent an initial message in old history.
- Metadata persistence must preserve unrelated keys and precede background work.
The public task attachment guide now explains preparation previews and distinguishes
them from successful agent delivery. See the work order for implementation results.
@@ -0,0 +1,177 @@
---
id: "01-initial-attachment-previews"
title: "Show initial attachment previews"
status: done
wave: 1
depends_on: []
plan: "plan.md"
requirements:
- REQ-TASKS-PROMPT-ATTACHMENTS-001
acceptance_criteria:
- AC-TASKS-PROMPT-ATTACHMENTS-001.8
- AC-TASKS-PROMPT-ATTACHMENTS-001.9
- AC-TASKS-PROMPT-ATTACHMENTS-001.10
- AC-TASKS-PROMPT-ATTACHMENTS-001.11
system_design:
- ../../specs/tasks/system-design/prompt-attachments.md
---
# Task 01: Show initial attachment previews
## Summary
Carry a validated session preview from task creation to the preparation transcript.
Reuse the existing attachment controls and replace the preview when stored history arrives.
## In scope
- Internal preparation option and persisted session preview before publication.
- Session metadata normalization, synthetic message composition, and attachment types.
- Focused backend, hook, desktop, and phone regression coverage from the plan.
## Out of scope
Agent delivery changes, new storage tables or APIs, queues, and legacy inline preview backfill.
## Acceptance
1. Backend preparation publishes the submitted preview before workspace work,
preserves it on fresh reads and failure, and preserves claim/session isolation.
2. Chat displays it with existing controls, handles empty text and malformed
metadata, and replaces it without duplicates under all existing history guards.
3. Desktop and phone E2E prove opening attachments during preparation, reload,
success replacement, and failure behavior without overflowing the phone viewport.
## ASCII UI preview
UI-01: Task Chat during workspace preparation. See the [full state preview](plan.md#ascii-ui-preview).
```text
+--------------------------+
| [image 1] [image 2] |
| [notes.txt] |
| Submitted text |
+--------------------------+
Preparing environment...
Create worktree
```
Shared phone/desktop ordering; phone content wraps in the existing Chat scroll
region. Image tap opens the existing viewer. Failure retains this row above
the existing error; stored history replaces it. Covers `.8` through `.11`.
## Verification
Run from the repository root. Bootstrap dependencies once if this worktree lacks them.
Write and run the backend/hook regression before production edits. The backend
test must fail because preparation has no snapshot; the hook test must fail
because the initial row has no attachment metadata, not because a selector is missing.
```bash
(cd apps && pnpm install --frozen-lockfile)
(cd apps/backend && go test ./internal/task/handlers -run 'Test.*InitialPreview' -count=1)
(cd apps/backend && go test ./internal/task/models -run 'TestInitialPromptPreview' -count=1)
(cd apps/backend && go test ./internal/orchestrator -run 'Test.*(InitialPromptPreview|LaunchSession|InitialPrompt)' -count=1)
(cd apps/web && pnpm exec vitest run hooks/use-processed-messages-fallback.test.ts)
(cd apps/web && pnpm run typecheck)
(cd apps/web && pnpm e2e:run --project chromium tests/task/preparation-attachments.spec.ts)
(cd apps/web && pnpm e2e:run --project mobile-chrome tests/task/mobile-preparation-attachments.spec.ts)
python3 scripts/lint-spec-files.py --all
git diff --check
```
Run changed-file ESLint and Go formatting checks. Include any additional test
files changed by implementation in this command list before marking complete.
Managed E2E builds current assets; do not reuse a stale build. Check discovered
test counts and compare the captured phone view with UI-01.
## Files likely touched
- `apps/backend/internal/task/handlers/task_http_handlers.go`
- `apps/backend/internal/task/handlers/task_http_handlers_initial_preview_test.go` (new)
- `apps/backend/internal/orchestrator/session_launch.go`
- `apps/backend/internal/orchestrator/task_operations.go`
- `apps/backend/internal/orchestrator/initial_prompt_preview_test.go` (new)
- `apps/backend/internal/task/models/initial_prompt_preview.go` (new typed metadata helper if needed)
- `apps/web/components/task/chat/use-chat-panel-state.ts`
- `apps/web/hooks/use-processed-messages.ts`
- `apps/web/hooks/use-processed-messages-fallback.test.ts`
- `apps/web/components/task/chat/messages/chat-message.tsx`
- `apps/web/e2e/tests/task/preparation-attachments.spec.ts` (new)
- `apps/web/e2e/tests/task/mobile-preparation-attachments.spec.ts` (new)
- A shared preparation fixture helper under `apps/web/e2e/helpers/`.
## Dependencies
None. Use the existing attachment claim service, session metadata persistence,
history initialization state, and attachment renderer.
## Risks
Do not pass display snapshots as a second launch prompt. Do not attach task-wide
files to sibling sessions or change fallback history eligibility. Preserve
passthrough prepare upgrades and unrelated session metadata.
## Parallelism
`sequential`
## Inputs
- [Requirements](../../specs/tasks/requirements/prompt-attachments.md), `.8` through `.11`.
- [Design](../../specs/tasks/system-design/prompt-attachments.md#initial-preview-during-workspace-preparation).
- Existing `use-processed-messages-fallback.test.ts` and `mixed-attachment-layout.ts` patterns.
- Root and scoped AGENTS.md, `/tdd`, `/e2e`, and `/mobile-parity`.
## Results
Implementation adds a display-only session snapshot before preparation publication.
The frontend validates descriptors and reuses the existing image controls and file
labels. History guards, session isolation, and stored-message precedence remain intact.
Synthetic rows cannot be favorited. Public task documentation explains the preview.
- Behavioral RED: the hook omitted attachment metadata; the task-create handler
omitted the preview from its preparation request. Both regressions passed after wiring.
- Hook regression suite: 12 tests passed, including late hydration, attachment-only
prompts, malformed descriptors, history guards, and stored-message replacement.
- TypeScript checking passed. Changed-file ESLint reported no errors.
- Model descriptor test passed. Public docs tests passed (62 tests); validation
passed for 46 pages. Specification lint and whitespace checks passed.
- Desktop and phone E2E scenarios were added. The initial managed browser run
stopped during backend build because the shared disk filled; no browser assertion ran.
- Backend verification retries encountered disappearing shared cache and temporary
build files. A retry uses a private cache and build directory.
Final targeted backend checks passed for models, handlers, and orchestrator.
The orchestrator command includes existing initial-prompt and launch-session tests.
The preview-write failure test first demonstrated a stranded CREATED session, then
passed with the existing launch-failure handler. The sibling-session fixture now
sets its workspace ready before creating the sibling, as the product requires.
The final hook run passed all 12 tests with `--silent`, after a console-forwarding
teardown failure on a prior run. TypeScript and changed-hook lint passed again.
Final browser verification passed with fresh host-only artifacts. Build commands:
```bash
(cd apps/backend && make build-agentctl build-kandev build-mock-agent e2e-plugin-package GOFLAGS=-p=1)
(cd apps/web && pnpm build:e2e)
(cd apps/web && pnpm e2e:run --host --no-build --project chromium tests/task/preparation-attachments.spec.ts --retries=0)
(cd apps/web && pnpm e2e:run --host --no-build --project mobile-chrome tests/task/mobile-preparation-attachments.spec.ts --retries=0)
```
- Desktop: 2 passed. Phone: 1 passed. Retries were disabled; cleanup also passed.
- Screenshots were inspected against UI-01. Both layouts retain the image controls,
file label, and prompt. The phone view has no document horizontal overflow.
- Browser coverage uses a controlled setup barrier and tests image opening,
reload, unavailable image content, and replacement by one saved user message.
The second desktop scenario checks non-fatal setup warnings. Terminal failure
persistence is covered at the backend boundary.
- Initial browser failures were fixture issues: cleanup needed explicit consent
to discard attachment-staging files; fetch responses expose `ok` as a property;
setup-script failures produce `completed_with_warnings`, not a terminal error.
- The hook and handler provided behavioral RED evidence before implementation.
Browser checks were added after the minimal fix to verify the rendered data path.
- Final changed-file ESLint: no errors; two test-file size/duplication warnings.
Go formatting and whitespace checks passed.
No production agent-delivery behavior or remote executor build was changed.
+2
View File
@@ -302,6 +302,8 @@ Folders are live host paths and are available only to **Local/Local PC** and **W
The task prompt supports image, audio, and resource attachments. Kandev accepts at most 10 files per submission, with a 100 MiB raw limit per file and a 100 MiB raw aggregate limit. Files are uploaded over authenticated HTTP before the task or message is submitted, so the task-create JSON and WebSocket frames carry attachment descriptors rather than base64 file contents. An upload that is still in progress or has failed must finish or be retried before the prompt can be sent. Removing a staged attachment discards its private upload; unclaimed uploads expire automatically after 24 hours. This prompt-attachment limit does not change the separate 10 MB task-document upload contract.
During workspace preparation, the initial message shows your uploaded screenshots and file labels. You can open image previews before the agent starts. The preview survives a page reload and remains visible if preparation fails. It does not indicate successful delivery to the agent.
Creating a fresh local branch is available only with the local executor. If the checkout is dirty, Kandev lists the affected paths and requires explicit consent before discarding those local changes. If another path becomes dirty after the warning, creation fails with a conflict and asks for consent again. Save or commit work before approving this operation.
</details>
@@ -2,7 +2,7 @@
status: draft
system: tasks
created: 2026-08-04
updated: 2026-09-02
updated: 2026-09-10
owners:
- Kandev team
---
@@ -41,6 +41,21 @@ This document is the migrated task-system source for the capability. The source
message delivered into a generating turn, the system shall surface a durable
error for that message and shall not deliver an attachment reference whose
bytes are absent from the session.
- **AC-TASKS-PROMPT-ATTACHMENTS-001.8:** While a newly created task session
prepares its workspace, its initial message preview shall show the submitted
text, image previews, and file labels before the agent starts. An attachment-only
submission shall show its attachments without an empty text bubble.
- **AC-TASKS-PROMPT-ATTACHMENTS-001.9:** Reloading that session during
preparation, or after preparation fails, shall preserve its initial preview.
The preview shall not imply successful agent delivery or hide preparation errors.
- **AC-TASKS-PROMPT-ATTACHMENTS-001.10:** When a stored user message becomes
visible, the transcript shall replace the initial preview without duplicate
messages or attachments. A different session shall not inherit that preview.
Unloaded older history shall not cause the preview to reappear.
- **AC-TASKS-PROMPT-ATTACHMENTS-001.11:** Desktop and phone users shall be
able to open preparation image previews and inspect file labels through the
existing attachment controls. Preview failures shall not hide prompt text
or other attachments, and the phone transcript shall remain within the viewport.
## Migrated source detail
@@ -4,6 +4,7 @@ system: tasks
requirements:
- REQ-TASKS-PROMPT-ATTACHMENTS-001
created: 2026-09-01
updated: 2026-09-10
owners:
- Kandev team
---
@@ -111,6 +112,94 @@ backend derives those values from authenticated and persisted state.
Task-scoped idempotency applies only when the stored task matches. Session
scoping remains strict when the stored claim contains a session identity.
## Initial preview during workspace preparation
This proposed extension covers AC-TASKS-PROMPT-ATTACHMENTS-001.8 through
AC-TASKS-PROMPT-ATTACHMENTS-001.11. The task system owns the submitted
attachment set and its session binding. Transcript history still follows the
[UI history design](../../ui/system-design/task-prompt-transcript-visibility.md).
The task-create handlers currently prepare a session synchronously, then start
it asynchronously. `prepareStartAgentSession` does not forward the submitted
text or attachments. `postLaunchCreated` and `postLaunchStart` record the
initial user message after launch. Until then, `useProcessedMessages` builds a
text-only task-description fallback.
Pass an internal, display-only initial-preview value through the task-create
prepare path, including prepare-only creation. Persist it in the target
session's metadata as `initial_prompt_preview` before publishing the created
session or starting workspace preparation. Use an internal preparation option;
do not make preview data a second agent-dispatch input or change passthrough
prepare upgrades. The value contains `content` and `attachments`, using the
existing display descriptor fields: `attachment_id`, `type`, `name`,
`mime_type`, `size_bytes`, and `delivery_mode`. Only validated file-backed
descriptors from the successful task attachment claim enter this value.
Do not copy inline bytes, storage paths, hidden prompts, or arbitrary metadata.
Use the existing session metadata persistence and session publication path.
No new endpoint or table is needed. An atomic metadata-key update must preserve
other session keys. A failed preview write must stop this preparation path
before background work starts and use its existing failure handling.
Reused sessions must not acquire another initial preview. Legacy inline-only
attachments keep their existing post-launch behavior; this extension targets
the file-backed web submission path.
The snapshot remains session-owned across reload and preparation failure. It
is display data, not evidence of delivery, a turn, a prompt ordinal, or a retry
queue. Retain it with the session so a delayed metadata hydration cannot revive
a deleted key. Existing session deletion removes it. Stored user messages are
always authoritative once loaded. Retries must not copy the snapshot into a
different session or alter attachment claim/delivery semantics.
`useSessionState` already provides the resolved session. Pass its validated
preview through `useSessionData` to `useProcessedMessages`. Validate unknown
metadata before mapping it to `Message.metadata.attachments`; reject malformed
entries independently. Never combine task-wide attachment inventory with the
current session or read the transient `deferred_launch` metadata as display state.
Render one synthetic user row only when history is initialized, no older
history remains, and no stored user row is visible. Prefer the session preview
when it has text or valid attachments; otherwise preserve the existing legacy
task-description fallback. Keep the synthetic row's existing identity and
missing-timestamp behavior. Include the preview in memo dependencies so late
session hydration updates the row. Do not insert it into persisted message
state or enable persisted-message mutations for it.
Reuse `chat-message.tsx` image and resource attachment rendering, including
`attachmentContentUrl` and the existing image dialog. Correct its local
attachment type to represent optional inline bytes and file-backed IDs.
Continue to authorize content reads through the existing attachment service.
An inaccessible image must not remove the surrounding message or siblings.
### Desktop and phone composition
Both surfaces show the initial user row above existing preparation progress.
Images and compact file labels wrap inside that row, above its text. The primary
action is opening an image. No additional navigation or toolbar is introduced.
The phone entry is the existing task Chat view. Reuse the dedicated phone
composition in `components/task/task-layout.tsx`, the attachment controls in
`chat-message.tsx`, and the mixed-attachment mobile E2E exemplar.
The transcript remains the single vertical scroll owner. Existing full-height
layout, safe-area handling, image-dialog dismissal, focus return, and touch
targets remain in effect. Inline attachment content fits this brief review
task without an intermediate picker. Desktop and phone share the preview data
and replacement logic; no responsive preference is persisted.
### Verification boundaries
Backend tests pause before workspace launch and verify persisted preview
metadata, fresh reads, prepare-only behavior, claim rejection, and unrelated
session isolation. Frontend tests cover descriptor mapping, attachment-only
content, malformed metadata, late hydration, history guards, and replacement.
Desktop and mobile browser tests open both attachments during preparation,
reload, then observe one stored initial message after launch. Include failure
and unavailable-content cases without suppressing existing progress errors.
## Implementation plans
- [Preparation attachment previews](../../../plans/preparation-attachment-previews/plan.md)
## Observability
Existing launch request diagnostics correlate claim errors with their task and