fix(slack): render agent markdown + drop stale pill on Claude end_turn#84
Open
rogeriochaves wants to merge 6 commits into
Open
fix(slack): render agent markdown + drop stale pill on Claude end_turn#84rogeriochaves wants to merge 6 commits into
rogeriochaves wants to merge 6 commits into
Conversation
Claude and Codex emit GitHub-flavoured Markdown in their assistant prose (`**bold**`, `[label](url)`, `# heading`, `*italic*`, …). Slack renders the `text` field as mrkdwn by default, but Slack mrkdwn is a different dialect — `*x*` is bold, `_x_` is italic, headings don't exist, links look like `<url|label>`. So today the agent's `**bold**` shows up in Slack as literal asterisks around the word; lists, headings and links are similarly broken. Add `gfmToSlackMrkdwn(text)` and call it on assistant-prose paths in both the Claude transcript formatter and the Codex rollout formatter (`agent_message`). Translation covers bold (`**` and `__`), italic (`*` → `_`), strikethrough, ATX headings, and links. Fenced code blocks and inline code spans are preserved verbatim so Markdown-looking content inside code stays untranslated. The static `:warning: Codex is out of credits…` post is intentionally unchanged — it isn't agent output, just a fixed string we author. Unit-tested round-trips, ordering pitfalls (bold-before-italic so `**x**` doesn't get mis-italicised), and code-block / inline-code preservation.
…turn) After PR #82 only the codex out-of-credits sentinel set `terminal: true`, so the bridge always re-attached the "is working…" pill after every non-terminal text post. For Claude that meant the pill stayed up forever after the agent posted its final message and ended the turn — exactly what you see in the screenshot where the agent's review is posted but the channel still shows a pill below. When a transcript-line message carries `stop_reason: "end_turn"` (or `stop_sequence` / `refusal`), find the last text post we just emitted from that message and mark it terminal. The bridge already honours that flag by skipping the pill re-attach. `tool_use` / `max_tokens` / `pause_turn` stay non-terminal — the agent is still working. If a turn ends with no text post (e.g. the last block was a tool call), we leave the pill alone — that's the existing behaviour; rare in practice, and dropping a pill mid-tool would be more confusing than the two-minute Slack TTL it relies on otherwise.
The pre-Escape sleep was 100ms. That's long enough for the kanban CLI to print its confirmation and exit, but not always long enough for Claude Code to finish processing the Bash tool result and return its prompt to a state that accepts `/compact` as a slash command. Symptom: the detached shell does Escape -> /compact -> Enter as designed, but Claude has not yet redrawn the prompt, so the paste lands in a transitional state and the compact never actually fires — the session just sits with the interrupt acknowledgement and never compacts. Bump the pre-Escape sleep to 2s. The follow-up note still uses the caller-provided delay (default 1s), unchanged. Worst case the operator sees a 2s window between calling self-compact and Claude actually showing /compact; in practice the agent that ran self-compact is already done speaking, so the extra latency is invisible.
The pill only went up once the assistant posted its first text reply, which left the channel looking idle for the 10–60s+ between someone typing in Slack and the agent's first observable output (tool calls happen first, and tool/thinking blocks are buffered rather than posted top-level). Operators reported "I don't get the badge after sending my prompt, only after the first reply arrives". Anchor the pill to the user's own Slack message ts immediately after `pasteTmuxPrompt`, so the channel reflects "agent is processing this" as soon as the relay lands. The existing prevPill-clear path moves the pill to the assistant's reply the moment it arrives, and the end_turn terminal flag drops it cleanly at the end of the turn. Codex/kanban-CLI nudges already get a pill via the announce path — this only fixes the human-typed-in-Slack case which had no equivalent.
Previous commit bumped the detached self-compact shell's pre-Escape sleep from 100ms to 2s. The self-compact CLI tests waited only 0.8s / 0.4s before reading the fake-tmux log file, so the assertions ran before the shell had reached the `send-keys Escape` step and saw only the earlier `display-message -p #S` line. Bump to 2.8s / 2.4s.
…er prompt" This reverts commit 15fb4a9.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Unit tests cover the round-trip and the tricky ordering pitfall (must replace `bold` BEFORE the single-`*` italic pass, otherwise the intermediate `bold` gets re-mangled into `bold`).
Test plan