diff --git a/.agents/skills/verify-deepseekbot/features/remote-hosts.md b/.agents/skills/verify-deepseekbot/features/remote-hosts.md index 305db01512..d3193d30b3 100644 --- a/.agents/skills/verify-deepseekbot/features/remote-hosts.md +++ b/.agents/skills/verify-deepseekbot/features/remote-hosts.md @@ -12,10 +12,12 @@ The browser pairs a Remote Host as a Paired Device and reads its Host Feed throu - `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. +- `viewers`: a remote Bot DM opens Browser or Computer when that Bot has access. The shared Host viewer modules read frames and send input to that Host only; closing, switching source or losing the connection tears down the viewer and releases requested control. +- `viewer-control`: queued input belongs to one takeover. Resume or frame loss invalidates it; frame recovery remains read-only even when the Host Feed never disconnected. Remove closes viewers and waits up to five seconds for takeover/release cleanup before revoking the pairing. ## How to get to it (user POV) -Enter Bot mode, then Messages. Open the channel header's add menu and choose Add remote. Paste a link from the other Host's Settings → Devices → Pair a phone. Choose All or the Remote Host icon, then a Bot or group row. Right-click the Host icon and choose Remove to remove it. +Enter Bot mode, then Messages. Open the channel header's add menu and choose Add remote. Paste a link from the other Host's Settings → Devices → Pair a phone. Choose All or the Remote Host icon, then a Bot or group row. Right-click the Host icon and choose Remove to remove it. A remote Bot DM with Browser or Computer access shows the corresponding header button. Open its viewer, choose Pause Bot and take control, interact with the screen or send text, then Resume Bot or Close viewer. Groups have no viewer entry. ## Driving it with verify-app @@ -25,11 +27,19 @@ Read the browser console and run `node scripts/verify-app.mjs observe --name (); + const viewerClient = sources.viewerClient(target.hostId); + const bot = + target.channelId === undefined + ? remote?.snapshot.roster?.bots.find((entry) => entry.slug === target.botSlug) + : undefined; const labels = useMemo( () => ({ code: { copyLabel: t('message.code.copy'), copiedLabel: t('message.code.copied') }, @@ -228,105 +236,150 @@ export function RemoteConversationView({ {remote?.host.hostName ?? ''} + {bot?.browserAccess ? ( + setViewer(viewer === 'browser' ? undefined : 'browser')} + > + + + ) : null} + {bot?.computerAccess ? ( + setViewer(viewer === 'computer' ? undefined : 'computer')} + > + + + ) : null} sources.close()}> - {!reachable ? ( -

- {t('remoteHosts.offline', { name: remote?.host.hostName ?? '' })} -

- ) : snapshot.status === 'interrupted' || snapshot.status === 'refused' ? ( -

- {snapshot.reason ?? t('remoteHosts.interrupted')} -

- ) : null} -
- {snapshot.drafts.map((entry) => ( -
- - - {bots.get(entry.botSlug) ?? entry.botSlug} - -
- -
+ {viewer !== undefined && + viewerClient !== undefined && + bot !== undefined && + (viewer === 'browser' ? bot.browserAccess : bot.computerAccess) ? ( + setViewer(undefined)} + /> + ) : ( + <> + {!reachable ? ( +

+ {t('remoteHosts.offline', { name: remote?.host.hostName ?? '' })} +

+ ) : snapshot.status === 'interrupted' || snapshot.status === 'refused' ? ( +

+ {snapshot.reason ?? t('remoteHosts.interrupted')} +

+ ) : null} +
+ {snapshot.drafts.map((entry) => ( +
+ + + {bots.get(entry.botSlug) ?? entry.botSlug} + +
+ +
+
+ ))} + {[...shown].reverse().map((message) => ( +
+ + {message.author === 'bot' && message.bot !== undefined ? ( + + ) : null} + {authorName(message, bots, t)} + +
+ attachment.name).join(', ') + } + streaming={false} + labels={labels} + /> +
+ {message.delivery === 'failed' ? ( + + {message.error ?? t('remoteHosts.sendFailed')} + + ) : null} +
+ ))}
- ))} - {[...shown].reverse().map((message) => ( -
{ + event.preventDefault(); + send(); + }} > - - {message.author === 'bot' && message.bot !== undefined ? ( - - ) : null} - {authorName(message, bots, t)} - -
- attachment.name).join(', ') - } - streaming={false} - labels={labels} - /> -
- {message.delivery === 'failed' ? ( - - {message.error ?? t('remoteHosts.sendFailed')} - - ) : null} -
- ))} -
-
{ - event.preventDefault(); - send(); - }} - > -