Skip to content

feat(web): add configurable queue/steer follow-up behavior#1479

Open
leonardoxr wants to merge 14 commits intopingdotgg:mainfrom
leonardoxr:feat/follow-up-behavior-1462
Open

feat(web): add configurable queue/steer follow-up behavior#1479
leonardoxr wants to merge 14 commits intopingdotgg:mainfrom
leonardoxr:feat/follow-up-behavior-1462

Conversation

@leonardoxr
Copy link
Copy Markdown

@leonardoxr leonardoxr commented Mar 28, 2026

Closes #1462

Summary

Adds explicit follow-up behavior while a thread is already running.

Users can now choose a global Follow-up behavior setting:

  • Steer: send the follow-up as guidance for the active run
  • Queue: hold the follow-up and auto-send it after the current run settles

The composer also supports a one-off opposite behavior shortcut for a single message.

What changed

  • added followUpBehavior to client settings with a default of steer
  • added a settings control in chat settings for Queue vs Steer
  • kept follow-up submission available while a run is active
  • added queued follow-up persistence per thread
  • added auto-dispatch for the queue head when the thread becomes sendable again
  • added queued follow-up actions for steer, edit, delete, and reorder
  • kept steered follow-ups out of the visible chat timeline so they behave like guidance rather than a new visible turn
  • added keyboard support for sending the opposite behavior once
    • Windows/Linux: Ctrl+Shift+Enter
    • macOS: Cmd+Shift+Enter

Why

This makes follow-up delivery explicit and predictable while a run is active, which is the core problem described in #1462.

Verification

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/composerDraftStore.test.ts
  • bun run test:browser -- src/components/ChatView.browser.tsx --testNamePattern "queued|follow-up"

Media

Settings

Settings

Before

Before

After

After

Before/after comparison

Before/after comparison

Preview

Preview


Note

Medium Risk
Adds new queued-follow-up persistence, projection logic, and an always-on reactor that can auto-dispatch turns after a run settles; mistakes could cause unexpected sends, attachment leaks, or projection/cleanup bugs. Includes a DB migration and new attachment normalization/validation paths in wsServer, increasing surface area for regressions.

Overview
Adds a persisted per-thread queued follow-up queue and a new QueuedFollowUpReactor that replays/auto-dispatches the queue head by issuing thread.turn.start and then removing it once the thread becomes sendable again (including after server restarts).

Extends orchestration with new queued follow-up commands/events and full support across decider, in-memory projector, ProjectionPipeline (including attachment pruning on revert/remove/upsert and touching thread updatedAt), and ProjectionSnapshotQuery so snapshots now include queuedFollowUps.

Updates wsServer to normalize queued follow-up image attachments the same way as turn sends (persist data URLs, validate persisted ids are thread-scoped, and serve attachments by id), adds the new projection_thread_queued_follow_ups table via migration 019, and expands integration/unit/browser tests plus a harness option to control reactor startup.

Written by Cursor Bugbot for commit f049043. This will update automatically on new commits. Configure here.

Note

Add configurable queue/steer follow-up behavior for running chat threads

  • Introduces a followUpBehavior setting (queue or steer, default steer) that controls what happens when the user submits a message while a thread turn is running.
  • When queuing, the composer snapshot (prompt, attachments, terminal contexts, model selection) is saved server-side as a QueuedFollowUp; a new QueuedFollowUpReactor automatically dispatches the head queued follow-up when the thread becomes sendable.
  • When steering, the current turn is interrupted and the follow-up is sent immediately; if interruption fails, the composer is restored.
  • Adds a ComposerQueuedFollowUpsPanel UI with drag-and-drop reorder, edit, delete, and steer-now actions per queued follow-up.
  • Adds six new orchestration commands/events (thread.queued-follow-up.{enqueue,update,remove,reorder,send-failed,send-error-cleared}), a new DB table (projection_thread_queued_follow_ups) via migration 19, and full decider/projector/projection-pipeline support.
  • A modifier key (Cmd/Ctrl+Shift+Enter) inverts the active follow-up behavior at send time.
  • Risk: bumps COMPOSER_DRAFT_STORAGE_VERSION to 4, invalidating previously persisted composer drafts.

Macroscope summarized f049043.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 749e8f5d-d4ea-4c60-9b0c-11ce660d23d9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Mar 28, 2026
@juliusmarminge
Copy link
Copy Markdown
Member

I want this behavior to be server side. I shouldn't need to keep my client open for the queing to work

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 11ec9d73f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@leonardoxr
Copy link
Copy Markdown
Author

leonardoxr commented Mar 28, 2026

I want this behavior to be server side. I shouldn't need to keep my client open for the queing to work
@juliusmarminge
I will work on that! Thanks for the feedback.

@leonardoxr
Copy link
Copy Markdown
Author

@juliusmarminge A question about the Steer behavior.

Should the steer wait for the AI to finish its "step/thinking/action" before sending(like claude code does) or should it interrupt indeed.

Or, should it also be an option? Like Queue / Steer / "Force Steer"

@leonardoxr leonardoxr marked this pull request as draft March 28, 2026 03:19
@leonardoxr
Copy link
Copy Markdown
Author

Follow-up update on this PR after the review and manual testing:

I moved queued follow-ups out of the client draft store and into server-owned orchestration state.

