Improve Slack UX: setStatus on request received, skip on_flow_started ack, persist plan block

What does this MR do and why?

Improves the Duo Slack response UX, building on the live progress streaming work in !242201 (merged):

1. on_request_received: Rotating status instead of a static message

Before: Posted a static markdown message ("On it! Working on your request.") into the thread immediately.

After: Calls assistant.threads.setStatus with rotating loading strings. This gives immediate animated feedback without cluttering the thread with a static message. The status clears automatically when the first real message is posted.

The loading strings describe the whole run rather than just agent startup ("is working on your request…", "is thinking it through…"), because when the agent produces no todo list and no monologue this status is the only progress signal the user sees before the final answer. They are built in a method (not a frozen constant) so translations resolve dynamically.

set_status is restored in lib/slack/api.rb (it was removed in !242201 (merged)).

2. on_flow_started: Skip the ack message — keep the status spinning

Before: Posted (or fell back to posting) an acknowledgement message with the session footer link.

After: Only persists session_url in the callback context. No message is posted. The rotating status indicator keeps spinning until on_progress posts the first real content (plan block or agent monologue). This keeps the experience in Slack for short tasks rather than pushing the user to GitLab.

As a fallback, if on_progress never fires (no todo list and no monologue), deliver_result posts the final answer directly. The session URL is persisted unconditionally so the footer is always available.

3. on_progress: Post the first real content, persist status_ts

After:

  • When no status_ts exists yet, on_progress posts a new message with the first real content (the status indicator clears automatically when a message is posted in the thread) and records its ts.
  • ProgressDeliveryWorker now persists status_ts alongside the progress cursor, so the next progress tick edits the existing message instead of posting a duplicate.

4. deliver_result: Rebuild the plan block from the terminal checkpoint

Before: deliver_result replaced the entire message with just the final answer, wiping the plan block.

After:

  • deliver_result now receives the workflow and reads the final todo list from Ai::DuoWorkflows::Workflow#latest_ui_chat_log (the cumulative ui_chat_log of the latest checkpoint).
  • It prepends a freshly built plan block (if the run had a todo list) before the final answer, so users can inspect the task list after the run.

Building the plan from the terminal checkpoint (rather than freezing the last on_progress render in the callback context) keeps completed tasks from being shown as in_progress in the final message: the final todo_write (all complete) lands in the terminal checkpoint after live progress stops, so reading it here keeps the plan in sync with the final answer. This also removes the stale-state persistence path (last_plan_block).

Workflow#latest_ui_chat_log is added so the messaging workers and adapters read the final conversation state without reaching into the checkpoints association themselves; CallbackWorker#extract_final_message now takes the chat log directly.

References

Screenshots or screen recordings

Before After
Static "On it! Working on your request." message posted immediately Rotating status indicator ("is working on your request…" etc.) shown immediately
Plan block wiped when final answer is delivered Plan block (rebuilt from the terminal checkpoint) preserved above final answer

How to set up and validate locally

  1. Configure a Slack integration with a Duo Developer workflow.
  2. Trigger a workflow by @mention-ing the app with something like "can you simulate a small task with 3 todos? Think out loud in between when you work through your tasks."
  3. Watch the thread:
    • You should see the rotating status indicator immediately (no static message).
    • When the agent starts working, the first real content (plan or monologue) appears as a message.
    • When the workflow finishes, the final answer appears with the plan block above it (if the agent used a todo list), with all tasks shown in their final state.
    • For a short task with no todo list and no monologue, the status spins until the final answer is posted directly.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Thomas Schmidt

Merge request reports

Loading
Loading