From 8a990dcd67e3a2e0947470dd0f1c40a30f90e1d6 Mon Sep 17 00:00:00 2001 From: DoodleBears Date: Thu, 22 Oct 2026 00:20:42 +0900 Subject: [PATCH 1/8] feat(client): project remote row activity and unread Issue: #2500 Agent-Task: codex/local/01a15ee9-7784-7292-9194-6a6b18b23098 --- .../features/remote-hosts.md | 6 +- changes/2500-remote-activity-unread.md | 11 ++ packages/client/src/client/bot-sidebar.tsx | 32 +---- packages/client/src/client/host-tabs.tsx | 9 +- packages/client/src/client/remote-avatar.tsx | 7 +- .../src/client/remote-conversation-read.ts | 38 ++++++ .../src/client/remote-conversation-view.tsx | 13 +- .../client/src/client/remote-host-activity.ts | 74 ++++++++++++ packages/client/src/client/remote-hosts.ts | 25 +++- packages/client/src/client/unread-count.tsx | 32 +++++ .../test/remote-conversation-read.test.ts | 111 ++++++++++++++++++ .../client/test/remote-host-activity.test.ts | 58 +++++++++ .../client/test/remote-host-rows.test.tsx | 81 +++++++++++++ packages/client/test/remote-hosts.test.ts | 33 ++++++ 14 files changed, 496 insertions(+), 34 deletions(-) create mode 100644 changes/2500-remote-activity-unread.md create mode 100644 packages/client/src/client/remote-conversation-read.ts create mode 100644 packages/client/src/client/remote-host-activity.ts create mode 100644 packages/client/src/client/unread-count.tsx create mode 100644 packages/client/test/remote-conversation-read.test.ts create mode 100644 packages/client/test/remote-host-activity.test.ts create mode 100644 packages/client/test/remote-host-rows.test.tsx diff --git a/.agents/skills/verify-deepseekbot/features/remote-hosts.md b/.agents/skills/verify-deepseekbot/features/remote-hosts.md index a640a16e39..305db01512 100644 --- a/.agents/skills/verify-deepseekbot/features/remote-hosts.md +++ b/.agents/skills/verify-deepseekbot/features/remote-hosts.md @@ -8,6 +8,8 @@ The browser pairs a Remote Host as a Paired Device and reads its Host Feed throu - `tabs`: the icon strip filters All, This host and each Remote Host; tooltips name the Host, and Chinese uses 全部、本机、远程. - `open`: a remote row opens a Host-scoped conversation. - `avatars`: remote Bot portraits and group images/member stacks appear in rows and conversation headers; Bot message authors and live drafts use the same remote portrait. +- `activity-unread`: remote Bot rows display received activity; DM/group unread counts come from their Host Inbox. +- `read`: a visible remote conversation at its latest message marks that message read on its owning Host and refreshes the shared Inbox. - `upgrade`: existing browser pairings and the selected tab survive the storage-key rename. - `remove`: removing a Host forgets its rows, stops its Inbox and releases cached avatar object URLs. @@ -25,7 +27,9 @@ For avatars, give Host B one uploaded Bot image, one seeded or composed Bot, a g ## Gotchas -- Row activity/unread remains a separate slice. Remote avatars consume Track 0's authenticated image cache; This Host retains its current read adapter. +- Remote avatars consume the authenticated image cache; This Host retains its current read adapter. +- With unread messages on both Hosts, compare each row and tab with `channelUnreadCounts`. Open the remote DM/group, verify its count clears on that Host while the other Host stays unread. Keep an active Bot running to inspect its activity indicator. Stop only the task-owned remote Host and verify cached rows/counts remain with an offline tab. +- Activity timestamps are receipt times; they are not authoritative tool start times. - Remote chat displays text and attachment names; images and cards are not qualified. - Host looks remain device-local, including each phone's own icon and colour. DSH Desktop and two physical machines over a real tailnet are not qualified by a two-Host local run. - Dismiss Continue and Skip onboarding overlays before selecting Messages. Retain any failed drive evidence before retrying. diff --git a/changes/2500-remote-activity-unread.md b/changes/2500-remote-activity-unread.md new file mode 100644 index 0000000000..144fa0b7a1 --- /dev/null +++ b/changes/2500-remote-activity-unread.md @@ -0,0 +1,11 @@ +--- +section: Added +--- + +## en + +- Remote Host chats now show Bot activity and unread counts, reading a remote conversation updates its own Host's read position ([#2500](https://github.com/BotHarness/DeepSeekBot/issues/2500)). + +## zh + +- 远程会话列表现在显示 Bot 活动状态和未读数,阅读远程会话会更新其所属 Host 的已读位置([#2500](https://github.com/BotHarness/DeepSeekBot/issues/2500))。 diff --git a/packages/client/src/client/bot-sidebar.tsx b/packages/client/src/client/bot-sidebar.tsx index be330a07ea..03dd637567 100644 --- a/packages/client/src/client/bot-sidebar.tsx +++ b/packages/client/src/client/bot-sidebar.tsx @@ -1,3 +1,5 @@ +import { UnreadCount } from './unread-count.js'; +export { unreadCountLabel } from './unread-count.js'; import { Menu } from './appearance-primitives.js'; import { ALL_HOSTS, remoteHosts } from './remote-hosts.js'; import { useChannelDockActive } from './channel-dock.js'; @@ -515,32 +517,6 @@ function ChannelMessageTime({ ); } -export function unreadCountLabel(count: number): string { - return count > 99 ? '99+' : String(count); -} - -function UnreadCount({ - count, - muted = false, - corner = false, - t, -}: { - count: number; - muted?: boolean; - corner?: boolean; - t: BotHarnessTranslate; -}): ReactElement | null { - if (count <= 0) return null; - return ( - - {unreadCountLabel(count)} - - ); -} - function MutedMark({ t }: { t: BotHarnessTranslate }): ReactElement { return ( @@ -2213,7 +2189,7 @@ export function BotSidebar({ {headerTarget ? createPortal({header}, headerTarget) : header} - +
{header}, headerTarget) : header} - {hostTabs ? : null} + {hostTabs ? : null} {state.status === 'loading' && state.bots.length === 0 ? ( ) : null} diff --git a/packages/client/src/client/host-tabs.tsx b/packages/client/src/client/host-tabs.tsx index 779d89092c..a12e921712 100644 --- a/packages/client/src/client/host-tabs.tsx +++ b/packages/client/src/client/host-tabs.tsx @@ -3,6 +3,7 @@ import * as stylex from '@stylexjs/stylex'; import type { RemoteHost, RemoteConversationTarget } from './remote-hosts.js'; import { ALL_HOSTS, remoteHosts, THIS_HOST } from './remote-hosts.js'; +import { UnreadCount } from './unread-count.js'; import { ChannelSidebarIcon } from './channel-sidebar-icon.js'; import { RemoteAvatar } from './remote-avatar.js'; import { ConfirmDialog, FormDialog } from './ds/form-dialog.js'; @@ -559,18 +560,21 @@ export function AddRemoteHostDialog({ function rows(remote: RemoteHost): (RemoteConversationTarget & { key: string; + unread: number; preview?: string; previewAt?: string; })[] { const roster = remote.snapshot.roster; if (roster === undefined) return []; const hostId = remote.host.hostId; + const unread = remote.inbox.status === 'ready' ? (remote.inbox.unread ?? {}) : {}; return [ ...roster.bots.map((bot) => ({ key: `bot:${bot.slug}`, hostId, title: bot.displayName, botSlug: bot.slug, + unread: bot.dmChannelId === undefined ? 0 : (unread[bot.dmChannelId] ?? 0), ...(bot.preview === undefined ? {} : { preview: bot.preview }), ...(bot.previewAt === undefined ? {} : { previewAt: bot.previewAt }), })), @@ -579,6 +583,7 @@ function rows(remote: RemoteHost): (RemoteConversationTarget & { hostId, title: channel.name, channelId: channel.id, + unread: unread[channel.id] ?? 0, ...(channel.preview === undefined ? {} : { preview: channel.preview }), ...(channel.previewAt === undefined ? {} : { previewAt: channel.previewAt }), })), @@ -623,7 +628,7 @@ export function RemoteHostRows({