mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 01:57:57 +00:00
refactor(renderer-terminal-runtime): clean up Phase 3-3.8 code (#26900)
- One helper plans and runs a paste into a terminal pane (five copies before) - Reuse getShortcutPlatform instead of three private copies - runtime-git-client becomes plain re-exports - TerminalErrorToast builds each token's test/replace regex from one helper - Reuse PreconnectInputWriter for the recovery input hold - Name the session-tabs snapshot callback type once - Narrow isTerminalDriverState with 'in' instead of a cast - Inline the single-use terminal-pane-client-host wrapper Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
@@ -34,48 +34,34 @@ const PANE_OWNER_UNVERIFIED_MARKER = 'terminal_pane_owner_unverified'
|
||||
// translated here rather than at the source -- otherwise the banner mixes English with the
|
||||
// localized chrome around it (#9194).
|
||||
const REMOTE_TERMINAL_CLOSED_MARKER = 'Remote terminal was closed.'
|
||||
// Why one source: the test and replace forms must match the same token, and a lone /g regex carries
|
||||
// lastIndex state across .test() calls. Capture the leading boundary so replacement can restore it.
|
||||
const TERMINAL_HOST_GONE_SOURCE = '(^|[^a-z0-9_])terminal_host_gone(?=$|[^a-z0-9_])'
|
||||
const TERMINAL_HOST_GONE_PATTERN = new RegExp(TERMINAL_HOST_GONE_SOURCE)
|
||||
const TERMINAL_HOST_GONE_REPLACE_PATTERN = new RegExp(TERMINAL_HOST_GONE_SOURCE, 'g')
|
||||
// Why: a lone /g regex carries lastIndex across .test() calls, so each token gets a stateless test form and a /g replace form.
|
||||
function tokenPattern(source: string): { match: RegExp; replaceAll: RegExp } {
|
||||
return { match: new RegExp(source), replaceAll: new RegExp(source, 'g') }
|
||||
}
|
||||
// Capture the leading boundary so replacement can restore it.
|
||||
const TERMINAL_HOST_GONE = tokenPattern('(^|[^a-z0-9_])terminal_host_gone(?=$|[^a-z0-9_])')
|
||||
const LEGACY_TERMINAL_HOST_GONE_PATTERN =
|
||||
/(^|[^a-z])connect (?:ENOENT|ECONNREFUSED) [^\r\n]*orca-terminal-host-v[^\r\n]*/i
|
||||
// A reattach the host answered "no such session" for: the SSH provider's expiry token, the relay's
|
||||
// raw not-found string when nothing mapped it, or a daemon generation old enough to still refuse a
|
||||
// pane respawning onto an id it is tearing down (#18046). None proves the shell died — the copy
|
||||
// says only that this pane lost its session. Same lastIndex hazard as above.
|
||||
const UNREATTACHABLE_SESSION_SOURCES = [
|
||||
// says only that this pane lost its session.
|
||||
const UNREATTACHABLE_SESSIONS = [
|
||||
'SSH_SESSION_EXPIRED:[ \\t]*\\S*(?:[ \\t]+SSH_PTY_IDENTITY_MISMATCH)?',
|
||||
'PTY "[^"\\r\\n]*" not found(?: \\(identity mismatch\\))?',
|
||||
'(?:SessionNotFoundError: )?Session not found: \\S+'
|
||||
]
|
||||
].map(tokenPattern)
|
||||
// The relay answered and proved the shell is still running — only its output delivery was retired.
|
||||
// Deliberately NOT one of the sources above: that copy says to open a new terminal, which here
|
||||
// abandons a live agent. Same lastIndex hazard, so keep the test and replace forms separate.
|
||||
const SOURCE_RESTORE_REQUIRED_SOURCE =
|
||||
// abandons a live agent.
|
||||
const SOURCE_RESTORE_REQUIRED = tokenPattern(
|
||||
'SSH_PTY_SOURCE_RESTORE_REQUIRED(?::[ \\t]*\\S*(?:[ \\t]+\\S+)?)?'
|
||||
const SOURCE_RESTORE_REQUIRED_PATTERN = new RegExp(SOURCE_RESTORE_REQUIRED_SOURCE)
|
||||
const SOURCE_RESTORE_REQUIRED_REPLACE_PATTERN = new RegExp(SOURCE_RESTORE_REQUIRED_SOURCE, 'g')
|
||||
)
|
||||
// An older Orca build's relay may still run this terminal, and this build cannot reach it. Not one of
|
||||
// the sources above: that copy implies the session is gone.
|
||||
const HELD_BY_PREVIOUS_RELAY_SOURCE = 'SSH_PTY_HELD_BY_PREVIOUS_RELAY(?::[ \\t]*\\S*)?'
|
||||
const HELD_BY_PREVIOUS_RELAY_PATTERN = new RegExp(HELD_BY_PREVIOUS_RELAY_SOURCE)
|
||||
const HELD_BY_PREVIOUS_RELAY = tokenPattern('SSH_PTY_HELD_BY_PREVIOUS_RELAY(?::[ \\t]*\\S*)?')
|
||||
// The pane's saved session is owned by another host connection, e.g. an older build's relay tab.
|
||||
const OWNER_HOST_MISMATCH_SOURCE = 'terminal_pane_owner_host_mismatch'
|
||||
const OWNER_HOST_MISMATCH_PATTERN = new RegExp(OWNER_HOST_MISMATCH_SOURCE)
|
||||
const OWNER_HOST_MISMATCH_REPLACE_PATTERN = new RegExp(OWNER_HOST_MISMATCH_SOURCE, 'g')
|
||||
const HELD_BY_PREVIOUS_RELAY_REPLACE_PATTERN = new RegExp(HELD_BY_PREVIOUS_RELAY_SOURCE, 'g')
|
||||
const UNREATTACHABLE_SESSION_PATTERNS = UNREATTACHABLE_SESSION_SOURCES.map(
|
||||
(source) => new RegExp(source)
|
||||
)
|
||||
const UNREATTACHABLE_SESSION_REPLACE_PATTERNS = UNREATTACHABLE_SESSION_SOURCES.map(
|
||||
(source) => new RegExp(source, 'g')
|
||||
)
|
||||
|
||||
function isSshError(error: string): boolean {
|
||||
return isSshReconnectOwnedTerminalError(error)
|
||||
}
|
||||
const OWNER_HOST_MISMATCH = tokenPattern('terminal_pane_owner_host_mismatch')
|
||||
|
||||
/** A single error line the SSH reconnect banner already covers — hide instead of stacking under/over it. */
|
||||
export function isSshReconnectOwnedTerminalError(error: string): boolean {
|
||||
@@ -106,18 +92,20 @@ export function isExplainedTerminalError(error: string): boolean {
|
||||
.split('\n')
|
||||
.some(
|
||||
(line) =>
|
||||
TERMINAL_HOST_GONE_PATTERN.test(line) ||
|
||||
LEGACY_TERMINAL_HOST_GONE_PATTERN.test(line) ||
|
||||
SOURCE_RESTORE_REQUIRED_PATTERN.test(line) ||
|
||||
HELD_BY_PREVIOUS_RELAY_PATTERN.test(line) ||
|
||||
OWNER_HOST_MISMATCH_PATTERN.test(line) ||
|
||||
UNREATTACHABLE_SESSION_PATTERNS.some((pattern) => pattern.test(line))
|
||||
[
|
||||
TERMINAL_HOST_GONE,
|
||||
SOURCE_RESTORE_REQUIRED,
|
||||
HELD_BY_PREVIOUS_RELAY,
|
||||
OWNER_HOST_MISMATCH,
|
||||
...UNREATTACHABLE_SESSIONS
|
||||
].some((pattern) => pattern.match.test(line))
|
||||
)
|
||||
}
|
||||
|
||||
/** A terminal the previous Orca version's relay runs on the host; this client's details say nothing about it. */
|
||||
export function isHeldByPreviousRelayError(error: string): boolean {
|
||||
return HELD_BY_PREVIOUS_RELAY_PATTERN.test(error)
|
||||
return HELD_BY_PREVIOUS_RELAY.match.test(error)
|
||||
}
|
||||
|
||||
export function isPaneOwnerUnverifiedError(error: string): boolean {
|
||||
@@ -131,8 +119,8 @@ function humanizeUnreattachableSession(error: string): string {
|
||||
"Orca couldn't reattach to this pane's terminal session on the host. Open a new terminal to continue."
|
||||
)
|
||||
// Why a replacer: a translation containing `$&` or `$1` would otherwise be read as a substitution.
|
||||
return UNREATTACHABLE_SESSION_REPLACE_PATTERNS.reduce(
|
||||
(message, pattern) => message.replace(pattern, () => explanation),
|
||||
return UNREATTACHABLE_SESSIONS.reduce(
|
||||
(message, pattern) => message.replace(pattern.replaceAll, () => explanation),
|
||||
error
|
||||
)
|
||||
}
|
||||
@@ -165,19 +153,19 @@ export function humanizeTerminalError(error: string): string {
|
||||
)
|
||||
humanized = humanized.replaceAll(PANE_OWNER_UNVERIFIED_MARKER, () => explanation)
|
||||
}
|
||||
humanized = humanized.replace(SOURCE_RESTORE_REQUIRED_REPLACE_PATTERN, () =>
|
||||
humanized = humanized.replace(SOURCE_RESTORE_REQUIRED.replaceAll, () =>
|
||||
translate(
|
||||
'auto.components.terminal.pane.TerminalErrorToast.sourceRestoring',
|
||||
'Reconnecting this terminal — its output is being re-established. The session is still running.'
|
||||
)
|
||||
)
|
||||
humanized = humanized.replace(HELD_BY_PREVIOUS_RELAY_REPLACE_PATTERN, () =>
|
||||
humanized = humanized.replace(HELD_BY_PREVIOUS_RELAY.replaceAll, () =>
|
||||
translate(
|
||||
'auto.components.terminal.pane.TerminalErrorToast.heldByPreviousRelay',
|
||||
'This terminal is still running on the host under the previous Orca version, which this version cannot connect to. It keeps running until it exits. Open a new terminal to keep working here.'
|
||||
)
|
||||
)
|
||||
humanized = humanized.replace(OWNER_HOST_MISMATCH_REPLACE_PATTERN, () =>
|
||||
humanized = humanized.replace(OWNER_HOST_MISMATCH.replaceAll, () =>
|
||||
translate(
|
||||
'auto.components.terminal.pane.TerminalErrorToast.ownerHostMismatch',
|
||||
"This terminal's saved session belongs to another host connection, so Orca can't reattach it here. Open a new terminal to continue."
|
||||
@@ -203,7 +191,7 @@ export function humanizeTerminalError(error: string): string {
|
||||
.split('\n')
|
||||
.map((line) =>
|
||||
line
|
||||
.replace(TERMINAL_HOST_GONE_REPLACE_PATTERN, (_match, prefix: string) =>
|
||||
.replace(TERMINAL_HOST_GONE.replaceAll, (_match, prefix: string) =>
|
||||
prefix.concat(explanation)
|
||||
)
|
||||
.replace(LEGACY_TERMINAL_HOST_GONE_PATTERN, (_match, prefix: string) =>
|
||||
@@ -227,7 +215,7 @@ export function TerminalErrorToast({
|
||||
onRestartDaemon?: () => void
|
||||
onRetry?: () => Promise<boolean>
|
||||
}): React.JSX.Element {
|
||||
const ssh = isSshError(error)
|
||||
const ssh = isSshReconnectOwnedTerminalError(error)
|
||||
// Why: the client's OS and shell describe neither the host nor its shell, and the renderer knows neither.
|
||||
const showClientEnvironment = paneOnClient && !ssh && !isHeldByPreviousRelayError(error)
|
||||
const paneOwnerUnverified = isPaneOwnerUnverifiedError(error)
|
||||
|
||||
@@ -27,7 +27,8 @@ import {
|
||||
} from './TerminalPaneRuntimePortals'
|
||||
import type { TerminalPaneController } from './use-terminal-pane-controller'
|
||||
import { useAppStore } from '@/store'
|
||||
import { isTerminalPaneOnClient } from './terminal-pane-client-host'
|
||||
import { getKnownExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
|
||||
|
||||
export function TerminalPaneSurface({
|
||||
controller
|
||||
@@ -118,7 +119,10 @@ export function TerminalPaneSurface({
|
||||
visibleTerminalError,
|
||||
worktreeId
|
||||
} = controller
|
||||
const paneOnClient = useAppStore((state) => isTerminalPaneOnClient(state, worktreeId))
|
||||
// Why: an SSH, runtime, or not-yet-known host must not be described with this client's OS/shell.
|
||||
const paneOnClient = useAppStore(
|
||||
(state) => getKnownExecutionHostIdForWorktree(state, worktreeId) === LOCAL_EXECUTION_HOST_ID
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -24,7 +24,7 @@ type BufferedInput = PtyPreconnectInputEntry & {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
|
||||
type PreconnectInputWriter = {
|
||||
export type PreconnectInputWriter = {
|
||||
isCurrent: () => boolean
|
||||
sendInput: (data: string, inputKind: TerminalInputKind) => boolean
|
||||
sendInputImmediate: (data: string) => boolean
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { TerminalInputKind } from '../../../../shared/terminal-input-kind'
|
||||
import {
|
||||
createPtyPreconnectInputBuffer,
|
||||
type AcceptedInputOptions,
|
||||
type PreconnectInputWriter,
|
||||
type PtyPreconnectInputBuffer
|
||||
} from './pty-preconnect-input-buffer'
|
||||
|
||||
@@ -11,17 +12,9 @@ export type RemoteRuntimeInputEndpoint = {
|
||||
incarnationId: string | null
|
||||
}
|
||||
|
||||
type HeldInputWriter = {
|
||||
isCurrent: () => boolean
|
||||
sendInput: (data: string, inputKind: TerminalInputKind) => boolean
|
||||
sendInputImmediate: (data: string) => boolean
|
||||
sendInputAccepted: (
|
||||
data: string,
|
||||
inputKind: TerminalInputKind,
|
||||
options?: AcceptedInputOptions
|
||||
) => Promise<boolean>
|
||||
continuesAfterFailedWrite?: () => boolean
|
||||
}
|
||||
// Why: release() always supplies an accepted-write path, so the hold requires what the buffer only accepts.
|
||||
type HeldInputWriter = PreconnectInputWriter &
|
||||
Required<Pick<PreconnectInputWriter, 'sendInputAccepted'>>
|
||||
|
||||
export type RemoteRuntimeRecoveryInputHold = {
|
||||
isHolding: () => boolean
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { WorktreeRuntimeOwnerState } from '@/lib/worktree-runtime-owner-state'
|
||||
import { isTerminalPaneOnClient } from './terminal-pane-client-host'
|
||||
|
||||
const state: WorktreeRuntimeOwnerState = {
|
||||
repos: [
|
||||
{ id: 'local-repo', executionHostId: 'local' },
|
||||
{ id: 'ssh-repo', connectionId: 'box', executionHostId: 'ssh:box' },
|
||||
{ id: 'managed-repo', executionHostId: 'runtime:env-1' }
|
||||
],
|
||||
worktreesByRepo: {}
|
||||
}
|
||||
|
||||
describe('whether a terminal pane runs on this client', () => {
|
||||
it('is true only for a local workspace', () => {
|
||||
expect(isTerminalPaneOnClient(state, 'local-repo::/home/me/app')).toBe(true)
|
||||
expect(isTerminalPaneOnClient(state, 'ssh-repo::/srv/app')).toBe(false)
|
||||
expect(isTerminalPaneOnClient(state, 'managed-repo::/root/repo')).toBe(false)
|
||||
})
|
||||
|
||||
it('is false while the workspace host is still unknown', () => {
|
||||
expect(isTerminalPaneOnClient(state, 'not-loaded::/srv/app')).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,14 +0,0 @@
|
||||
import { getKnownExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import type { WorktreeRuntimeOwnerState } from '@/lib/worktree-runtime-owner-state'
|
||||
import { LOCAL_EXECUTION_HOST_ID } from '../../../../shared/execution-host'
|
||||
|
||||
/**
|
||||
* True only when the pane's shell runs on this client. An SSH or remote host, or one not yet
|
||||
* known, must not be described with this client's OS and shell.
|
||||
*/
|
||||
export function isTerminalPaneOnClient(
|
||||
state: WorktreeRuntimeOwnerState,
|
||||
worktreeId: string | null | undefined
|
||||
): boolean {
|
||||
return getKnownExecutionHostIdForWorktree(state, worktreeId) === LOCAL_EXECUTION_HOST_ID
|
||||
}
|
||||
@@ -3,19 +3,14 @@ import type { PtyTransport } from './pty-transport'
|
||||
import { requestNativeChatCoverPaste } from './native-chat-cover-paste'
|
||||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import { pasteTerminalText } from './terminal-bracketed-paste'
|
||||
import { getShortcutPlatform } from '@/lib/shortcut-platform'
|
||||
import { pasteTerminalClipboard } from './terminal-clipboard-paste'
|
||||
import {
|
||||
executeTerminalPastePlan,
|
||||
planTerminalPasteWithYield,
|
||||
type TerminalPasteSource,
|
||||
type TerminalPasteTextOptions
|
||||
} from './terminal-paste-coordinator'
|
||||
import type { TerminalPasteSource, TerminalPasteTextOptions } from './terminal-paste-coordinator'
|
||||
import { formatTerminalPasteExecutionError } from './terminal-paste-errors'
|
||||
import { resolveTerminalPasteRuntime } from './terminal-paste-runtime'
|
||||
import { getTerminalPasteSshRemotePlatform } from './terminal-paste-ssh-platform'
|
||||
import { isTerminalPanePasteTargetCurrent } from './terminal-paste-target-state'
|
||||
import { writeTerminalPastePtyInput } from './terminal-pty-paste-writer'
|
||||
import { pasteTextIntoTerminalPane } from './terminal-pane-paste-dispatch'
|
||||
import { scheduleImagePasteWebglAtlasRecovery } from './terminal-webgl-atlas-recovery'
|
||||
import { useAppStore } from '@/store'
|
||||
import { resolveProtectedMultilinePasteOptionsForPane } from './terminal-agent-paste-bracketing'
|
||||
@@ -31,13 +26,6 @@ export type TerminalPaneMenuPasteContext = {
|
||||
onPasteError: (message: string) => void
|
||||
}
|
||||
|
||||
export const getTerminalPaneMenuShortcutPlatform = (): NodeJS.Platform => {
|
||||
if (navigator.userAgent.includes('Mac')) {
|
||||
return 'darwin'
|
||||
}
|
||||
return navigator.userAgent.includes('Windows') ? 'win32' : 'linux'
|
||||
}
|
||||
|
||||
const isPanePasteTargetMounted = (
|
||||
context: TerminalPaneMenuPasteContext,
|
||||
pane: ManagedPane,
|
||||
@@ -64,35 +52,25 @@ export const executeTerminalPaneMenuPasteText = async (
|
||||
const connectionId = getConnectionId(context.worktreeId) ?? null
|
||||
const transport = context.paneTransportsRef.current.get(pane.id)
|
||||
const ptyId = transport?.getPtyId() ?? null
|
||||
const shortcutPlatform = getTerminalPaneMenuShortcutPlatform()
|
||||
const plan = await planTerminalPasteWithYield({
|
||||
const isTargetMounted = (): boolean => isPanePasteTargetMounted(context, pane, transport, ptyId)
|
||||
const execution = await pasteTextIntoTerminalPane({
|
||||
pane,
|
||||
text,
|
||||
source,
|
||||
target: {
|
||||
kind: 'terminal',
|
||||
paneId: pane.id,
|
||||
leafId: pane.leafId,
|
||||
ptyId,
|
||||
runtime: resolveTerminalPasteRuntime({
|
||||
platform: getShortcutPlatform(),
|
||||
ptyId,
|
||||
runtime: resolveTerminalPasteRuntime({
|
||||
platform: shortcutPlatform,
|
||||
ptyId,
|
||||
connectionId,
|
||||
remotePlatform: getTerminalPasteSshRemotePlatform(connectionId),
|
||||
transport,
|
||||
isWindowsConpty: context.forceBracketedMultilineTextPaste
|
||||
})
|
||||
},
|
||||
forceBracketedPaste: options?.forceBracketedPaste,
|
||||
forceBracketedPasteForMultiline: options?.forceBracketedPasteForMultiline,
|
||||
windowsInputRecordNewline: options?.windowsInputRecordNewline,
|
||||
terminalBracketedPasteMode: pane.terminal.modes.bracketedPasteMode
|
||||
})
|
||||
const execution = await executeTerminalPastePlan(plan, {
|
||||
pasteText: (pasteText, pasteOptions) =>
|
||||
pasteTerminalText(pane.terminal, pasteText, pasteOptions),
|
||||
writePty: (data, signal) => writeTerminalPastePtyInput(transport, data, 'driving', signal),
|
||||
isTargetCurrent: () => isPanePasteTargetMounted(context, pane, transport, ptyId),
|
||||
canContinue: () => isPanePasteTargetMounted(context, pane, transport, ptyId)
|
||||
connectionId,
|
||||
remotePlatform: getTerminalPasteSshRemotePlatform(connectionId),
|
||||
transport,
|
||||
isWindowsConpty: context.forceBracketedMultilineTextPaste
|
||||
}),
|
||||
transport,
|
||||
inputKind: 'driving',
|
||||
isTargetCurrent: isTargetMounted,
|
||||
canContinue: isTargetMounted,
|
||||
planOptions: options
|
||||
})
|
||||
if (execution.status !== 'pasted') {
|
||||
context.onPasteError(formatTerminalPasteExecutionError(execution.reason))
|
||||
@@ -131,7 +109,7 @@ export const pasteTerminalPaneMenuClipboard = async (
|
||||
protectedMultilineTextPasteOptions: resolveProtectedMultilinePasteOptionsForPane({
|
||||
isWindowsClient: forceBracketedMultilineTextPaste,
|
||||
hostPlatform: resolveTerminalInputHostPlatform({
|
||||
clientPlatform: getTerminalPaneMenuShortcutPlatform(),
|
||||
clientPlatform: getShortcutPlatform(),
|
||||
state,
|
||||
worktreeId,
|
||||
transport
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { pasteTerminalText } from './terminal-bracketed-paste'
|
||||
import {
|
||||
executeTerminalPastePlan,
|
||||
planTerminalPasteWithYield,
|
||||
type TerminalPasteExecutionResult,
|
||||
type TerminalPasteRuntime,
|
||||
type TerminalPasteSource,
|
||||
type TerminalPasteTextOptions
|
||||
} from './terminal-paste-coordinator'
|
||||
import { writeTerminalPastePtyInput } from './terminal-pty-paste-writer'
|
||||
|
||||
type PasteTextIntoTerminalPaneArgs = {
|
||||
pane: { id: number; leafId: string; terminal: Parameters<typeof pasteTerminalText>[0] }
|
||||
text: string
|
||||
source: TerminalPasteSource
|
||||
ptyId: string | null
|
||||
runtime: TerminalPasteRuntime
|
||||
transport: Parameters<typeof writeTerminalPastePtyInput>[0]
|
||||
inputKind: 'driving' | 'launch'
|
||||
isTargetCurrent: () => boolean
|
||||
canContinue: () => boolean
|
||||
planOptions?: Pick<
|
||||
TerminalPasteTextOptions,
|
||||
'forceBracketedPaste' | 'forceBracketedPasteForMultiline' | 'windowsInputRecordNewline'
|
||||
>
|
||||
}
|
||||
|
||||
export async function pasteTextIntoTerminalPane({
|
||||
pane,
|
||||
text,
|
||||
source,
|
||||
ptyId,
|
||||
runtime,
|
||||
transport,
|
||||
inputKind,
|
||||
isTargetCurrent,
|
||||
canContinue,
|
||||
planOptions
|
||||
}: PasteTextIntoTerminalPaneArgs): Promise<TerminalPasteExecutionResult> {
|
||||
const plan = await planTerminalPasteWithYield({
|
||||
text,
|
||||
source,
|
||||
target: { kind: 'terminal', paneId: pane.id, leafId: pane.leafId, ptyId, runtime },
|
||||
forceBracketedPaste: planOptions?.forceBracketedPaste,
|
||||
forceBracketedPasteForMultiline: planOptions?.forceBracketedPasteForMultiline,
|
||||
windowsInputRecordNewline: planOptions?.windowsInputRecordNewline,
|
||||
terminalBracketedPasteMode: pane.terminal.modes?.bracketedPasteMode === true
|
||||
})
|
||||
return executeTerminalPastePlan(plan, {
|
||||
pasteText: (pasteText, pasteOptions) =>
|
||||
pasteTerminalText(pane.terminal, pasteText, pasteOptions),
|
||||
writePty: (data, signal) => writeTerminalPastePtyInput(transport, data, inputKind, signal),
|
||||
isTargetCurrent,
|
||||
canContinue
|
||||
})
|
||||
}
|
||||
@@ -3,20 +3,14 @@ import type { ManagedPane } from '@/lib/pane-manager/pane-manager'
|
||||
import type { PtyTransport } from './pty-transport'
|
||||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { getRuntimeEnvironmentIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import {
|
||||
executeTerminalPastePlan,
|
||||
planTerminalPasteWithYield,
|
||||
type TerminalPasteSource,
|
||||
type TerminalPasteTextOptions
|
||||
} from './terminal-paste-coordinator'
|
||||
import type { TerminalPasteSource, TerminalPasteTextOptions } from './terminal-paste-coordinator'
|
||||
import { resolveTerminalPasteRuntime } from './terminal-paste-runtime'
|
||||
import { getTerminalPasteSshRemotePlatform } from './terminal-paste-ssh-platform'
|
||||
import {
|
||||
isTerminalPanePasteFocusCurrent,
|
||||
isTerminalPanePasteTargetCurrent
|
||||
} from './terminal-paste-target-state'
|
||||
import { pasteTerminalText } from './terminal-bracketed-paste'
|
||||
import { writeTerminalPastePtyInput } from './terminal-pty-paste-writer'
|
||||
import { pasteTextIntoTerminalPane } from './terminal-pane-paste-dispatch'
|
||||
import { formatTerminalPasteExecutionError } from './terminal-paste-errors'
|
||||
import { recordTerminalUserInputForLeaf } from './terminal-input-activity'
|
||||
import { scheduleImagePasteWebglAtlasRecovery } from './terminal-webgl-atlas-recovery'
|
||||
@@ -69,31 +63,21 @@ export function createTerminalPanePasteExecution(
|
||||
const ptyId = transport?.getPtyId() ?? null
|
||||
const keyboardOwnedPaste =
|
||||
source === 'keyboard' || source === 'paste-event' || source === 'app-menu'
|
||||
const plan = await planTerminalPasteWithYield({
|
||||
const execution = await pasteTextIntoTerminalPane({
|
||||
pane,
|
||||
text,
|
||||
source,
|
||||
target: {
|
||||
kind: 'terminal',
|
||||
paneId: pane.id,
|
||||
leafId: pane.leafId,
|
||||
ptyId,
|
||||
runtime: resolveTerminalPasteRuntime({
|
||||
platform: shortcutPlatform,
|
||||
ptyId,
|
||||
runtime: resolveTerminalPasteRuntime({
|
||||
platform: shortcutPlatform,
|
||||
ptyId,
|
||||
connectionId,
|
||||
remotePlatform: getTerminalPasteSshRemotePlatform(connectionId),
|
||||
transport,
|
||||
isWindowsConpty: forceBracketedMultilineTextPaste
|
||||
})
|
||||
},
|
||||
forceBracketedPaste: options?.forceBracketedPaste,
|
||||
forceBracketedPasteForMultiline: options?.forceBracketedPasteForMultiline,
|
||||
terminalBracketedPasteMode: pane.terminal.modes.bracketedPasteMode
|
||||
})
|
||||
const execution = await executeTerminalPastePlan(plan, {
|
||||
pasteText: (pasteText, pasteOptions) =>
|
||||
pasteTerminalText(pane.terminal, pasteText, pasteOptions),
|
||||
writePty: (data, signal) => writeTerminalPastePtyInput(transport, data, 'driving', signal),
|
||||
connectionId,
|
||||
remotePlatform: getTerminalPasteSshRemotePlatform(connectionId),
|
||||
transport,
|
||||
isWindowsConpty: forceBracketedMultilineTextPaste
|
||||
}),
|
||||
transport,
|
||||
inputKind: 'driving',
|
||||
isTargetCurrent: () => {
|
||||
if (!isPanePasteTargetMounted(pane, transport, ptyId)) {
|
||||
return false
|
||||
@@ -104,7 +88,8 @@ export function createTerminalPanePasteExecution(
|
||||
paneContainer: pane.container
|
||||
})
|
||||
},
|
||||
canContinue: () => isPanePasteTargetMounted(pane, transport, ptyId)
|
||||
canContinue: () => isPanePasteTargetMounted(pane, transport, ptyId),
|
||||
planOptions: options
|
||||
})
|
||||
if (execution.status !== 'pasted') {
|
||||
setTerminalError(formatTerminalPasteExecutionError(execution.reason))
|
||||
|
||||
@@ -2,13 +2,12 @@ import type { PasteTerminalTextDetail } from '@/constants/terminal'
|
||||
import type { PaneManager } from '@/lib/pane-manager/pane-manager'
|
||||
import type { PtyTransport } from './pty-transport'
|
||||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { pasteTerminalText } from './terminal-bracketed-paste'
|
||||
import { getShortcutPlatform } from '@/lib/shortcut-platform'
|
||||
import { recordTerminalUserInputForLeaf } from './terminal-input-activity'
|
||||
import { executeTerminalPastePlan, planTerminalPasteWithYield } from './terminal-paste-coordinator'
|
||||
import { pasteTextIntoTerminalPane } from './terminal-pane-paste-dispatch'
|
||||
import { resolveTerminalPasteRuntime } from './terminal-paste-runtime'
|
||||
import { getTerminalPasteSshRemotePlatform } from './terminal-paste-ssh-platform'
|
||||
import { isTerminalPanePasteTargetCurrent } from './terminal-paste-target-state'
|
||||
import { writeTerminalPastePtyInput } from './terminal-pty-paste-writer'
|
||||
|
||||
type HandleTerminalProgrammaticTextPasteArgs = {
|
||||
detail: PasteTerminalTextDetail | undefined
|
||||
@@ -43,62 +42,37 @@ export function handleTerminalProgrammaticTextPaste({
|
||||
const paneTransports = getPaneTransports()
|
||||
const transport = paneTransports.get(pane.id)
|
||||
const ptyId = transport?.getPtyId() ?? null
|
||||
const platform = getShortcutPlatform()
|
||||
const connectionId = getConnectionId(worktreeId) ?? null
|
||||
void planTerminalPasteWithYield({
|
||||
text: detail.text,
|
||||
source: 'programmatic',
|
||||
target: {
|
||||
kind: 'terminal',
|
||||
const isTargetCurrent = (): boolean =>
|
||||
isTerminalPanePasteTargetCurrent({
|
||||
manager: getManager(),
|
||||
paneTransports: getPaneTransports(),
|
||||
paneId: pane.id,
|
||||
leafId: pane.leafId,
|
||||
ptyId,
|
||||
runtime: resolveTerminalPasteRuntime({
|
||||
platform,
|
||||
ptyId,
|
||||
connectionId,
|
||||
remotePlatform: getTerminalPasteSshRemotePlatform(connectionId),
|
||||
transport
|
||||
})
|
||||
},
|
||||
terminalBracketedPasteMode: pane.terminal.modes?.bracketedPasteMode === true
|
||||
})
|
||||
.then((plan) =>
|
||||
executeTerminalPastePlan(plan, {
|
||||
pasteText: (text, options) => pasteTerminalText(pane.terminal, text, options),
|
||||
writePty: (data, signal) => writeTerminalPastePtyInput(transport, data, 'driving', signal),
|
||||
isTargetCurrent: () =>
|
||||
isTerminalPanePasteTargetCurrent({
|
||||
manager: getManager(),
|
||||
paneTransports: getPaneTransports(),
|
||||
paneId: pane.id,
|
||||
leafId: pane.leafId,
|
||||
transport,
|
||||
ptyId
|
||||
}),
|
||||
canContinue: () =>
|
||||
isTerminalPanePasteTargetCurrent({
|
||||
manager: getManager(),
|
||||
paneTransports: getPaneTransports(),
|
||||
paneId: pane.id,
|
||||
leafId: pane.leafId,
|
||||
transport,
|
||||
ptyId
|
||||
})
|
||||
})
|
||||
)
|
||||
.then((result) => {
|
||||
if (result.status !== 'pasted') {
|
||||
return
|
||||
}
|
||||
recordTerminalUserInputForLeaf(tabId, pane.leafId)
|
||||
pane.terminal.focus()
|
||||
transport,
|
||||
ptyId
|
||||
})
|
||||
}
|
||||
|
||||
function getShortcutPlatform(userAgent = globalThis.navigator?.userAgent ?? ''): NodeJS.Platform {
|
||||
if (userAgent.includes('Mac')) {
|
||||
return 'darwin'
|
||||
}
|
||||
return userAgent.includes('Windows') ? 'win32' : 'linux'
|
||||
void pasteTextIntoTerminalPane({
|
||||
pane,
|
||||
text: detail.text,
|
||||
source: 'programmatic',
|
||||
ptyId,
|
||||
runtime: resolveTerminalPasteRuntime({
|
||||
platform: getShortcutPlatform(),
|
||||
ptyId,
|
||||
connectionId,
|
||||
remotePlatform: getTerminalPasteSshRemotePlatform(connectionId),
|
||||
transport
|
||||
}),
|
||||
transport,
|
||||
inputKind: 'driving',
|
||||
isTargetCurrent,
|
||||
canContinue: isTargetCurrent
|
||||
}).then((result) => {
|
||||
if (result.status !== 'pasted') {
|
||||
return
|
||||
}
|
||||
recordTerminalUserInputForLeaf(tabId, pane.leafId)
|
||||
pane.terminal.focus()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import type { PtyTransport } from './pty-transport'
|
||||
import { pasteTerminalText } from './terminal-bracketed-paste'
|
||||
import {
|
||||
executeTerminalPastePlan,
|
||||
planTerminalPasteWithYield,
|
||||
type TerminalPasteExecutionResult,
|
||||
type TerminalPasteRuntime
|
||||
import type { pasteTerminalText } from './terminal-bracketed-paste'
|
||||
import type {
|
||||
TerminalPasteExecutionResult,
|
||||
TerminalPasteRuntime
|
||||
} from './terminal-paste-coordinator'
|
||||
import { writeTerminalPastePtyInput } from './terminal-pty-paste-writer'
|
||||
import { pasteTextIntoTerminalPane } from './terminal-pane-paste-dispatch'
|
||||
|
||||
type StartupCommandPane = {
|
||||
id: number
|
||||
@@ -32,22 +30,14 @@ export async function executeTerminalStartupCommandPaste({
|
||||
isTargetCurrent
|
||||
}: ExecuteTerminalStartupCommandPasteArgs): Promise<TerminalPasteExecutionResult> {
|
||||
const isCurrent = (): boolean => isTargetCurrent?.(ptyId) ?? true
|
||||
const plan = await planTerminalPasteWithYield({
|
||||
return pasteTextIntoTerminalPane({
|
||||
pane,
|
||||
text: command,
|
||||
source: 'programmatic',
|
||||
target: {
|
||||
kind: 'terminal',
|
||||
paneId: pane.id,
|
||||
leafId: pane.leafId,
|
||||
ptyId,
|
||||
runtime
|
||||
},
|
||||
terminalBracketedPasteMode: pane.terminal.modes?.bracketedPasteMode === true
|
||||
})
|
||||
|
||||
return executeTerminalPastePlan(plan, {
|
||||
pasteText: (text, options) => pasteTerminalText(pane.terminal, text, options),
|
||||
writePty: (data, signal) => writeTerminalPastePtyInput(transport, data, 'launch', signal),
|
||||
ptyId,
|
||||
runtime,
|
||||
transport,
|
||||
inputKind: 'launch',
|
||||
isTargetCurrent: isCurrent,
|
||||
canContinue: isCurrent
|
||||
})
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
readPrimarySelectionText
|
||||
} from '@/lib/primary-selection'
|
||||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { executeTerminalPastePlan, planTerminalPasteWithYield } from './terminal-paste-coordinator'
|
||||
import { getShortcutPlatform } from '@/lib/shortcut-platform'
|
||||
import { resolveTerminalPasteRuntime } from './terminal-paste-runtime'
|
||||
import { getTerminalPasteSshRemotePlatform } from './terminal-paste-ssh-platform'
|
||||
import { pasteTerminalText } from './terminal-bracketed-paste'
|
||||
import { writeTerminalPastePtyInput } from './terminal-pty-paste-writer'
|
||||
import { isTerminalPanePasteTargetCurrent } from './terminal-paste-target-state'
|
||||
import { pasteTextIntoTerminalPane } from './terminal-pane-paste-dispatch'
|
||||
import { formatTerminalPasteExecutionError } from './terminal-paste-errors'
|
||||
import { recordTerminalUserInputForLeaf } from './terminal-input-activity'
|
||||
import { splitTerminalPaneWithInheritedCwd } from './terminal-pane-split-with-inherited-cwd'
|
||||
@@ -153,51 +153,30 @@ export function useTerminalPaneMobileActions(controller: TerminalPaneContextCont
|
||||
}
|
||||
const transport = paneTransportsRef.current.get(clickedPane.id)
|
||||
const ptyId = transport?.getPtyId() ?? null
|
||||
const isMac = navigator.userAgent.includes('Mac')
|
||||
const shortcutPlatform: NodeJS.Platform = isMac
|
||||
? 'darwin'
|
||||
: navigator.userAgent.includes('Windows')
|
||||
? 'win32'
|
||||
: 'linux'
|
||||
const connectionId = getConnectionId(worktreeId) ?? null
|
||||
const targetStillMounted = (): boolean => {
|
||||
const manager = managerRef.current
|
||||
return Boolean(
|
||||
manager
|
||||
?.getPanes()
|
||||
.some(
|
||||
(livePane) =>
|
||||
livePane.id === clickedPane.id && livePane.leafId === clickedPane.leafId
|
||||
) &&
|
||||
transport &&
|
||||
paneTransportsRef.current.get(clickedPane.id) === transport &&
|
||||
transport.isConnected() &&
|
||||
transport.getPtyId() === ptyId
|
||||
)
|
||||
}
|
||||
const plan = await planTerminalPasteWithYield({
|
||||
text,
|
||||
source: 'middle-click',
|
||||
target: {
|
||||
kind: 'terminal',
|
||||
const targetStillMounted = (): boolean =>
|
||||
isTerminalPanePasteTargetCurrent({
|
||||
manager: managerRef.current,
|
||||
paneTransports: paneTransportsRef.current,
|
||||
paneId: clickedPane.id,
|
||||
leafId: clickedPane.leafId,
|
||||
transport,
|
||||
ptyId
|
||||
})
|
||||
const execution = await pasteTextIntoTerminalPane({
|
||||
pane: clickedPane,
|
||||
text,
|
||||
source: 'middle-click',
|
||||
ptyId,
|
||||
runtime: resolveTerminalPasteRuntime({
|
||||
platform: getShortcutPlatform(),
|
||||
ptyId,
|
||||
runtime: resolveTerminalPasteRuntime({
|
||||
platform: shortcutPlatform,
|
||||
ptyId,
|
||||
connectionId,
|
||||
remotePlatform: getTerminalPasteSshRemotePlatform(connectionId),
|
||||
transport
|
||||
})
|
||||
},
|
||||
terminalBracketedPasteMode: clickedPane.terminal.modes.bracketedPasteMode
|
||||
})
|
||||
const execution = await executeTerminalPastePlan(plan, {
|
||||
pasteText: (pasteText, pasteOptions) =>
|
||||
pasteTerminalText(clickedPane.terminal, pasteText, pasteOptions),
|
||||
writePty: (data, signal) =>
|
||||
writeTerminalPastePtyInput(transport, data, 'driving', signal),
|
||||
connectionId,
|
||||
remotePlatform: getTerminalPasteSshRemotePlatform(connectionId),
|
||||
transport
|
||||
}),
|
||||
transport,
|
||||
inputKind: 'driving',
|
||||
isTargetCurrent: targetStillMounted,
|
||||
canContinue: targetStillMounted
|
||||
})
|
||||
|
||||
@@ -644,6 +644,12 @@ describe('getKnownExecutionHostIdForWorktree', () => {
|
||||
it.each([
|
||||
['an ownerless repo row', { repos: [{ id: 'r' }] }, 'r::wt', 'local'],
|
||||
['an SSH repo row', { repos: [{ id: 'r', connectionId: 'box' }] }, 'r::wt', 'ssh:box'],
|
||||
[
|
||||
'a runtime repo row',
|
||||
{ repos: [{ id: 'r', executionHostId: 'runtime:env-1' }] },
|
||||
'r::wt',
|
||||
'runtime:env-1'
|
||||
],
|
||||
[
|
||||
'a per-worktree host',
|
||||
{ worktreesByRepo: { r: [{ id: 'r::wt', repoId: 'r', hostId: 'ssh:box' }] } },
|
||||
|
||||
@@ -171,10 +171,9 @@ export function isTerminalDriverState(
|
||||
if (!value || typeof value !== 'object' || !('kind' in value)) {
|
||||
return false
|
||||
}
|
||||
const driver = value as { kind?: unknown; clientId?: unknown }
|
||||
return (
|
||||
driver.kind === 'idle' ||
|
||||
driver.kind === 'desktop' ||
|
||||
(driver.kind === 'mobile' && typeof driver.clientId === 'string')
|
||||
value.kind === 'idle' ||
|
||||
value.kind === 'desktop' ||
|
||||
(value.kind === 'mobile' && 'clientId' in value && typeof value.clientId === 'string')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,50 +1,49 @@
|
||||
import { getRuntimeGitScope as getRuntimeGitScopeImplementation } from './runtime-git-client-context'
|
||||
import {
|
||||
getRuntimeGitBranchCompare as getRuntimeGitBranchCompareImplementation,
|
||||
getRuntimeGitBranchDiff as getRuntimeGitBranchDiffImplementation,
|
||||
getRuntimeGitCommitCompare as getRuntimeGitCommitCompareImplementation,
|
||||
getRuntimeGitCommitDiff as getRuntimeGitCommitDiffImplementation,
|
||||
getRuntimeGitDiff as getRuntimeGitDiffImplementation
|
||||
export { getRuntimeGitScope } from './runtime-git-client-context'
|
||||
export {
|
||||
getRuntimeGitBranchCompare,
|
||||
getRuntimeGitBranchDiff,
|
||||
getRuntimeGitCommitCompare,
|
||||
getRuntimeGitCommitDiff,
|
||||
getRuntimeGitDiff
|
||||
} from './runtime-git-diff-client'
|
||||
import {
|
||||
cancelRuntimeGenerateCommitMessage as cancelRuntimeGenerateCommitMessageImplementation,
|
||||
cancelRuntimeGeneratePullRequestFields as cancelRuntimeGeneratePullRequestFieldsImplementation,
|
||||
discoverRuntimeCommitMessageModels as discoverRuntimeCommitMessageModelsImplementation,
|
||||
generateRuntimeCommitMessage as generateRuntimeCommitMessageImplementation,
|
||||
generateRuntimePullRequestFields as generateRuntimePullRequestFieldsImplementation
|
||||
export {
|
||||
cancelRuntimeGenerateCommitMessage,
|
||||
cancelRuntimeGeneratePullRequestFields,
|
||||
discoverRuntimeCommitMessageModels,
|
||||
generateRuntimeCommitMessage,
|
||||
generateRuntimePullRequestFields
|
||||
} from './runtime-git-generation-client'
|
||||
import {
|
||||
getRuntimeGitConflictOperation as getRuntimeGitConflictOperationImplementation,
|
||||
getRuntimeGitHistory as getRuntimeGitHistoryImplementation,
|
||||
getRuntimeGitIgnoredPaths as getRuntimeGitIgnoredPathsImplementation,
|
||||
getRuntimeGitStatus as getRuntimeGitStatusImplementation,
|
||||
getRuntimeGitSubmoduleStatus as getRuntimeGitSubmoduleStatusImplementation,
|
||||
setRuntimeGitStatusUpstreamRefWatch as setRuntimeGitStatusUpstreamRefWatchImplementation
|
||||
export {
|
||||
getRuntimeGitConflictOperation,
|
||||
getRuntimeGitHistory,
|
||||
getRuntimeGitIgnoredPaths,
|
||||
getRuntimeGitStatus,
|
||||
getRuntimeGitSubmoduleStatus,
|
||||
setRuntimeGitStatusUpstreamRefWatch
|
||||
} from './runtime-git-status-client'
|
||||
import {
|
||||
abortRuntimeGitMerge as abortRuntimeGitMergeImplementation,
|
||||
abortRuntimeGitRebase as abortRuntimeGitRebaseImplementation,
|
||||
commitRuntimeGit as commitRuntimeGitImplementation,
|
||||
fastForwardRuntimeGit as fastForwardRuntimeGitImplementation,
|
||||
fetchRuntimeGit as fetchRuntimeGitImplementation,
|
||||
getRuntimeGitUpstreamStatus as getRuntimeGitUpstreamStatusImplementation,
|
||||
pullRuntimeGit as pullRuntimeGitImplementation,
|
||||
pushRuntimeGit as pushRuntimeGitImplementation,
|
||||
rebaseRuntimeGitFromBase as rebaseRuntimeGitFromBaseImplementation,
|
||||
syncRuntimeGitForkDefaultBranch as syncRuntimeGitForkDefaultBranchImplementation
|
||||
export {
|
||||
abortRuntimeGitMerge,
|
||||
abortRuntimeGitRebase,
|
||||
commitRuntimeGit,
|
||||
fastForwardRuntimeGit,
|
||||
fetchRuntimeGit,
|
||||
getRuntimeGitUpstreamStatus,
|
||||
pullRuntimeGit,
|
||||
pushRuntimeGit,
|
||||
rebaseRuntimeGitFromBase,
|
||||
syncRuntimeGitForkDefaultBranch
|
||||
} from './runtime-git-sync-client'
|
||||
import {
|
||||
bulkDiscardRuntimeGitPaths as bulkDiscardRuntimeGitPathsImplementation,
|
||||
bulkStageRuntimeGitPaths as bulkStageRuntimeGitPathsImplementation,
|
||||
bulkUnstageRuntimeGitPaths as bulkUnstageRuntimeGitPathsImplementation,
|
||||
discardRuntimeGitPath as discardRuntimeGitPathImplementation,
|
||||
getRuntimeGitRemoteCommitUrl as getRuntimeGitRemoteCommitUrlImplementation,
|
||||
getRuntimeGitRemoteFileUrl as getRuntimeGitRemoteFileUrlImplementation,
|
||||
stageRuntimeGitPath as stageRuntimeGitPathImplementation,
|
||||
stageRuntimeGitWorktreeScope as stageRuntimeGitWorktreeScopeImplementation,
|
||||
unstageRuntimeGitPath as unstageRuntimeGitPathImplementation
|
||||
export {
|
||||
bulkDiscardRuntimeGitPaths,
|
||||
bulkStageRuntimeGitPaths,
|
||||
bulkUnstageRuntimeGitPaths,
|
||||
discardRuntimeGitPath,
|
||||
getRuntimeGitRemoteCommitUrl,
|
||||
getRuntimeGitRemoteFileUrl,
|
||||
stageRuntimeGitPath,
|
||||
stageRuntimeGitWorktreeScope,
|
||||
unstageRuntimeGitPath
|
||||
} from './runtime-git-working-tree-client'
|
||||
|
||||
export type {
|
||||
RuntimeGenerateCommitMessageOverrides,
|
||||
RuntimeGenerateCommitMessageResult,
|
||||
@@ -53,41 +52,3 @@ export type {
|
||||
RuntimeGitContext,
|
||||
RuntimePullRequestGenerationInput
|
||||
} from './runtime-git-client-context'
|
||||
|
||||
export const getRuntimeGitScope = getRuntimeGitScopeImplementation
|
||||
export const getRuntimeGitStatus = getRuntimeGitStatusImplementation
|
||||
export const setRuntimeGitStatusUpstreamRefWatch = setRuntimeGitStatusUpstreamRefWatchImplementation
|
||||
export const getRuntimeGitSubmoduleStatus = getRuntimeGitSubmoduleStatusImplementation
|
||||
export const getRuntimeGitIgnoredPaths = getRuntimeGitIgnoredPathsImplementation
|
||||
export const getRuntimeGitHistory = getRuntimeGitHistoryImplementation
|
||||
export const getRuntimeGitConflictOperation = getRuntimeGitConflictOperationImplementation
|
||||
export const abortRuntimeGitMerge = abortRuntimeGitMergeImplementation
|
||||
export const abortRuntimeGitRebase = abortRuntimeGitRebaseImplementation
|
||||
export const getRuntimeGitDiff = getRuntimeGitDiffImplementation
|
||||
export const getRuntimeGitBranchCompare = getRuntimeGitBranchCompareImplementation
|
||||
export const getRuntimeGitCommitCompare = getRuntimeGitCommitCompareImplementation
|
||||
export const getRuntimeGitUpstreamStatus = getRuntimeGitUpstreamStatusImplementation
|
||||
export const fetchRuntimeGit = fetchRuntimeGitImplementation
|
||||
export const syncRuntimeGitForkDefaultBranch = syncRuntimeGitForkDefaultBranchImplementation
|
||||
export const pullRuntimeGit = pullRuntimeGitImplementation
|
||||
export const fastForwardRuntimeGit = fastForwardRuntimeGitImplementation
|
||||
export const rebaseRuntimeGitFromBase = rebaseRuntimeGitFromBaseImplementation
|
||||
export const pushRuntimeGit = pushRuntimeGitImplementation
|
||||
export const getRuntimeGitBranchDiff = getRuntimeGitBranchDiffImplementation
|
||||
export const getRuntimeGitCommitDiff = getRuntimeGitCommitDiffImplementation
|
||||
export const commitRuntimeGit = commitRuntimeGitImplementation
|
||||
export const generateRuntimeCommitMessage = generateRuntimeCommitMessageImplementation
|
||||
export const discoverRuntimeCommitMessageModels = discoverRuntimeCommitMessageModelsImplementation
|
||||
export const cancelRuntimeGenerateCommitMessage = cancelRuntimeGenerateCommitMessageImplementation
|
||||
export const generateRuntimePullRequestFields = generateRuntimePullRequestFieldsImplementation
|
||||
export const cancelRuntimeGeneratePullRequestFields =
|
||||
cancelRuntimeGeneratePullRequestFieldsImplementation
|
||||
export const stageRuntimeGitPath = stageRuntimeGitPathImplementation
|
||||
export const bulkStageRuntimeGitPaths = bulkStageRuntimeGitPathsImplementation
|
||||
export const stageRuntimeGitWorktreeScope = stageRuntimeGitWorktreeScopeImplementation
|
||||
export const unstageRuntimeGitPath = unstageRuntimeGitPathImplementation
|
||||
export const bulkUnstageRuntimeGitPaths = bulkUnstageRuntimeGitPathsImplementation
|
||||
export const bulkDiscardRuntimeGitPaths = bulkDiscardRuntimeGitPathsImplementation
|
||||
export const discardRuntimeGitPath = discardRuntimeGitPathImplementation
|
||||
export const getRuntimeGitRemoteFileUrl = getRuntimeGitRemoteFileUrlImplementation
|
||||
export const getRuntimeGitRemoteCommitUrl = getRuntimeGitRemoteCommitUrlImplementation
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
import { dispatchWebRuntimeInitialTerminalBootstrap } from '../web-runtime-initial-terminal-bootstrap-dispatch'
|
||||
import { toRuntimeWorktreeSelector } from '../runtime-worktree-selector'
|
||||
import type { SessionTabsStreamEvent } from './state'
|
||||
import type { SessionTabsSnapshotHandler } from './visibility-resume-types'
|
||||
import { subscribeRuntimeEnvironment } from '../runtime-environment-pairing-refresh'
|
||||
|
||||
type Ref<T> = { current: T }
|
||||
@@ -52,30 +53,9 @@ export type ActiveSubscriptionArgs = {
|
||||
activeWorktreeRuntimeConnectionGeneration: number
|
||||
activeWorktreeRuntimePairingRevision: number | undefined
|
||||
workspaceSessionReady: boolean
|
||||
visibilitySnapshotReceipt: Ref<
|
||||
(
|
||||
environmentId: string,
|
||||
snapshot: RuntimeMobileSessionTabsResult,
|
||||
receivedFrame: number,
|
||||
runtimeId?: string
|
||||
) => void
|
||||
>
|
||||
visibilitySnapshotApply: Ref<
|
||||
(
|
||||
environmentId: string,
|
||||
snapshot: RuntimeMobileSessionTabsResult,
|
||||
receivedFrame: number,
|
||||
runtimeId?: string
|
||||
) => boolean
|
||||
>
|
||||
visibilitySnapshotAccepted: Ref<
|
||||
(
|
||||
environmentId: string,
|
||||
snapshot: RuntimeMobileSessionTabsResult,
|
||||
receivedFrame: number,
|
||||
runtimeId?: string
|
||||
) => void
|
||||
>
|
||||
visibilitySnapshotReceipt: Ref<SessionTabsSnapshotHandler<void>>
|
||||
visibilitySnapshotApply: Ref<SessionTabsSnapshotHandler<boolean>>
|
||||
visibilitySnapshotAccepted: Ref<SessionTabsSnapshotHandler<void>>
|
||||
}
|
||||
|
||||
/** Install the selected-worktree stream, which resumes immediately on visibility changes. */
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { RuntimeMobileSessionTabsResult } from '../../../../shared/runtime-types'
|
||||
import { getRuntimeEnvironmentRevision } from '../runtime-environment-revision'
|
||||
import { clearWebSessionCloseIntentsForOwner } from '../web-session-close-intent'
|
||||
import { clearWebSessionFocusIntentsForOwner } from '../web-session-focus-intent'
|
||||
@@ -16,7 +15,10 @@ import { loadInitialWebSessionTabs } from './load-initial'
|
||||
import { handleGlobalSessionEvent } from './global-session-events'
|
||||
import { VisibilityResumeCoordinator } from './visibility-resume-coordinator'
|
||||
import type { VisibilityResumeOmission, SessionTabsStreamEvent } from './state'
|
||||
import type { MirroredRuntimeEnvironment } from './visibility-resume-types'
|
||||
import type {
|
||||
MirroredRuntimeEnvironment,
|
||||
SessionTabsSnapshotHandler
|
||||
} from './visibility-resume-types'
|
||||
import { WEB_SESSION_TABS_VISIBILITY_RESUME_STAGGER_MS } from './state'
|
||||
import { subscribeRuntimeEnvironment } from '../runtime-environment-pairing-refresh'
|
||||
|
||||
@@ -26,30 +28,9 @@ export type GlobalSubscriptionRefs = {
|
||||
activeRuntimeEnvironmentId: Ref<string | null>
|
||||
activeRuntimeWorktreeKey: Ref<string | null>
|
||||
visibilityResumeOmissions: Ref<Map<string, VisibilityResumeOmission>>
|
||||
snapshotReceipt: Ref<
|
||||
(
|
||||
environmentId: string,
|
||||
snapshot: RuntimeMobileSessionTabsResult,
|
||||
receivedFrame: number,
|
||||
runtimeId?: string
|
||||
) => void
|
||||
>
|
||||
snapshotApply: Ref<
|
||||
(
|
||||
environmentId: string,
|
||||
snapshot: RuntimeMobileSessionTabsResult,
|
||||
receivedFrame: number,
|
||||
runtimeId?: string
|
||||
) => boolean
|
||||
>
|
||||
snapshotAccepted: Ref<
|
||||
(
|
||||
environmentId: string,
|
||||
snapshot: RuntimeMobileSessionTabsResult,
|
||||
receivedFrame: number,
|
||||
runtimeId?: string
|
||||
) => void
|
||||
>
|
||||
snapshotReceipt: Ref<SessionTabsSnapshotHandler<void>>
|
||||
snapshotApply: Ref<SessionTabsSnapshotHandler<boolean>>
|
||||
snapshotAccepted: Ref<SessionTabsSnapshotHandler<void>>
|
||||
ownerRevisions: Ref<Map<string, number | undefined>>
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,13 @@ import type {
|
||||
} from '../../../../shared/runtime-types'
|
||||
import type { TrackedWebSessionTabsWorktree } from './state'
|
||||
|
||||
export type SessionTabsSnapshotHandler<R> = (
|
||||
environmentId: string,
|
||||
snapshot: RuntimeMobileSessionTabsResult,
|
||||
receivedFrame: number,
|
||||
runtimeId?: string
|
||||
) => R
|
||||
|
||||
export type VisibilityResumeEnvironment = {
|
||||
trackedWorktrees: readonly TrackedWebSessionTabsWorktree[]
|
||||
inventoryReceived: boolean
|
||||
|
||||
Reference in New Issue
Block a user