Files
orca/src/preload/api/app-api.ts
T
Brennan Benson 757736628f fix(native-chat): a paired server admits structured chat by client capability, not its own chat setting (#24203)
* fix(native-chat): a host admits structured sessions by client capability, not its own chat setting

A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.

The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.

The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).

* fix(native-chat): negotiate client-chosen launch mode so released phones and old servers keep terminals

Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.

* chore(native-chat): justify the two type assertions this change's lines touch

* fix(native-chat): chats that already exist keep showing whatever the chat setting says

The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.

* test(native-chat): pin that a host advertises the client-chosen launch mode

* fix(native-chat): mirror this machine's chats only where it holds them

Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.

The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.

* test(native-chat): record install listeners without a cast

* fix(native-chat): mirror this machine's chats only once it holds one, not once its host is built

Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.

* feat(native-chat): createSupport reports the saved selection a new chat on this host starts with

A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.

* refactor(protocol): move the Electron remote client capability list into its own module

Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.

* test(cross-version): stub the launch seed resolver createSupport now reads

* fix(native-chat): the desktop tells its own host it picks each launch mode, so retrying an existing chat works with the setting off

* docs(native-chat): name the real exit for the released-phone createSupport rule

* test(cross-version): a released client still gets the host-setting createSupport answer; a launch-mode client gets supported plus the seed
2026-10-01 18:55:32 -07:00

99 lines
5.6 KiB
TypeScript

import type { AppIdentity } from '../../shared/app-identity'
import type { E2EConfig } from '../../shared/e2e-config'
import type { ExecutionHostId } from '../../shared/execution-host'
import type {
WriteTerminalRenderDesyncEvidenceArgs,
WriteTerminalRenderDesyncEvidenceResult
} from '../../shared/terminal-render-desync-evidence'
import type { MacCapturedDigitRowChord } from '../../shared/macos-symbolic-hotkeys'
import type { MarkdownDocument } from '../../shared/filesystem-entry-types'
import type { PersistedUIState } from '../../shared/persisted-ui-state-types'
import type { FloatingTerminalCwdRequest } from '../../shared/ui-chrome-types'
import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types'
import type { KeyboardLayoutSnapshot } from '../../shared/keyboard-layout-snapshot'
import type { KeyboardLayoutChangeEvent } from '../../shared/keyboard-layout-events'
export type AppApi = {
/** Returns the app identity currently exposed to native chrome and the titlebar. */
getIdentity: () => Promise<AppIdentity>
/** Returns a URL base for feature-wall assets. In dev this is Vite /@fs;
* in packaged builds this is file:// resources. Renderer appends filenames. */
getFeatureWallAssetBaseUrl: () => Promise<string>
/** Relaunches the app (app.relaunch() + app.exit(0)) for settings that need a full restart to apply. */
relaunch: () => Promise<void>
/** Restarts Orca through the normal quit pipeline so daemon-backed terminal
* sessions survive and can reattach after the new process starts. */
restart: () => Promise<void>
/** Reloads the current app renderer through main so expected renderer
* teardown can be classified before Electron emits process-gone events. */
reload: () => Promise<void>
/** Stages the renderer's final state synchronously before unload. */
stageBeforeUnloadSync: (args: {
sessions: { state: WorkspaceSessionState; hostId?: ExecutionHostId }[]
ui: Partial<PersistedUIState>
}) => void
/** Resolves once the last staged checkpoint is durably written; rejects if that
* write failed, so a reload/restart can abort instead of losing the snapshot. */
awaitBeforeUnloadCheckpoint: () => Promise<void>
/** Resolves when the daemon PTY provider and hook receiver have either
* started or failed open for the first BrowserWindow. */
awaitFirstWindowStartupServices: () => Promise<void>
/** Resolves when host Git can run: shell-PATH generation is published and the
* managed WSL CLI registration has reconciled. Does not wait on PTY services. */
awaitGitEnvironmentStartupBarrier: () => Promise<void>
/** Inventories retained PTYs and restores durable structured ownership before renderer adoption. */
prepareTerminalStartupRestoration: () => Promise<void>
/** Whether this machine's runtime holds a structured chat, saved or live. */
holdsStructuredAgentSessions: () => Promise<boolean>
/** Fires when that answer changes, e.g. when a paired client creates the first chat here. */
onStructuredAgentSessionsHeldChanged: (callback: (held: boolean) => void) => () => void
/** Reconciles legacy worker authority around persisted terminal reconnect. */
recoverLegacyWorkerTerminalsForRendererStartup: () => Promise<void>
/** Emits a startup benchmark marker when ORCA_STARTUP_DIAGNOSTICS is enabled. */
startupDiagnostic: (event: string, details?: Record<string, unknown>) => Promise<void>
/** macOS active input mode, or layout ID when no IME is selected (e.g. `com.apple.keylayout.PolishPro`).
* Distinguishes CJK IMEs and Option-layer-composing layouts that look like US QWERTY (issue #1205).
* Returns null on non-Darwin or when the defaults read fails. */
getKeyboardInputSourceId: () => Promise<string | null>
/** Physical Mission Control chords before layout resolution. */
getMacCapturedDigitRowChords: () => Promise<MacCapturedDigitRowChord[]>
/** Active macOS layout characters without Option, or null off macOS or when the native probe fails. */
getKeyboardLayoutSnapshot: () => Promise<KeyboardLayoutSnapshot | null>
/** Subscribes to active macOS input-source changes. No-op in the browser fallback. */
onKeyboardLayoutChanged: (callback: (event: KeyboardLayoutChangeEvent) => void) => () => void
/** Updates the macOS Dock unread badge. No-op on Windows/Linux. */
setUnreadDockBadgeCount: (count: number) => Promise<void>
/** Resolves the launch directory for global Floating Terminal tabs. */
getFloatingTerminalCwd: (args?: FloatingTerminalCwdRequest) => Promise<string>
/** Resolves Orca's app-owned directory for auto-created Floating Workspace
* markdown notes. */
getFloatingMarkdownDirectory: () => Promise<string>
/** Opens a native picker for markdown documents, rooted in the floating
* workspace, and authorizes the selected file for editor reads/writes. */
pickFloatingMarkdownDocument: () => Promise<MarkdownDocument | null>
/** Opens a native directory picker and authorizes the selected directory
* for Floating Workspace markdown file creation. */
pickFloatingWorkspaceDirectory: () => Promise<string | null>
/** Persists flag-gated terminal render evidence under app-owned userData. */
writeTerminalRenderDesyncEvidence: (
args: WriteTerminalRenderDesyncEvidenceArgs
) => Promise<WriteTerminalRenderDesyncEvidenceResult>
}
/** Panel contribution as surfaced by the main-process plugin service. */
export type PlatformApi = {
get: () => {
platform: NodeJS.Platform
osRelease: string
arch: string
/** Login shell or ComSpec when available. */
shell: string
displayServer: 'wayland' | 'x11' | null
}
}
export type E2EApi = {
getConfig: () => E2EConfig
}