What changed:

  • Added durable queued follow-up contracts to the orchestration model, including prompt, persisted attachments, terminal context snapshots, model/runtime/interaction settings, and per-item send error state.
  • Added projection storage + migration for queued follow-ups, and included queuedFollowUps in thread snapshots so the web renders queue state from the server read model instead of local draft state.
  • Added a dedicated queued follow-up reactor on the server. It now owns head-only auto-dispatch, startup recovery after restart, and blocked-state gating.
  • Queued image attachments are normalized into persisted server attachments at enqueue time, so queued items survive reconnect/browser close instead of depending on client-local blob/data URLs.
  • The client queue panel now round-trips reorder/delete/queue/steer actions through orchestration commands, while local draft state only keeps composer/edit metadata.

I also fixed two regressions uncovered while testing:

  • Queue dispatch race: multiple queued items could drain back-to-back before the previous queued send visibly settled. The reactor now keeps a per-thread dispatch lock until that queued send settles.
  • Steer visibility: the browser tests were enforcing hidden steer messages, which matched the old hidden-message path in the implementation. Steered follow-ups now show up as normal visible user messages after the interrupt completes, and the browser assertions were updated accordingly.

Coverage added/updated for:

  • queue survives orchestration restart and replays later
  • queued image attachments are persisted server-side
  • reactor blocks on pending approval / pending user-input
  • queued-item steer interrupts before send
  • visible steer behavior from the composer and opposite-submit shortcut

Validation I ran before pushing this update:

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/orchestration/Layers/QueuedFollowUpReactor.test.ts --testNamePattern "pending approval|pending user-input"
  • bun run test -- src/wsServer.test.ts --testNamePattern "normalizes queued follow-up image attachments into persisted server attachments"
  • bun run test -- integration/orchestrationEngine.integration.test.ts --testNamePattern "replays queued follow-ups after orchestration restarts"
  • bun run test -- src/components/ChatView.logic.test.ts src/composerDraftStore.test.ts src/store.test.ts
  • bun run test:browser -- src/components/ChatView.browser.tsx --testNamePattern "queued|follow-up|steer|Ctrl\\+Shift\\+Enter|Cmd\\+Shift\\+Enter"

@leonardoxr
Copy link
Copy Markdown
Author

Addressed the remaining review items in this branch:

  • Moved the queued follow-ups panel hooks above the empty-state early return so it no longer risks a Rules of Hooks crash when the queue transitions between empty and non-empty.
  • Kept the interrupt control available while drafting a follow-up during an active run.
  • Guarded queued attachment hydration so a missing previewUrl now fails explicitly instead of fetch("")ing the current page.
  • Switched the queued-attachment ws test back to makeTempDir(...) and added retrying cleanup to avoid the Windows EPERM teardown issue.
  • Updated the queued projector handlers to decode their payloads through decodeForEvent(...) for consistency with the rest of the projector error-handling path.
  • Added/updated browser coverage for the stop button staying available while typing a running follow-up.

Validation rerun after these fixes:

  • bun fmt
  • bun lint
  • bun typecheck
  • bun run test -- src/wsServer.test.ts src/orchestration/projector.test.ts
  • bun run test:browser -- src/components/ChatView.browser.tsx --testNamePattern "shows a pointer cursor for the running stop button|keeps the running stop button available while drafting a follow-up|queued|follow-up|steer|Ctrl\\+Shift\\+Enter|Cmd\\+Shift\\+Enter"
  • bun run test -- src/components/ChatView.logic.test.ts src/composerDraftStore.test.ts src/store.test.ts

@leonardoxr leonardoxr marked this pull request as ready for review March 28, 2026 21:21
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 775ee4286c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cace8fccce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4587c9d87

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 06d68cdfaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46f95dac9d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d92e28bc8e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

}),
),
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double dispatch when both remove and send-failed fail

Medium Severity

When thread.turn.start succeeds but the subsequent thread.queued-follow-up.remove dispatch fails, the reactor tries to record a send-failed event. If that nested dispatch also fails, the error is swallowed by Effect.catchCause. The queued follow-up remains in the queue without a lastSendError marker. Once the dispatched turn completes, hasQueuedDispatchSettled returns true, the pendingQueuedDispatchByThreadId entry is cleared, and canDispatchQueuedFollowUp sees no error — so the reactor dispatches the same follow-up again, creating a duplicate turn.

Additional Locations (1)
Fix in Cursor Fix in Web


it.live("replays queued follow-ups after orchestration restarts", () =>
Effect.gen(function* () {
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-orchestration-queue-restart-"));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Integration test leaks temp directory on disk

Low Severity

The rootDir created via fs.mkdtempSync is never cleaned up. When rootDir is provided externally to makeOrchestrationIntegrationHarness, the scoped temp-directory cleanup from makeTempDirectoryScoped is skipped, and the harness dispose does not remove it. Each test run accumulates an orphaned temp directory under the OS temp folder.

Fix in Cursor Fix in Web

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f049043dd4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

})),
);
return buildQueuedFollowUpDraft({
prompt: trimmedPrompt,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve inline terminal markers in follow-up snapshots

createFollowUpSnapshotFromComposer stores trimmedPrompt, which is derived via deriveComposerSendState and strips inline terminal-context placeholders. In running queue/steer flows this means prompts that referenced terminal context inline lose those markers before dispatch, so the sent text differs from normal send behavior and can change the user's intent (e.g., inline references disappear while only the trailing context block remains). Use a prompt representation that preserves/materializes inline context markers before snapshotting.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Add configurable follow-up behavior while a turn is running

2 participants