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/.github/pr-screenshots/2500-t2/a-d.png b/.github/pr-screenshots/2500-t2/a-d.png new file mode 100644 index 0000000000..579bac25d4 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/a-d.png differ diff --git a/.github/pr-screenshots/2500-t2/a-l.png b/.github/pr-screenshots/2500-t2/a-l.png new file mode 100644 index 0000000000..45af862a83 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/a-l.png differ diff --git a/.github/pr-screenshots/2500-t2/b-d.png b/.github/pr-screenshots/2500-t2/b-d.png new file mode 100644 index 0000000000..c6814bcaaf Binary files /dev/null and b/.github/pr-screenshots/2500-t2/b-d.png differ diff --git a/.github/pr-screenshots/2500-t2/b-l.png b/.github/pr-screenshots/2500-t2/b-l.png new file mode 100644 index 0000000000..d12b93fb60 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/b-l.png differ diff --git a/.github/pr-screenshots/2500-t2/c-d.png b/.github/pr-screenshots/2500-t2/c-d.png new file mode 100644 index 0000000000..b543637d42 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/c-d.png differ diff --git a/.github/pr-screenshots/2500-t2/c-l.png b/.github/pr-screenshots/2500-t2/c-l.png new file mode 100644 index 0000000000..57126efd94 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/c-l.png differ diff --git a/.github/pr-screenshots/2500-t2/i-d.png b/.github/pr-screenshots/2500-t2/i-d.png new file mode 100644 index 0000000000..c0598d4d6b Binary files /dev/null and b/.github/pr-screenshots/2500-t2/i-d.png differ diff --git a/.github/pr-screenshots/2500-t2/i-l.png b/.github/pr-screenshots/2500-t2/i-l.png new file mode 100644 index 0000000000..913727154b Binary files /dev/null and b/.github/pr-screenshots/2500-t2/i-l.png differ diff --git a/.github/pr-screenshots/2500-t2/j-d.png b/.github/pr-screenshots/2500-t2/j-d.png new file mode 100644 index 0000000000..88326f2c70 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/j-d.png differ diff --git a/.github/pr-screenshots/2500-t2/j-l.png b/.github/pr-screenshots/2500-t2/j-l.png new file mode 100644 index 0000000000..aab194f2c1 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/j-l.png differ diff --git a/.github/pr-screenshots/2500-t2/k-d.png b/.github/pr-screenshots/2500-t2/k-d.png new file mode 100644 index 0000000000..5ddc6fe893 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/k-d.png differ diff --git a/.github/pr-screenshots/2500-t2/k-l.png b/.github/pr-screenshots/2500-t2/k-l.png new file mode 100644 index 0000000000..53f66145c0 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/k-l.png differ diff --git a/.github/pr-screenshots/2500-t2/o-d.png b/.github/pr-screenshots/2500-t2/o-d.png new file mode 100644 index 0000000000..e966d5fcf7 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/o-d.png differ diff --git a/.github/pr-screenshots/2500-t2/o-l.png b/.github/pr-screenshots/2500-t2/o-l.png new file mode 100644 index 0000000000..1e89babfdd Binary files /dev/null and b/.github/pr-screenshots/2500-t2/o-l.png differ diff --git a/.github/pr-screenshots/2500-t2/r-d.png b/.github/pr-screenshots/2500-t2/r-d.png new file mode 100644 index 0000000000..2e28efc866 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/r-d.png differ diff --git a/.github/pr-screenshots/2500-t2/r-l.png b/.github/pr-screenshots/2500-t2/r-l.png new file mode 100644 index 0000000000..fb96e1ecf1 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/r-l.png differ diff --git a/.github/pr-screenshots/2500-t2/w-d.png b/.github/pr-screenshots/2500-t2/w-d.png new file mode 100644 index 0000000000..52843c7c54 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/w-d.png differ diff --git a/.github/pr-screenshots/2500-t2/w-l.png b/.github/pr-screenshots/2500-t2/w-l.png new file mode 100644 index 0000000000..0ba06976e1 Binary files /dev/null and b/.github/pr-screenshots/2500-t2/w-l.png differ 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..b46f5283de 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 ( 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({