@@ -0,0 +1,40 @@
|
||||
# ADR-2026-09-18-late-clarification-messages: Answer earlier questions as new messages
|
||||
|
||||
**Status:** accepted
|
||||
**Date:** 2026-09-18
|
||||
**Area:** frontend, workflow
|
||||
|
||||
## Context
|
||||
|
||||
Current-turn ownership prevents stale tool responses from resuming obsolete work.
|
||||
Users still need to answer questions after the original waiter or turn ends.
|
||||
Removing the stale panel alone loses that conversation affordance.
|
||||
|
||||
## Decision
|
||||
|
||||
Preserve current-turn authority for the original tool response. Offer late
|
||||
answers as ordinary user messages in the question's original conversation.
|
||||
Use existing message admission, steering, queue ordering, and retry identity.
|
||||
An affirmative submission receiving a recognized inactive response preserves
|
||||
its answers and continues through that message path. Closing sends nothing.
|
||||
|
||||
The decision extends the presentation of the
|
||||
[current-turn ownership rule](2026-08-14-current-turn-clarification-ownership.md).
|
||||
It does not grant historical questions operational authority. Ordinary messages
|
||||
remain subject to existing session availability and other live input barriers.
|
||||
|
||||
## Consequences
|
||||
|
||||
Users can answer earlier questions without resurrecting stale tool waits.
|
||||
Message admission must retain drafts and stable identities across failures.
|
||||
The UI must distinguish tool delivery, new-message delivery, and queue admission.
|
||||
The Inbox History tab can retain its read-only boundary and link to conversation.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- Reopen historical tool requests: rejected because this restores obsolete
|
||||
workflow barriers and can resume the wrong turn.
|
||||
- Remove inactive controls without a late-answer path: rejected because users
|
||||
lose the ability to respond to questions the agent asked.
|
||||
- Add a separate late-answer transport: rejected because ordinary message
|
||||
admission already owns sending, queueing, and duplicate prevention.
|
||||
@@ -0,0 +1,307 @@
|
||||
---
|
||||
created: 2026-09-18
|
||||
status: complete
|
||||
requirements:
|
||||
- REQ-TASKS-CLARIFICATION-LIFECYCLE-001
|
||||
- REQ-TASKS-CLARIFICATION-RESPONSE-RELIABILITY-001
|
||||
system_design:
|
||||
- ../../specs/tasks/system-design/clarification-active-lifecycle.md
|
||||
- ../../specs/tasks/system-design/clarification-response-reliability.md
|
||||
legacy_specs: []
|
||||
---
|
||||
|
||||
# Implementation Plan: Close or answer inactive clarification questions
|
||||
|
||||
## Overview
|
||||
|
||||
Repair the confirmed inactive-response path associated with
|
||||
[issue #3798](https://github.com/kdlbs/kandev/issues/3798) and preserve a useful
|
||||
conversation path after the original clarification waiter ends. Task 01
|
||||
reconciles inactive responses, race ordering, and Escape behavior. Task 02
|
||||
supersedes its removal-only presentation with late answers delivered as
|
||||
ordinary messages through the source task and session.
|
||||
|
||||
## Evidence and root cause
|
||||
|
||||
Investigation base: `26254fe51f`. The report names `57bbae10ad` but provides no
|
||||
network trace or confirmed reproduction. Both revisions wire the X-shaped
|
||||
`ClarificationSkipButton` to rejection, separately from local collapse.
|
||||
|
||||
`useClarificationGroup` classifies `409 not_active` as `expired`. It updates
|
||||
message status only for `ok`. `useResolveCallback` also handles only `ok`.
|
||||
The overlay renders its expired banner beside enabled question actions.
|
||||
Without an authoritative message update, the question remains pending locally.
|
||||
Further X clicks repeat the request instead of removing the obsolete panel.
|
||||
|
||||
A temporary test copied the existing overlay harness and clicked X twice.
|
||||
Both responses returned `409 {"code":"not_active"}`. After both settled, the
|
||||
test confirmed two requests, `rejected=true`, and no message-store update.
|
||||
Its final removal assertion failed because X remained mounted and enabled.
|
||||
The temporary test was removed; permanent regressions are recorded in Task 01.
|
||||
|
||||
This proves one repairable path, not the cause of every reported occurrence.
|
||||
Timeouts, failed requests, and malformed responses remain distinct outcomes.
|
||||
|
||||
## Revised contract and delivery scope
|
||||
|
||||
The user's latest direction takes precedence over the historical Task 01
|
||||
approach below. Reuse `REQ-TASKS-CLARIFICATION-LIFECYCLE-001`, amended criteria
|
||||
`.1`/`.2` and new criteria `.4` through `.9`. Requirements, design, and the
|
||||
[late-answer ADR](../../decisions/2026-09-18-late-clarification-messages.md)
|
||||
now separate operational tool responses from ordinary messages.
|
||||
|
||||
- Preserve earlier questions in transcript history with an answer-as-new-message action.
|
||||
- Preserve submitted answers across `409 not_active` and send through normal admission.
|
||||
- Keep X/Close as dismissal without a new message for inactive questions.
|
||||
- Reuse existing sending/steering/queue rules; no new busy-agent policy is introduced.
|
||||
- Keep Inbox History read-only and use its existing source-conversation navigation.
|
||||
- Keep Task 01's race and Escape fixes wherever applicable.
|
||||
- Task 02 owns UI, admission adapter, localization, and focused regression coverage.
|
||||
|
||||
UI-02: Earlier question, shared desktop and phone inline composition.
|
||||
|
||||
```text
|
||||
[Earlier question ]
|
||||
[Answer as new message ]
|
||||
opens:
|
||||
[Question and answer choices ]
|
||||
[Custom answer ]
|
||||
[Close] [Send as new message ]
|
||||
[Failure: draft retained, Retry available ]
|
||||
```
|
||||
|
||||
A recognized inactive result during answer submission uses the same delivery
|
||||
path with the captured draft. Sent or queued admission replaces the form with
|
||||
its actual outcome. Closing returns focus to its opener. Keep the existing
|
||||
phone scroll owner and safe areas; controls have 44px coarse-pointer targets.
|
||||
This view covers lifecycle criteria `.4` through `.9`. Exact spacing is illustrative.
|
||||
|
||||
The technical approach and results below retain Task 01's historical repair
|
||||
details and record Task 02's revised implementation separately.
|
||||
|
||||
## Requirement conformance
|
||||
|
||||
- `AC-TASKS-CLARIFICATION-LIFECYCLE-001.2` requires obsolete questions to stop
|
||||
being answerable. Reuse this active requirement without creating a repair spec.
|
||||
- `AC-TASKS-CLARIFICATION-RESPONSE-RELIABILITY-001.1` distinguishes expiry from
|
||||
acceptance and retryable failure.
|
||||
- `AC-TASKS-CLARIFICATION-RESPONSE-RELIABILITY-001.4` preserves retry controls
|
||||
after actual failures. Do not make every unsuccessful response disappear.
|
||||
- The response-reliability design now specifies cache reconciliation. Existing
|
||||
authority and persistence decisions remain unchanged.
|
||||
|
||||
## Scope
|
||||
|
||||
### In scope
|
||||
|
||||
- Reconcile submitted inactive bundles without overwriting newer authority,
|
||||
terminal siblings, or restored pending rows.
|
||||
- Keep expired static overlays free of response controls and preserve the
|
||||
existing Escape, collapse, and generation fences.
|
||||
- Offer historical unanswered questions an answer-as-new-message flow on
|
||||
desktop and phone.
|
||||
- Preserve affirmative answers after recognized inactive responses and deliver
|
||||
them through ordinary message admission with stable retry identity.
|
||||
- Localize the new actions and feedback, update public task guidance, and
|
||||
validate the shared hook plus desktop/mobile browser flows.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Backend mutations, new endpoints, database migrations, and agent cancellation.
|
||||
- Changing Escape/collapse semantics or treating expiry as successful rejection.
|
||||
- Redesigning the Inbox, task summaries, or the clarification layout.
|
||||
- Claiming that this repairs an unobserved timeout or backend failure.
|
||||
|
||||
## Technical approach
|
||||
|
||||
In `apps/web/hooks/domains/session/use-clarification-group.ts`, extend the
|
||||
existing response reconciliation for `expired`. Read the current store rows
|
||||
before changing only pending rows from the submitted bundle to local `expired`.
|
||||
Match message ID, session ID, and pending ID. Preserve current metadata and
|
||||
terminal siblings; never insert a missing row from the submitted snapshot.
|
||||
Use `isPendingClarificationMessage` for the existing legacy pending semantics.
|
||||
Compare `updated_at` independently for each row before applying expiry so a newer
|
||||
authoritative restoration wins while unchanged pending siblings remain eligible.
|
||||
If the request generation is stale while the same pending ID is current again,
|
||||
skip the cache write; an old bundle remains eligible for retirement while a
|
||||
different pending ID is active.
|
||||
|
||||
Keep response identity separate from current UI identity. An old request may
|
||||
retire its own stale rows but cannot change a replacement bundle's controls,
|
||||
outcome callback, or in-flight guard. Preserve the existing generation fence.
|
||||
Do not store expiry in the backend or suppress later authoritative restoration.
|
||||
|
||||
In `clarification-input-overlay.tsx`, render only the existing expired notice
|
||||
when static props retain an inactive bundle. Do not register active response
|
||||
shortcuts in that state. Keep `no_longer_active` distinct from `resolved` and
|
||||
leave `onResolved` success-only. Guard direct submission/retry calls after
|
||||
expiry until bundle replacement or an authoritative restoration resets state.
|
||||
|
||||
Task chat, Quick Chat, and run transcripts derive pending questions from the
|
||||
shared message cache. Their existing selectors remove the obsolete panel.
|
||||
The Inbox retains its existing `no_longer_active` handling. No host-specific
|
||||
successful-response callback substitutes for cache reconciliation.
|
||||
|
||||
For Task 02, the shared late-answer adapter captures the source task/session,
|
||||
question bundle, and answers before invoking `useMessageHandler`. It reuses
|
||||
ordinary input-mode and queue admission, keeps the captured client message ID
|
||||
across remounts, and exposes sent, queued, and retryable failure outcomes.
|
||||
Historical transcript rows open the same inline question form in explicit
|
||||
new-message mode. The source conversation remains the routing authority.
|
||||
|
||||
## ASCII UI preview
|
||||
|
||||
UI-01: Task chat after X receives `409 not_active`, desktop and phone.
|
||||
|
||||
```text
|
||||
Before After
|
||||
[Question X v] [Conversation / question history]
|
||||
[Question no longer active] [Message composer ]
|
||||
[Answer choices ]
|
||||
[Message composer ]
|
||||
```
|
||||
|
||||
The stale panel disappears; history remains. A static host can show the expired
|
||||
notice without response controls. The diagram uses illustrative copy.
|
||||
Phone retains the existing focused chat and inline composer, with no new sheet.
|
||||
`ClarificationPanelSection` and `mobile-clarification.spec.ts` are the nearest
|
||||
shipped exemplars. Keep current scroll ownership, safe-area handling, and
|
||||
44px coarse-pointer targets for remaining controls. Shared state owns expiry.
|
||||
Map this view to lifecycle AC `.2` and response-reliability AC `.1`.
|
||||
|
||||
## Tests
|
||||
|
||||
- `use-clarification-group.test.ts`: inactive Skip updates only matching
|
||||
pending rows; preserve terminal siblings and unrelated bundles in one fixture.
|
||||
- `use-clarification-group.regressions.test.ts`: delayed A response after B
|
||||
mounts, deleted rows, latest metadata preservation, authoritative restoration,
|
||||
and direct resubmission guards.
|
||||
- `clarification-input-overlay.test.tsx`: `X retires an inactive bundle without
|
||||
another rejection request`; replace the existing expired-banner expectation
|
||||
with a non-actionable notice and no success callback. Cover answer retention
|
||||
and late-message success, queue, and retry feedback.
|
||||
- `clarification-request-message.test.tsx` and the formatter tests cover the
|
||||
historical action, current-turn exclusion, question context, selected
|
||||
labels, custom text, and system-marker neutralization.
|
||||
- `use-message-handler.test.ts` covers sent/queued outcomes, a current
|
||||
clarification barrier, unavailable transport, and caller-owned retry IDs.
|
||||
- Existing timeout, retry, malformed-response, success, and collapse tests
|
||||
remain required. These cover response-reliability AC `.1` and `.4`.
|
||||
|
||||
## E2E tests
|
||||
|
||||
Add a scenario tagged `inactive dismissal` in `e2e/tests/chat/clarification.spec.ts`
|
||||
and `e2e/tests/chat/mobile-clarification.spec.ts`. Use `chromium` and
|
||||
`mobile-chrome`, respectively. Intercept the exact response request with
|
||||
`409 not_active`, while retaining the stale pending message in the client.
|
||||
Arm the HTTP causal wait before clicking/tapping X. Assert panel removal,
|
||||
composer availability, and no second rejection request. A newer bundle must
|
||||
remain answerable. Also retain existing real-backend Skip success coverage.
|
||||
|
||||
Add `late answer` scenarios to the same desktop and phone files. Assert that a
|
||||
historical question can open the form, that the admitted message contains the
|
||||
question and answer, and that coarse-pointer controls remain reachable. Keep
|
||||
the active submission fallback covered by the submit-failure scenario.
|
||||
|
||||
## Work orders
|
||||
|
||||
- [x] [Task 01: Reconcile inactive clarification responses](task-01-reconcile-inactive-responses.md)
|
||||
- [x] [Task 02: Send late answers as new messages](task-02-late-answer-messages.md)
|
||||
|
||||
Execute sequentially. Task 02 depends on Task 01 and revises its user-facing outcome.
|
||||
|
||||
## Verification results
|
||||
|
||||
The temporary reproduction failed at the expected control-removal assertion.
|
||||
The existing hook, overlay, and panel suites passed: 3 files, 73 tests.
|
||||
|
||||
```bash
|
||||
(cd apps/web && pnpm exec vitest run hooks/domains/session/use-clarification-group.test.ts components/task/chat/clarification-input-overlay.test.tsx components/task/chat/clarification-panel-section.test.tsx)
|
||||
```
|
||||
|
||||
Documentation catalog validation passed: 291 decisions and 1015 specifications.
|
||||
The full specification lint initially exposed a pre-existing duplicate
|
||||
`AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.8` in
|
||||
`docs/specs/tasks/requirements/queued-session-ownership.md`; the unrelated
|
||||
conversation-surface criterion was renumbered to `.003.10` so the catalog has
|
||||
unique acceptance IDs. The full lint then passed. `git diff --check` passed.
|
||||
|
||||
The normal commit hooks passed without a bypass. The issue assignment was
|
||||
verified as `carlosflorencio`.
|
||||
|
||||
## Implementation results
|
||||
|
||||
Task 01 is complete. The hook now reconciles only current pending rows from the
|
||||
submitted inactive bundle, preserves newer metadata and terminal siblings, and
|
||||
blocks repeated actions until replacement or authoritative restoration. A newer
|
||||
authoritative `updated_at` wins over a delayed inactive response, and stale
|
||||
A→B→A generations cannot expire the bundle that is current again. Static expired
|
||||
overlays retain only the non-actionable notice and do not arm their Escape
|
||||
guard. Desktop and phone E2E cover the same intercepted inactive response.
|
||||
|
||||
The browser-first regression failed before the correction at the expected
|
||||
store-update and stale-control assertions. After the correction, validation
|
||||
passed:
|
||||
|
||||
The review regressions also failed before the correction: the delayed restored
|
||||
bundle became `expired`, the A→B→A path wrote an expired row, and the expired
|
||||
overlay still claimed Escape. Each now passes.
|
||||
|
||||
The later PR review found three follow-up defects in retry ownership, mounted
|
||||
late-message state, and Inbox source-session hydration. The remediation adds
|
||||
post-await pending ID, generation, and bundle guards; a shared source-keyed
|
||||
admission owner; and conditional session/message hydration before ordinary
|
||||
admission. New deterministic coverage records delayed retry rejection and
|
||||
success across bundle replacement, unmount/remount draft recovery, active to
|
||||
transcript sharing, and an Inbox send with empty source caches.
|
||||
|
||||
The review was explicitly code-only. No local build, typecheck, unit, browser,
|
||||
or E2E command was run for that remediation. The normal commit hooks and
|
||||
`git diff --check` are recorded separately from the prior implementation
|
||||
validation below.
|
||||
|
||||
- Task 02 focused Vitest: 8 files, 142 tests passed.
|
||||
- Targeted ESLint: no errors or warnings on changed frontend and E2E files.
|
||||
- Targeted Prettier, TypeScript typecheck, `make build-web`, `make build-backend`, and
|
||||
`git diff --check` passed.
|
||||
- Desktop historical late-answer E2E: 1 test passed.
|
||||
- Phone historical late-answer E2E: 1 test passed.
|
||||
- Active 409 late-answer fallback E2E: 1 test passed.
|
||||
- E2E fixture plugin packaging passed.
|
||||
- Documentation catalog validation passed: 291 decisions and 1015
|
||||
specifications.
|
||||
- `i18n:check` passed for all supported catalogs. The all-locale
|
||||
`i18n:zh-hant` helper still stops on the two pre-existing simplified
|
||||
`workflows.openAgentSettings` entries; the changed `task` namespace converted
|
||||
successfully for both Traditional Chinese catalogs.
|
||||
- Full specification lint passed after the pre-existing duplicate acceptance
|
||||
ID was corrected to `AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.10`.
|
||||
- PR documentation coverage passed after nesting the late-answer criteria under
|
||||
the lifecycle requirement heading.
|
||||
- The review follow-up now compares message versions with `parseTurnTimestamp`,
|
||||
preserving nanosecond ordering and rejecting malformed timestamps. Expiry is
|
||||
selective per row, so an unchanged pending sibling still retires beside a
|
||||
newer restored row. Companion parking work orders reference the corrected
|
||||
`.003.10` criterion.
|
||||
|
||||
## Related delivery records
|
||||
|
||||
[Response reliability](../clarification-response-reliability/plan.md) and
|
||||
[active lifecycle](../clarification-active-lifecycle/plan.md) remain historical
|
||||
delivery records. This follow-up does not change their completed scopes or results.
|
||||
|
||||
## Risks
|
||||
|
||||
- A 409 is not proof that this caller rejected the question. Preserve outcome identity.
|
||||
- Another caller's failed delivery can restore pending state. Authoritative
|
||||
restoration must remain possible; do not add permanent local suppression.
|
||||
- A delayed response must not overwrite newer metadata or terminal siblings.
|
||||
- The report lacks runtime evidence. Investigate separately if the failure
|
||||
persists with a successful response after this focused repair.
|
||||
|
||||
## Documentation impact
|
||||
|
||||
Task 01 did not need public documentation changes. Task 02 must document the
|
||||
late-answer action and its normal send/queue behavior in the existing task
|
||||
conversation guide. This is recorded in `docs/public/tasks-and-workflows.md`;
|
||||
queued admission is described as queueing, not agent receipt.
|
||||
@@ -0,0 +1,194 @@
|
||||
---
|
||||
id: "01-reconcile-inactive-responses"
|
||||
title: "Reconcile inactive clarification responses"
|
||||
status: done
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
requirements:
|
||||
- REQ-TASKS-CLARIFICATION-LIFECYCLE-001
|
||||
- REQ-TASKS-CLARIFICATION-RESPONSE-RELIABILITY-001
|
||||
acceptance_criteria:
|
||||
- AC-TASKS-CLARIFICATION-LIFECYCLE-001.2
|
||||
- AC-TASKS-CLARIFICATION-RESPONSE-RELIABILITY-001.1
|
||||
- AC-TASKS-CLARIFICATION-RESPONSE-RELIABILITY-001.4
|
||||
system_design:
|
||||
- ../../specs/tasks/system-design/clarification-active-lifecycle.md
|
||||
- ../../specs/tasks/system-design/clarification-response-reliability.md
|
||||
---
|
||||
|
||||
# Task 01: Reconcile inactive clarification responses
|
||||
|
||||
## Follow-up scope
|
||||
|
||||
This work order records the completed removal-only implementation at PR #3799
|
||||
head `6edc7320dd`. The user's later direction supersedes that presentation.
|
||||
[Task 02](task-02-late-answer-messages.md) adds answers as ordinary messages.
|
||||
The results below are historical evidence, not validation of the new scope.
|
||||
|
||||
## Summary
|
||||
|
||||
Close stale task-chat questions when X receives an authoritative inactive response.
|
||||
Keep expiry distinct from successful rejection and retryable failure.
|
||||
|
||||
## In scope
|
||||
|
||||
- Shared hook reconciliation against the latest store snapshot.
|
||||
- Non-actionable expired rendering and response-shortcut cleanup.
|
||||
- Focused hook/component regressions and desktop/phone browser evidence.
|
||||
- Accurate plan and work-order results after implementation.
|
||||
|
||||
## Out of scope
|
||||
|
||||
Backend changes, permanent dismissal storage, new UI copy, layout redesign,
|
||||
and changing Escape/collapse into rejection.
|
||||
|
||||
## Acceptance
|
||||
|
||||
1. A recognized inactive response retires only matching pending rows in the
|
||||
submitted bundle. Task chat removes the panel without a WebSocket event.
|
||||
Preserve terminal siblings, unrelated bundles, deleted rows, and newer metadata.
|
||||
2. A static expired overlay exposes no answer, Skip, Submit, or Retry action.
|
||||
Expiry reports `no_longer_active`, never successful rejection. Unknown or
|
||||
malformed responses and transport failures preserve retryable behavior.
|
||||
3. A delayed A response cannot alter B's submission state or callbacks. A later
|
||||
authoritative pending restoration remains answerable, including the same ID.
|
||||
Desktop and phone pass the same inactive-dismissal flow.
|
||||
|
||||
## Implementation sequence
|
||||
|
||||
1. Read the linked requirements and design's inactive-response section.
|
||||
2. Add the regression before changing production code. Mock `409 not_active`,
|
||||
click X, and assert that no actionable stale question remains.
|
||||
3. Reconcile submitted rows using their latest message-cache values. Reuse
|
||||
`isPendingClarificationMessage`, including its missing-status compatibility.
|
||||
Compare each latest `updated_at` with its submitted snapshot and preserve a
|
||||
newer authoritative row while retiring unchanged pending siblings. Fence
|
||||
cache expiry when the request generation is stale and the same pending ID is
|
||||
current again; still retire an old bundle while another pending ID is active.
|
||||
4. Keep current request-generation checks for UI state and callbacks. Test a
|
||||
mixed bundle containing pending and terminal rows, plus an unrelated live bundle.
|
||||
5. Render the expired notice without response actions for static hosts. Prevent
|
||||
repeated hook submissions until replacement or authoritative restoration.
|
||||
An ordinary rerender of stale props must not reset expiry. Do not arm the
|
||||
Escape guard when the expired notice has no keyboard handler.
|
||||
6. Add desktop and phone coverage, run the commands below, and record results.
|
||||
|
||||
## ASCII UI preview
|
||||
|
||||
UI-01: Task chat after inactive X response, shared desktop/phone structure.
|
||||
|
||||
```text
|
||||
Before: [Question / expired notice / X / answers]
|
||||
After: [Conversation / question history]
|
||||
[Message composer ]
|
||||
```
|
||||
|
||||
See the [full preview](plan.md#ascii-ui-preview) for composition and constraints.
|
||||
The stale panel disappears; static hosts retain only the non-actionable notice.
|
||||
Keep current phone scroll ownership, safe areas, and remaining touch targets.
|
||||
This view covers lifecycle AC `.2` and response-reliability AC `.1`.
|
||||
|
||||
## Verification
|
||||
|
||||
Run from the repository root. Install dependencies first in a fresh worktree.
|
||||
Run the new regression alone before the correction and record its expected failure.
|
||||
Then run the complete checks below; E2E commands run sequentially.
|
||||
|
||||
```bash
|
||||
(cd apps && pnpm install --frozen-lockfile)
|
||||
(cd apps/web && pnpm exec vitest run hooks/domains/session/use-clarification-group.test.ts hooks/domains/session/use-clarification-group.regressions.test.ts hooks/domains/session/use-clarification-group.timeout.test.ts components/task/chat/clarification-input-overlay.test.tsx components/task/chat/clarification-panel-section.test.tsx)
|
||||
(cd apps/web && pnpm exec eslint hooks/domains/session/use-clarification-group.ts components/task/chat/clarification-input-overlay.tsx)
|
||||
(cd apps/web && pnpm run typecheck)
|
||||
make build-web
|
||||
make build-backend
|
||||
(cd apps/web && pnpm e2e:run --no-build --project chromium -- tests/chat/clarification.spec.ts --grep 'inactive dismissal')
|
||||
(cd apps/web && pnpm e2e:run --no-build --project mobile-chrome -- tests/chat/mobile-clarification.spec.ts --grep 'inactive dismissal')
|
||||
python3 scripts/list-docs.py validate
|
||||
python3 scripts/lint-spec-files.py --all
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Use the current E2E fixture and causal-wait helpers. Intercept only the target
|
||||
response and retain stale client data so a message event cannot hide the bug.
|
||||
Inspect the phone rendering against UI-01. Do not claim screenshots or E2E
|
||||
results from the design turn; those checks have not run.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/hooks/domains/session/use-clarification-group.ts`
|
||||
- `apps/web/hooks/domains/session/use-clarification-group.test.ts`
|
||||
- `apps/web/hooks/domains/session/use-clarification-group.regressions.test.ts`
|
||||
- `apps/web/hooks/domains/session/use-clarification-group.timeout.test.ts`
|
||||
- `apps/web/components/task/chat/clarification-input-overlay.tsx`
|
||||
- `apps/web/components/task/chat/clarification-input-overlay.test.tsx`
|
||||
- `apps/web/components/task/chat/clarification-panel-section.test.tsx`
|
||||
- `apps/web/e2e/tests/chat/clarification.spec.ts`
|
||||
- `apps/web/e2e/tests/chat/mobile-clarification.spec.ts`
|
||||
|
||||
Read-only integration points include `lib/utils/pending-clarification.ts`,
|
||||
`hooks/use-processed-messages.ts`, `components/task/task-chat-panel.tsx`,
|
||||
`components/quick-chat/quick-chat-content.tsx`, and `components/runs/run-transcript.tsx`
|
||||
under `apps/web/`. Inspect the Inbox's existing outcome consumer too.
|
||||
|
||||
## Dependencies
|
||||
|
||||
None. Execute only after the user's explicit implementation request.
|
||||
|
||||
## Risks
|
||||
|
||||
Local expiry must not become a backend write or permanent suppression. Read
|
||||
current rows before cache updates; a submitted snapshot can contain stale metadata.
|
||||
The report's exact runtime cause remains unconfirmed beyond this reproduced path.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`
|
||||
|
||||
## Inputs
|
||||
|
||||
- [Evidence and root cause](plan.md#evidence-and-root-cause).
|
||||
- [Lifecycle requirements](../../specs/tasks/requirements/clarification-active-lifecycle.md).
|
||||
- [Response requirements](../../specs/tasks/requirements/clarification-response-reliability.md).
|
||||
- [Client recovery design](../../specs/tasks/system-design/clarification-response-reliability.md#inactive-response-reconciliation).
|
||||
- Existing expired-banner test and successful Skip/store-update tests.
|
||||
|
||||
## Results
|
||||
|
||||
Implemented the inactive-response repair. A recognized `409 not_active` now
|
||||
reconciles only matching pending rows from the latest message cache to local
|
||||
`expired` status, preserving newer metadata and terminal siblings while
|
||||
skipping deleted rows and unrelated bundles. A newer authoritative `updated_at`
|
||||
wins over a delayed inactive response, and stale A→B→A generations cannot
|
||||
expire the bundle that is current again. The overlay exposes only the existing
|
||||
expired notice, does not arm its Escape guard, and direct submit, Skip, and
|
||||
Retry calls are guarded until a replacement or authoritative pending
|
||||
restoration arrives.
|
||||
|
||||
Added shared hook and overlay regressions, delayed-response coverage, and
|
||||
desktop/mobile E2E scenarios. The browser-first regression failed before the
|
||||
production change at the expected cache-update and stale-control assertions.
|
||||
The review regressions then failed before the ordering and Escape-guard
|
||||
correction, and pass after it.
|
||||
|
||||
Validation passed:
|
||||
|
||||
- Focused Vitest: 5 files, 96 tests.
|
||||
- Strict timestamp comparison preserves RFC3339Nano ordering and rejects
|
||||
malformed or Date-normalized values.
|
||||
- Selective expiry preserves a newer restored row while retiring an unchanged
|
||||
pending sibling in the same bundle.
|
||||
- Targeted ESLint: no errors or warnings.
|
||||
- Prettier and TypeScript typecheck.
|
||||
- `make build-web` and `make build-backend`.
|
||||
- Desktop inactive-dismissal E2E: 1 passed.
|
||||
- Phone inactive-dismissal E2E: 1 passed.
|
||||
- E2E fixture plugin packaging.
|
||||
- Documentation catalog validation, full specification lint, and
|
||||
`git diff --check`.
|
||||
|
||||
The full specification lint first exposed a pre-existing duplicate acceptance
|
||||
ID in the queued-session ownership requirements. The unrelated
|
||||
conversation-surface criterion was renumbered to
|
||||
`AC-TASKS-QUEUED-SESSION-OWNERSHIP-003.10`; the final catalog lint passed.
|
||||
Companion parking work orders now reference that corrected criterion.
|
||||
@@ -0,0 +1,221 @@
|
||||
---
|
||||
id: "02-late-answer-messages"
|
||||
title: "Send late answers as new messages"
|
||||
status: done
|
||||
wave: 2
|
||||
depends_on:
|
||||
- 01-reconcile-inactive-responses
|
||||
plan: "plan.md"
|
||||
requirements:
|
||||
- REQ-TASKS-CLARIFICATION-LIFECYCLE-001
|
||||
acceptance_criteria:
|
||||
- AC-TASKS-CLARIFICATION-LIFECYCLE-001.4
|
||||
- AC-TASKS-CLARIFICATION-LIFECYCLE-001.5
|
||||
- AC-TASKS-CLARIFICATION-LIFECYCLE-001.6
|
||||
- AC-TASKS-CLARIFICATION-LIFECYCLE-001.7
|
||||
- AC-TASKS-CLARIFICATION-LIFECYCLE-001.8
|
||||
- AC-TASKS-CLARIFICATION-LIFECYCLE-001.9
|
||||
system_design:
|
||||
- ../../specs/tasks/system-design/clarification-active-lifecycle.md
|
||||
- ../../specs/tasks/system-design/clarification-response-reliability.md
|
||||
---
|
||||
|
||||
# Task 02: Send late answers as new messages
|
||||
|
||||
## Summary
|
||||
|
||||
Let users answer earlier human clarification questions through ordinary messages.
|
||||
Preserve answers when tool submission receives a recognized inactive result.
|
||||
This revises PR #3799's removal-only behavior without reopening tool authority.
|
||||
|
||||
## In scope
|
||||
|
||||
- Transcript entry point and inline late-answer form on desktop and phone.
|
||||
- A shared question/answer formatter and adapter to ordinary message admission.
|
||||
- Recognized inactive-answer fallback, draft retention, stable retry identity,
|
||||
correct session targeting, and explicit sent/queued feedback.
|
||||
- Existing live-waiter and detached-current-turn paths remain operational.
|
||||
- Localization in all supported catalogs and focused public documentation.
|
||||
|
||||
## Out of scope
|
||||
|
||||
Backend tool-response authority changes, replacement sessions, forced interruption,
|
||||
Inbox History mutations, permission approvals, and parent-agent question routing.
|
||||
Do not make historical questions count as pending workflow input.
|
||||
|
||||
## Acceptance
|
||||
|
||||
1. A user can answer an earlier unanswered question from its conversation.
|
||||
Question prompts, selected labels, and custom text reach ordinary admission
|
||||
for the source task/session. Idle, busy, and unavailable cases match chat.
|
||||
2. Recognized inactive results preserve affirmative answers for the same path.
|
||||
Close sends nothing. Unknown conflicts and uncertain tool transport results
|
||||
retain the existing response retry path instead of sending a duplicate message.
|
||||
3. Failures retain drafts; retries reuse admission identity. Navigation and
|
||||
newer questions cannot redirect delivery or lose a pending admission.
|
||||
Desktop and phone distinguish queued admission from sent messages.
|
||||
|
||||
## Technical approach
|
||||
|
||||
Extend `useClarificationGroup` with a typed late-message callback/outcome.
|
||||
Capture the submitted questions and answers before awaiting the response.
|
||||
Do not emit a destructive host outcome before fallback admission settles.
|
||||
Keep an admission owner mounted independently of the expiring overlay, so
|
||||
its draft and stable request ID survive cache updates and close/reopen cycles.
|
||||
|
||||
Reuse `useMessageHandler`, `deriveSessionInputMode`, and `useQueue` through
|
||||
one shared adapter. Do not issue raw prompt or queue requests from components.
|
||||
Read the source session's latest state; an obsolete question must not force
|
||||
`hasPendingClarification=true`. A separate current question keeps its barrier.
|
||||
Retain existing request-generation and per-row version fences.
|
||||
|
||||
Add a shared formatter with focused tests. Render source questions and answers
|
||||
as ordinary user text, not a privileged system marker. Never derive the target
|
||||
from global active selection after asynchronous work.
|
||||
|
||||
`ClarificationRequestMessage` exposes the history action for human questions
|
||||
that are no longer operational. Reuse the existing question form with explicit
|
||||
new-message mode and Close. The message renderer supplies needed conversation
|
||||
context. Read-only surfaces navigate to that conversation; the Inbox History
|
||||
read path remains free of mutations. Existing authenticated message admission
|
||||
owns access and availability checks.
|
||||
|
||||
## ASCII UI preview
|
||||
|
||||
UI-02: Shared desktop and phone inline flow, from transcript history.
|
||||
|
||||
```text
|
||||
[Earlier question ]
|
||||
[Answer as new message ]
|
||||
opens:
|
||||
[Question and answer choices ]
|
||||
[Custom answer ]
|
||||
[Close] [Send as new message ]
|
||||
[Failure: draft retained, Retry available ]
|
||||
```
|
||||
|
||||
See [the combined preview](plan.md#revised-contract-and-delivery-scope).
|
||||
Reuse inline clarification controls and existing scroll ownership. Keep phone
|
||||
safe areas and 44px coarse-pointer targets. Return focus to opener on Close.
|
||||
Localize action and sent/queued feedback. Map to criteria `.4` through `.9`.
|
||||
|
||||
## Tests and verification
|
||||
|
||||
Deterministic coverage covers both entry points: inactive response after an
|
||||
answer submission, and direct historical-question answering. The shared
|
||||
message-handler tests cover source-session barriers, busy queue admission,
|
||||
unavailable transport, and stable caller-owned retry IDs. Overlay and group
|
||||
tests cover answer retention, retry feedback, current-question exclusion,
|
||||
timeout/error separation, and zero-message Close behavior. Existing
|
||||
live-answer and Skip tests stay green.
|
||||
|
||||
Add `late answer` cases to desktop/mobile clarification browser files. Assert
|
||||
actual admitted message or queue content with the original question and answers,
|
||||
not only disappearance of the overlay. A failed admission preserves the form.
|
||||
|
||||
Run these commands from the repository root during implementation, not design:
|
||||
|
||||
```bash
|
||||
(cd apps && pnpm install --frozen-lockfile)
|
||||
(cd apps/web && pnpm exec vitest run hooks/domains/session/use-clarification-group.test.ts hooks/domains/session/use-clarification-group.regressions.test.ts hooks/domains/session/use-clarification-group.timeout.test.ts hooks/use-message-handler.test.ts hooks/domains/session/session-input-mode.test.ts components/task/chat/clarification-input-overlay.test.tsx components/task/chat/messages/clarification-request-message.test.tsx)
|
||||
(cd apps/web && pnpm run typecheck)
|
||||
(cd apps/web && pnpm run i18n:zh-hant && pnpm run i18n:check)
|
||||
make build-web
|
||||
make build-backend
|
||||
(cd apps/web && pnpm e2e:run --no-build --project chromium -- tests/chat/clarification.spec.ts --grep 'late answer')
|
||||
(cd apps/web && pnpm e2e:run --no-build --project mobile-chrome -- tests/chat/mobile-clarification.spec.ts --grep 'late answer')
|
||||
python3 scripts/list-docs.py validate
|
||||
python3 scripts/lint-spec-files.py --all
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Include any newly extracted helper test files in the exact Vitest command
|
||||
before implementation completion. Record real results and rendered UI checks.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/hooks/domains/session/use-clarification-group.ts` and its tests.
|
||||
- `apps/web/hooks/use-message-handler.ts` and its existing tests if adaptation is needed.
|
||||
- `apps/web/components/task/chat/clarification-input-overlay.tsx` and its tests.
|
||||
- `apps/web/components/task/chat/messages/clarification-request-message.tsx` and its tests.
|
||||
- `apps/web/components/task/chat/message-renderer.tsx`.
|
||||
- Task chat, Quick Chat, and run-transcript host callback wiring as required.
|
||||
- `apps/web/e2e/tests/chat/clarification.spec.ts`.
|
||||
- `apps/web/e2e/tests/chat/mobile-clarification.spec.ts`.
|
||||
- Relevant `apps/web/src/locales/*/task.json` and `chat.json` catalogs.
|
||||
- `docs/public/tasks-and-workflows.md`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
Task 01, PR #3799 head `6edc7320dd`. Re-read its current head before implementation.
|
||||
|
||||
## Risks
|
||||
|
||||
A tool failure can be ambiguous. Fall back only on recognized inactive results.
|
||||
Cache retirement must not unmount the owner of draft/admission state.
|
||||
Historical content must never become trusted system instructions.
|
||||
Current-turn authority and Inbox History isolation remain unchanged.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`
|
||||
|
||||
## Inputs
|
||||
|
||||
- Updated lifecycle criteria `.4` through `.9`.
|
||||
- Response-reliability design's inactive-response and late-answer section.
|
||||
- ADR `2026-09-18-late-clarification-messages`.
|
||||
- Task 01's completed race and Escape regressions.
|
||||
|
||||
## Results
|
||||
|
||||
Task 02 is complete. The shared late-answer adapter captures the source task and
|
||||
session, formats question context with selected labels and custom text as
|
||||
ordinary user content, and delivers it through the existing message admission
|
||||
and queue rules. A recognized inactive affirmative response preserves the
|
||||
answers and uses that same adapter. Close only dismisses the form. Unknown or
|
||||
ambiguous failures remain on the original retry path.
|
||||
|
||||
The transcript action is available for historical unanswered questions, while a
|
||||
current pending turn does not expose a duplicate answer action. Desktop and
|
||||
phone use the same inline form with localized sent, queued, and retry feedback.
|
||||
The source question bundle and client admission ID remain stable across
|
||||
navigation and remounts. Inbox History remains read-only and continues to link
|
||||
to the source conversation.
|
||||
|
||||
Validation passed:
|
||||
|
||||
- Focused Vitest: 8 files, 142 tests.
|
||||
- TypeScript typecheck, `make build-web`, and `make build-backend`.
|
||||
- `i18n:check`; changed `task` namespaces converted for `zh-hk` and `zh-tw`.
|
||||
The all-locale converter still reports the two unrelated historical
|
||||
`workflows.openAgentSettings` residuals.
|
||||
- E2E: desktop historical late answer, mobile historical late answer, and
|
||||
active 409 fallback, 1 test each.
|
||||
- Documentation catalog validation, full specification lint, targeted Prettier, and
|
||||
`git diff --check`.
|
||||
- PR documentation coverage passed after nesting the late-answer criteria under
|
||||
the lifecycle requirement heading.
|
||||
- Targeted ESLint reported no errors or warnings on changed frontend and E2E
|
||||
files.
|
||||
|
||||
## Review remediation
|
||||
|
||||
The code-review follow-up hardens late admission across the same source task,
|
||||
session, and clarification bundle. Retry completion now captures and checks the
|
||||
pending ID, request generation, and submitted bundle before every post-await
|
||||
state update, cache reconciliation, and outcome callback. A delayed retry from
|
||||
an older bundle cannot replace the state of a newer bundle or a returned
|
||||
generation.
|
||||
|
||||
Late-message status, draft answers, and client admission identity now live in a
|
||||
module-owned external record shared by active, transcript, and Inbox hosts.
|
||||
When the source session or message cache is absent, the adapter hydrates them
|
||||
through the existing session APIs before ordinary send, steer, or queue
|
||||
admission derives its input mode. The regression coverage includes delayed
|
||||
failure across unmount/remount, active-to-transcript state sharing, missing
|
||||
Inbox caches, and A→B and A→B→A delayed retry races.
|
||||
|
||||
This review was code-only by instruction. No local builds, typechecks, unit
|
||||
tests, browser tests, or E2E tests were run for the remediation. The normal
|
||||
commit hooks and `git diff --check` remain the applicable local checks.
|
||||
@@ -41,7 +41,7 @@ resume on open even while a session is parked. The screenshot is presentation
|
||||
evidence; this plan does not claim a new live-state investigation of that task.
|
||||
|
||||
The old inspection requirement 001.1 and parked-note requirement 003.3 are
|
||||
explicitly superseded by 001.9 and 003.8. Criteria 001.7/8 retain restart and
|
||||
explicitly superseded by 001.9 and 003.10. Criteria 001.7/8 retain restart and
|
||||
settled-queue coverage. Queue identity, automatic admission, callback correlation,
|
||||
and the deadlock fixes remain compatibility constraints.
|
||||
|
||||
@@ -123,7 +123,7 @@ The task drawer remains the entry point. The session picker selects the
|
||||
conversation and normal recovery follows. The dedicated mobile layout, safe-area
|
||||
navigation, and single chat scroll owner remain. No hover interaction is required.
|
||||
|
||||
UI-01 and UI-02 cover AC 001.9 and 003.8. Placement and absence of parking chrome
|
||||
UI-01 and UI-02 cover AC 001.9 and 003.10. Placement and absence of parking chrome
|
||||
are requirements; text spacing is illustrative. Existing recovery errors, queue
|
||||
waiting, loading, and explicit controls keep their established presentation.
|
||||
The nearest mobile exemplar is `mobile-queued-session-ownership.spec.ts`.
|
||||
@@ -144,7 +144,7 @@ AC suffixes use `AC-TASKS-QUEUED-SESSION-OWNERSHIP-`.
|
||||
| Archive, explicit cancellation, completion, authorization failure | Existing lifecycle restrictions remain | 001.9 |
|
||||
| Allowed status followed by conflicting queue or route change | Guarded admission rejects stale execution | 001.2, 001.6; 002.6 |
|
||||
| Restart after switch or queue settlement | Conversation context survives; opening requests normal recovery | 001.7, 001.8, 001.9 |
|
||||
| Desktop/phone with stored parking marker | No parked note; ordinary controls and real queue status remain | 003.8 |
|
||||
| Desktop/phone with stored parking marker | No parked note; ordinary controls and real queue status remain | 003.10 |
|
||||
|
||||
## E2E tests
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Both: No parked-session row; genuine task queue status remains when needed.
|
||||
|
||||
Keep the existing desktop tabs and phone task drawer/session picker.
|
||||
The chat remains the single conversation scroll owner. No new touch targets or
|
||||
hover-only behavior are introduced. Cover AC 001.9 and 003.8.
|
||||
hover-only behavior are introduced. Cover AC 001.9 and 003.10.
|
||||
|
||||
## Verification
|
||||
|
||||
|
||||
@@ -461,6 +461,20 @@ The preference only gates opening a task. Choosing **Start agent** (or a
|
||||
workflow step transition) always starts the agent as usual, and a failed or
|
||||
interrupted session still shows its recovery actions.
|
||||
|
||||
## Answer clarification questions
|
||||
|
||||
When an agent asks a clarification question, answer it from the question panel
|
||||
while the session is waiting. If the question becomes inactive before you
|
||||
answer, its entry remains in the conversation with **Answer as new message**.
|
||||
That action includes the question and your answer in a normal conversation
|
||||
message. It does not reopen the old tool request.
|
||||
|
||||
Normal message rules still apply. An idle session receives the message; a busy
|
||||
session sends or queues it according to its current input mode. If delivery
|
||||
fails, the form keeps your answer so you can retry. **Close question** removes
|
||||
the answer surface without sending anything, while the question stays in the
|
||||
conversation history.
|
||||
|
||||
## Task dependencies
|
||||
|
||||
A task can declare that it **depends on** one or more other tasks. This is a
|
||||
|
||||
@@ -15,7 +15,7 @@ owners:
|
||||
|
||||
|
||||
|
||||
Clarification bundles remain answerable only for the session turn that owns them, while detached and recovery paths preserve the response contract.
|
||||
Tool responses belong to the owning session turn. Users can send answers to earlier questions as new messages without reopening the original tool request.
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ hiding the action the icon represents.
|
||||
|
||||
## What
|
||||
|
||||
The active/answerable rules in this section describe the original tool request.
|
||||
They do not prohibit the separate late-answer message action specified below.
|
||||
|
||||
- A clarification bundle is active only when at least one row in the bundle is pending and the bundle
|
||||
belongs to the session's current turn.
|
||||
- A detached bundle remains active and answerable while its turn remains current. Detachment sets
|
||||
@@ -130,8 +133,7 @@ hiding the action the icon represents.
|
||||
one turn identity from the same bundle rule: legacy empty turn IDs do not mask a consistent non-empty
|
||||
identity, while conflicting non-empty IDs invalidate the identity.
|
||||
- Any response to a superseded or terminal bundle returns conflict, performs no message mutation, and
|
||||
initiates no agent resume. Current clients close their obsolete local overlay through the existing
|
||||
conflict handling.
|
||||
initiates no agent resume. Clients distinguish rejection from an affirmative answer. An affirmative answer can continue as a new user message under the late-answer criteria below.
|
||||
- Persisted task status summaries reconcile `pending_action` against current-turn repository state on
|
||||
source events and task-list/boot reads. Existing summaries are repaired, not only missing rows.
|
||||
- When a task row advertises a pending action, desktop and phone task activation load the task's
|
||||
@@ -158,7 +160,7 @@ hiding the action the icon represents.
|
||||
|
||||
|
||||
|
||||
**Intent:** Clarification bundles remain answerable only for the session turn that owns them, while detached and recovery paths preserve the response contract.
|
||||
**Intent:** Tool responses belong to the owning session turn. Users can send answers to earlier questions as new messages without reopening the original tool request.
|
||||
|
||||
|
||||
|
||||
@@ -166,12 +168,39 @@ hiding the action the icon represents.
|
||||
|
||||
|
||||
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.1:** When a clarification is pending, the system shall expose and process only the bundle owned by the session's current turn.
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.2:** When a newer turn or terminal session state supersedes a bundle, the system shall keep its transcript history without leaving it answerable or able to block workflow progress.
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.1:** When a clarification is pending, the system shall process tool responses only for the bundle owned by the session's current turn. Earlier questions can accept new-message answers under the late-answer criteria below.
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.2:** When a newer turn or terminal session state supersedes a bundle, the system shall keep its transcript history without leaving the original tool request answerable or able to block workflow progress. Users can answer through a separate new-message action.
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.3:** When detached clarification recovery succeeds or fails, the system shall return the bounded response and durable recovery outcome defined by the lifecycle contract.
|
||||
|
||||
|
||||
|
||||
#### Late answers as new messages
|
||||
|
||||
These criteria extend the existing lifecycle requirement. They separate an
|
||||
operational tool response from a user choosing to continue the conversation.
|
||||
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.4:** When an unanswered question is
|
||||
superseded or expired, its transcript entry shall offer an answer-as-new-message
|
||||
action on desktop and phone. It shall not claim that the agent is still waiting.
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.5:** When an affirmative answer receives
|
||||
a recognized inactive result, the interface shall preserve the submitted answers
|
||||
and send them with their question context through ordinary message delivery.
|
||||
It shall not silently discard those answers or report successful tool delivery.
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.6:** A late answer shall target the
|
||||
question's original task and session. It shall follow ordinary sending,
|
||||
steering, queue ordering, and unavailable-session behavior. An idle input-capable
|
||||
session shall receive it without requiring the original tool waiter.
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.7:** Closing an inactive question shall
|
||||
dismiss its answer surface without sending a message or resuming the agent.
|
||||
The question shall remain in history. Active rejection keeps its existing semantics.
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.8:** A failed late-message admission shall
|
||||
preserve the answer and offer retry. An ambiguous transport result shall not
|
||||
automatically start another delivery path. Retrying one admission shall reuse
|
||||
its identity rather than create duplicate messages.
|
||||
- **AC-TASKS-CLARIFICATION-LIFECYCLE-001.9:** Sending a late answer shall not reopen
|
||||
the original tool request, restore its workflow barrier, or resolve another
|
||||
pending question. The interface shall distinguish sent from queued admission.
|
||||
|
||||
## Out of scope
|
||||
|
||||
|
||||
|
||||
@@ -97,6 +97,13 @@ No new route. Task-session responses add `pending_action_revision` beside the ex
|
||||
still owned by the in-memory clarification store. The chat Skip control uses `/respond` with
|
||||
`rejected=true`, including for detached requests.
|
||||
|
||||
## Late conversation replies
|
||||
|
||||
The tool-response contract above does not prohibit an ordinary user message
|
||||
about an earlier question. See [late-answer delivery](clarification-response-reliability.md#inactive-response-reconciliation-and-late-answers)
|
||||
for transcript actions and inactive-response fallback. That path does not claim
|
||||
the historical bundle or revive its operational authority.
|
||||
|
||||
## State machine
|
||||
|
||||
One clarification bundle has five operational states:
|
||||
|
||||
@@ -3,6 +3,7 @@ status: current
|
||||
system: tasks
|
||||
requirements:
|
||||
- REQ-TASKS-CLARIFICATION-RESPONSE-RELIABILITY-001
|
||||
- REQ-TASKS-CLARIFICATION-LIFECYCLE-001
|
||||
---
|
||||
|
||||
# Clarification response reliability System Design
|
||||
@@ -133,6 +134,68 @@ winner when the first request committed, or claims the still-pending bundle
|
||||
when the first request did not. A 409 `not_active` remains an expired outcome,
|
||||
not a successful submission.
|
||||
|
||||
### Inactive-response reconciliation and late answers
|
||||
|
||||
This section also maps `REQ-TASKS-CLARIFICATION-LIFECYCLE-001`, criteria
|
||||
`.4` through `.9`. The [late-answer decision](../../../decisions/2026-09-18-late-clarification-messages.md)
|
||||
separates operational tool authority from ordinary conversation.
|
||||
|
||||
`useClarificationGroup` captures question prompts, option labels, custom text,
|
||||
answers, task ID, and session ID before submitting. An affirmative response
|
||||
classified as inactive transfers that snapshot to a shared late-message action.
|
||||
It does not clear the draft before message admission succeeds. Rejection instead
|
||||
retires the obsolete surface without sending an ordinary message.
|
||||
|
||||
Use the existing `useMessageHandler` admission path for late messages. Format
|
||||
question prompts and selected labels with custom answers as ordinary user text.
|
||||
Do not interpolate them into a privileged system block. A directly opened
|
||||
historical question uses this path without retrying the obsolete response endpoint.
|
||||
A recognized inactive result after an affirmative submit uses the same path
|
||||
without requiring the user to enter the answers again.
|
||||
|
||||
A typed host callback connects the shared question UI to message admission.
|
||||
Resolve its task/session from the source question, not global active selection.
|
||||
Retain the captured identity across navigation. Task chat and Quick Chat wire
|
||||
this callback through their existing chat handler. A transcript question opens
|
||||
an inline late-answer form using the same formatter and admission adapter.
|
||||
Read-only run surfaces link to the source conversation rather than acquire a
|
||||
second message transport. The Inbox History tab remains read-only; its existing
|
||||
conversation navigation provides access to the transcript action.
|
||||
|
||||
Refresh message/session authority after an inactive result before deriving
|
||||
promptability. The stale question itself must not force `hasPendingClarification`
|
||||
to true and queue an otherwise idle session forever. A different active question
|
||||
retains its barrier. Reuse `deriveSessionInputMode` and queue admission for busy
|
||||
sessions; do not introduce forced interruption or a separate queue policy.
|
||||
Unavailable sessions preserve the answer and use ordinary recovery feedback.
|
||||
No automatic replacement task or session is created.
|
||||
|
||||
Keep the existing stable admission identity across retries, including when the
|
||||
message surface closes and reopens while admission is unresolved. A rejected
|
||||
admission preserves the draft; an uncertain admission reconciles by that same
|
||||
identity. Do not send ordinary messages after timeouts, malformed responses,
|
||||
unknown conflicts, or generic server failures in the tool-response path.
|
||||
Those outcomes retain its existing retry flow.
|
||||
|
||||
Separate tool outcomes from message outcomes. `no_longer_active` is not a
|
||||
successful tool response; sent/queued late answers need their own outcome.
|
||||
Consumers must not unmount an answer draft merely on `no_longer_active` while
|
||||
new-message admission is pending or has failed. Success-only `onResolved`
|
||||
remains specific to the original response path.
|
||||
|
||||
Cache retirement preserves current row metadata, newer authoritative versions,
|
||||
terminal siblings, deleted rows, and request-generation ownership. Keep the
|
||||
per-row RFC3339Nano version checks from the inactive-dismissal repair. Retiring
|
||||
operational state must not remove the transcript's late-answer entry point.
|
||||
An expired surface has working Close and new-message actions. Its Escape guard
|
||||
must match an actually mounted handler. New-message admission never marks the
|
||||
historical tool request answered or reactivates its pending-action projection.
|
||||
|
||||
Desktop and phone use the existing inline question form. Keep the current scroll
|
||||
owner and safe areas, provide 44px coarse-pointer actions, and return focus to
|
||||
the transcript opener or composer after closing. Translate new labels in all
|
||||
supported catalogs. The action explicitly says it sends a new message.
|
||||
|
||||
Local collapse, dismiss, Escape, and task navigation do not mutate the bundle.
|
||||
Skip remains the explicit rejection path. These controls may be disabled only
|
||||
while the bounded request is in flight and become available again on a
|
||||
|
||||
Reference in New Issue
Block a user