feat(channels): stream agent turns into threads, read Activity per mention#3534
feat(channels): stream agent turns into threads, read Activity per mention#3534adamleithp wants to merge 2 commits into
Conversation
…ntion Agent turns and permission asks now land in a channel thread as messages, and Activity marks a mention read when you open its thread rather than when you open the page. Thread messages: - Render agent rows from `author_kind`/`event`/`payload` instead of `author`, which is null by design on agent rows and read as "Unknown". - Drop the durable `turn_complete` copy of a run the viewer is already watching stream, so the live turn doesn't get swapped at the end. - Break an agent's turn into paragraphs at tool-call boundaries instead of gluing every chunk into one clump. - Frame long bodies in a capped, bordered scroll window that opens on the last line and, while streaming, follows the output. Activity: - Open a mention's thread beside the list instead of navigating to the task. - Track read per mention, so the badge survives a glance at the page. - Highlight the row whose thread is open. - Count a task's thread as "being viewed" when its panel is open, so a notification doesn't announce what's already on screen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
|
Reviews (1): Last reviewed commit: "feat(channels): stream agent turns into ..." | Re-trigger Greptile |
| const threadChannelId = threadTaskId | ||
| ? folderChannelIdFor( | ||
| items.find((item) => item.taskId === threadTaskId)?.channelName ?? null, | ||
| ) |
There was a problem hiding this comment.
Selected Mention Loses Its Channel
When the same task has mentions in multiple channels, this lookup takes the channel from the first matching feed item rather than the mention that opened the panel. Clicking another mention can therefore give ThreadSidebar the wrong channel ID and make “Open full” navigate to the wrong channel.
| storage: electronStorage, | ||
| // Sets don't survive JSON; store the ids as an array and rebuild on load. |
There was a problem hiding this comment.
If a mention is opened before the asynchronous persisted state finishes loading, electronStorage drops writes during that first-read window and hydration can replace the in-memory set with the saved IDs. The sidebar updates for the current session, but the mention becomes unread again after restart.
The committed lockfile was generated without pnpm-workspace.yaml's overrides applied, so it dropped the top-level overrides:/patchedDependencies: blocks and resolved overridden deps to their un-overridden versions (plain vite instead of rolldown-vite, undici 6.27.0 instead of the pinned 8.4.1, node-abi 4.33.0 instead of 3.92.0). pnpm install --frozen-lockfile rejected this with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH, failing every CI job at the install step. Regenerated with pnpm install --lockfile-only so the lockfile matches the workspace config again. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LakkSu5zcYbTzMKwurvBD4
Agent turns and permission asks now land in a channel thread as messages you can read in place, and Activity treats a mention as read when you open its thread — not when you glance at the page.
Thread messages
author_kind: "agent", noauthor), so anything resolvingauthoralone named them "Unknown". They now render fromauthor_kind+event+payload, with a robot avatar.TaskThreadMessagecarries the agent's shape. Added optionalauthor_kind,event,payloadto the shared type, plus aTaskThreadMessageEventunion (turn_complete|permission_request). Optional so older payloads still parse; absent means human, which is what every message was before agents could post.turn_completerow keyed bypayload.run_id.visibleThreadMessagesdrops that copy for a run the viewer is already streaming — the streamed turn is the one they watched arrive, and swapping it for the server's copy at the end made the message jump. A row with no run id is kept: a possible duplicate beats dropping the only copy.ThreadScrollBody, built on quill'sChatStream): capped height, scrolled for the rest, so one essay doesn't push the next row off screen. It follows live output while streaming, and opens on the last line once finished. The border lives on the wrapper, not the stream —ChatStream's mask would otherwise eat a border on its own element exactly when there's more to read.Activity
isMentionUnread/countUnreadMentionsreplace the global watermark;lastSeenAtstays as a read-only historical watermark so the switch doesn't resurface old mentions as unread.bg-fill-selected). Keyed on the mention, not the task — the panel tracks a task, and a task can be mentioned in several rows, so keying on it alone lit all of them for one click.Setdoesn't round-trip through JSON, so it's stored as an array and rebuilt on load.Notifications
activeNotificationTargetnow considers the panel — a collapsed panel is a rail with nothing legible in it, so it doesn't count. The host'sgetActiveTargetis thin glue over it.Notes
@posthog/quill0.3.0-beta.24→0.3.0-beta.25forChatStream.permission_requestrows render as text; their Approve/Deny actions aren't wired yet.TaskMentionDTOcarries noevent/payload/author_kind, so the same missing fields are why an agent's mention still shows as "Unknown" in the list. Needs a../posthogchange; not in this PR.main, unrelated and untouched here:@posthog/gitdetectDefaultBranch > prefers remote HEAD over local detection.Testing
pnpm typecheck,pnpm lint,node scripts/check-host-boundaries.mjs(no new violations) all clean.pnpm test: green except the pre-existing@posthog/gitfailure above.visibleThreadMessages/isAgentThreadMessage(threadTimeline),isMentionUnread/countUnreadMentions(mentionActivity), tool-call turn breaks (threadAgentTurns),activeNotificationTarget.🤖 Generated with Claude Code