Files
orca/src/shared/runtime-mobile-session-tab-contracts.ts
T
Neil ddcedee37c fix(remote): a desktop no longer serves other runtimes its tabs on its servers' files (#33424) (#33752)
* fix(remote): a desktop ignores its own workspaces echoed back by a server it serves (#33424)

When two desktops pair to each other, each server republishes the tabs it
mirrors from its own servers. Desktop A then received B's mirror of A's own
workspace under the real workspace id and grafted it onto its local copy as
B-hosted tabs, so opening those files asked B (selector_not_found) and closing
them closed B's tabs.

A session-tabs frame from a paired server no longer writes a workspace this
app lists as its own (a stamped local or direct SSH row) unless that server
also lists a copy. Other servers' workspaces stay open to every runtime.

* fix(remote): retire echoes already taken and replay frames a twin's late row releases

An echo fenced out of this app's own workspace now applies as that server's
tombstone, so rows it left before the fence (restored sessions, or frames that
arrived before this app's own row listed) are retired without closing anything.
A held frame replays once the server's own copy of the workspace lists, so a
same-id twin that publishes before its row loads still mirrors. The e2e waits
on a later frame from the same stream instead of a fixed delay.

* fix(remote): serve a desktop's tabs on its servers' files only to its phone, not to other runtimes

Redesign of the #33424 fix at the source. A desktop publishes every editor tab in
its window, including ones on files of its paired servers; its phone uses those
through the desktop's relay. Served to another runtime, such a tab routes the file
to the desktop, and in a pairing cycle it is that runtime's own workspace coming
back as the desktop's tab: file reads fail with selector_not_found and closing it
closes the desktop's real tab.

Publishers now stamp those rows with hostRuntimeEnvironmentId (window, layout-owner
main projection and the windowless projection alike). projectSessionTabsForClient
strips the stamp for every caller and drops the stamped rows for runtime-scope
callers, keeping groups, layout and the active tab consistent. Phones and local
callers see what they saw before. The receiver-side fence is removed.

* refactor(remote): keep the file-host stamp helper outside the old-client boundary; test an all-server snapshot
2026-10-25 20:28:07 -07:00

148 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { AgentStatusEntry } from './agent-status-types'
import type { BrowserCertificateFailure, BrowserLoadError } from './browser-workspace-types'
import type { RuntimeBrowserPlacement } from './runtime-browser-placement'
import type { TerminalColorOverrides } from './terminal-color-overrides'
import type { TerminalLayoutSnapshot, TerminalTab } from './terminal-tab-types'
import type { TuiAgent } from './tui-agent'
import type { StructuredAgentId } from './agent-session-provider-handle'
export type RuntimeMobileSessionTerminalTab = {
type: 'terminal'
id: string
title: string
/** Host manual title; absent on old hosts preserves the client record, whereas null clears it. */
customTitle?: string | null
quickCommandLabel?: string | null
/** Window-to-main names ranked above live titles; generatedTitle is sent only when enabled. */
aiVaultTitle?: TerminalTab['aiVaultTitle']
generatedTitle?: string | null
parentTabId: string
leafId: string
ptyId?: string | null
/** Host-owned PTY incarnation used to fence remote identity observations. */
incarnationId?: string | null
terminalTheme?: RuntimeMobileTerminalTheme
agentStatus?: AgentStatusEntry | null
/** Event-only lead-turn end time for paired clients; never persisted in AgentStatusEntry. */
turnCompletedAt?: number
launchAgent?: TuiAgent
startupCwd?: string
parentLayout?: TerminalLayoutSnapshot
color?: string | null
isPinned?: boolean
viewMode?: 'terminal' | 'chat'
launchDraft?: string
launchDraftCreatedAt?: number
isActive: boolean
}
export type RuntimeMobileTerminalTheme = {
mode: 'dark' | 'light'
theme: TerminalColorOverrides
/** Optional desktop contrast override (#10754); absent uses the client’s background-luminance floor. */
minimumContrastRatio?: number
}
export type RuntimeMobileSessionMarkdownTab = {
type: 'markdown'
id: string
title: string
filePath: string
relativePath: string
language: 'markdown'
mode: 'edit' | 'markdown-preview'
isDirty: boolean
isActive: boolean
sourceFileId: string
sourceFilePath: string
sourceRelativePath: string
/** Set by a desktop for a "New Markdown" document not yet named; hosts and phones before it omit it. */
isUntitled?: true
documentVersion: string
color?: string | null
isPinned?: boolean
/** The paired server the file lives on. Its publisher sets it; runtimes strip it before serving. */
hostRuntimeEnvironmentId?: string
}
export type RuntimeMobileSessionFileTab = {
type: 'file'
id: string
title: string
filePath: string
relativePath: string
language: string
mode?: 'edit' | 'diff'
diffSource?: 'staged' | 'unstaged'
isDirty: boolean
color?: string | null
isPinned?: boolean
isActive: boolean
/** The paired server the file lives on. Its publisher sets it; runtimes strip it before serving. */
hostRuntimeEnvironmentId?: string
}
export type RuntimeMobileSessionBrowserTab = {
type: 'browser'
id: string
title: string
browserWorkspaceId: string
browserPageId: string | null
browserProfileId?: string
executionHostKey?: string
placement?: RuntimeBrowserPlacement
url: string
loading: boolean
canGoBack: boolean
canGoForward: boolean
loadError?: BrowserLoadError | null
certificateFailure?: BrowserCertificateFailure | null
color?: string | null
isPinned?: boolean
isActive: boolean
}
export type RuntimeMobileSessionAgentTab = {
type: 'agent-session'
id: string
title: string
sessionId: string
replacesSessionId?: string
/** Host-registered agent; gate names beyond Claude/Codex on client registered-agents capability. */
agent: StructuredAgentId
/** The host's structured status row for this chat. Optional: old hosts never send it. */
chatStatus?: RuntimeMobileSessionAgentTabStatus
color?: string | null
isPinned?: boolean
isActive: boolean
}
export type RuntimeMobileSessionAgentTabStatus = Pick<
AgentStatusEntry,
| 'state'
| 'workingMode'
| 'interrupted'
| 'mainAgent'
| 'updatedAt'
| 'evidenceObservedAt'
| 'structuredHostOwned'
>
export type RuntimeMobileSessionSnapshotTab =
| RuntimeMobileSessionTerminalTab
| RuntimeMobileSessionMarkdownTab
| RuntimeMobileSessionFileTab
| RuntimeMobileSessionBrowserTab
| RuntimeMobileSessionAgentTab
export type RuntimeMobileSessionTerminalClientTab =
| (RuntimeMobileSessionTerminalTab & { status: 'pending-handle'; terminal: null })
| (RuntimeMobileSessionTerminalTab & { status: 'ready'; terminal: string })
export type RuntimeMobileSessionClientTab =
| RuntimeMobileSessionTerminalClientTab
| RuntimeMobileSessionMarkdownTab
| RuntimeMobileSessionFileTab
| RuntimeMobileSessionBrowserTab
| RuntimeMobileSessionAgentTab