Fix npm server connection actions

This commit is contained in:
Jinwoo-H
2026-08-12 22:46:53 -04:00
parent a5bff28147
commit 255d033750
13 changed files with 242 additions and 27 deletions
+4 -2
View File
@@ -154,7 +154,7 @@ render credential-bearing URLs as compact clickable actions:
```text
Orca server is ready
Connect
Connect (click for options, or Cmd/Ctrl-click)
Web browser Open web client
Desktop app Add this host
@@ -163,7 +163,9 @@ Server details
Pairing uses ws://100.64.1.20:6768
```
Redirected output prints the full web client and pairing URLs.
Redirected output prints the full web client and pairing URLs. In Orca terminals, the desktop
action opens the Remote Orca Servers form with the access link prefilled; its action menu also
provides a copy fallback.
For supervisors, request the versioned single-line JSON contract:
+3 -2
View File
@@ -83,7 +83,7 @@ Supported interactive terminals render credential-bearing URLs as compact clicka
```text
Orca server is ready
Connect
Connect (click for options, or Cmd/Ctrl-click)
Web browser Open web client
Desktop app Add this host
@@ -93,7 +93,8 @@ Server details
```
Redirected output prints the full web client and pairing URLs. `--json` remains the stable
machine-readable contract.
machine-readable contract. In Orca terminals, the desktop action opens the Remote Orca Servers
form with the access link prefilled; its action menu also provides a copy fallback.
### Automation
+3 -1
View File
@@ -61,7 +61,9 @@ describe('ServeReadinessPublisher', () => {
await publisher.publish(ready, { mode: 'human' })
const output = write.mock.calls[0]?.[0] ?? ''
expect(output).toContain('Orca server is ready\n\nConnect')
expect(output).toContain(
'Orca server is ready\n\nConnect (click for options, or Cmd/Ctrl-click)'
)
expect(output).toContain(
'\u001B]8;;https://orca.example.test/runtime/web-index.html#pairing=secret\u001B\\Open web client\u001B]8;;\u001B\\'
)
+1 -1
View File
@@ -110,7 +110,7 @@ function renderHumanReadiness(
const lines = ['Orca server is ready', '']
if (readiness.pairing.available) {
lines.push('Connect')
lines.push('Connect (click for options, or Cmd/Ctrl-click)')
if (readiness.pairing.webClientUrl) {
lines.push(
` Web browser ${terminalHyperlink('Open web client', readiness.pairing.webClientUrl)}`
@@ -71,7 +71,7 @@ type RuntimeEnvironmentsPaneProps = {
setActiveRuntimeEnvironmentPreference: (environmentId: string | null) => Promise<boolean>
canGeneratePairingUrl?: boolean
allowLocalRuntime?: boolean
addServerIntentSignal?: number
addServerIntent?: { signal: number; accessLink?: string }
}
export type RuntimeHostDetails = {
@@ -263,7 +263,7 @@ export function RuntimeEnvironmentsPane({
setActiveRuntimeEnvironmentPreference,
canGeneratePairingUrl = true,
allowLocalRuntime = true,
addServerIntentSignal
addServerIntent
}: RuntimeEnvironmentsPaneProps): React.JSX.Element {
const [environments, setEnvironments] = useState<PublicKnownRuntimeEnvironment[]>([])
const [isLoading, setIsLoading] = useState(false)
@@ -294,6 +294,13 @@ export function RuntimeEnvironmentsPane({
(state) => state.setRemoteServerUpdateDialogOpen
)
const consumedAddServerIntentSignalRef = useRef(0)
if (addServerIntent && consumedAddServerIntentSignalRef.current !== addServerIntent.signal) {
// Why: consume Settings deep links before paint so the pairing form never flashes empty.
consumedAddServerIntentSignalRef.current = addServerIntent.signal
setPairingCode(addServerIntent.accessLink ?? '')
setAddServerFailure(null)
setAddServerFormOpen(true)
}
const mountedRef = useMountedRef()
const updateCheckHint = getUpdateCheckHint()
const activeValue =
@@ -430,19 +437,6 @@ export function RuntimeEnvironmentsPane({
useEffect(() => {
void refreshRemoteServerUpdates()
}, [environmentIdsKey, refreshRemoteServerUpdates])
useEffect(() => {
if (
!addServerIntentSignal ||
consumedAddServerIntentSignalRef.current === addServerIntentSignal
) {
return
}
consumedAddServerIntentSignalRef.current = addServerIntentSignal
// Why: composer deep-links should land on the existing pairing form, not just
// the server list.
setAddServerFormOpen(true)
}, [addServerIntentSignal])
const closeAddServerForm = (): void => {
if (isSaving) {
return
@@ -381,7 +381,10 @@ function Settings(): React.JSX.Element {
)
const [quickCommandAddIntentSignal, setQuickCommandAddIntentSignal] = useState(0)
const [sshHostAddIntentSignal, setSshHostAddIntentSignal] = useState(0)
const [remoteServerAddIntentSignal, setRemoteServerAddIntentSignal] = useState(0)
const [remoteServerAddIntent, setRemoteServerAddIntent] = useState<{
signal: number
accessLink?: string
}>({ signal: 0 })
const [hasUnsavedCommitPromptChanges, setHasUnsavedCommitPromptChanges] = useState(false)
const [hasUnsavedBranchPromptChanges, setHasUnsavedBranchPromptChanges] = useState(false)
const [sourceControlAiPromptDiscardSignal, setSourceControlAiPromptDiscardSignal] = useState(0)
@@ -696,7 +699,12 @@ function Settings(): React.JSX.Element {
} else if (settingsNavigationTarget.intent === 'add-ssh-host') {
setSshHostAddIntentSignal((signal) => signal + 1)
} else if (settingsNavigationTarget.intent === 'add-remote-orca-server') {
setRemoteServerAddIntentSignal((signal) => signal + 1)
setRemoteServerAddIntent((current) => ({
signal: current.signal + 1,
...(settingsNavigationTarget.accessLink
? { accessLink: settingsNavigationTarget.accessLink }
: {})
}))
}
setMountedSectionIds((previous) => {
if (previous.has(paneSectionId)) {
@@ -1728,7 +1736,7 @@ function Settings(): React.JSX.Element {
setActiveRuntimeEnvironmentPreference={setActiveRuntimeEnvironmentPreference}
canGeneratePairingUrl={!isWebClient}
allowLocalRuntime={!isWebClient}
addServerIntentSignal={remoteServerAddIntentSignal}
addServerIntent={remoteServerAddIntent}
/>
) : null}
</SettingsSection>
@@ -11,6 +11,7 @@ import {
} from './terminal-link-action-request'
import { handleOscLink } from './terminal-osc-link-routing'
import { handleTerminalHttpLink } from './terminal-url-link-hit-testing'
import { encodePairingOffer } from '../../../../shared/pairing'
const openUrl = vi.fn()
const createBrowserTab = vi.fn()
@@ -250,4 +251,78 @@ describe('terminal link action routing', () => {
).toBe(true)
expect(request.mock.calls[0][0].destination).toBe(destination)
})
it.each([
['macOS', 'Macintosh', { metaKey: true, ctrlKey: false }],
['Windows', 'Windows', { metaKey: false, ctrlKey: true }],
['Linux', 'Linux', { metaKey: false, ctrlKey: true }]
])('opens a valid pairing link with the platform modifier on %s', (_name, userAgent, keys) => {
vi.stubGlobal('navigator', { userAgent })
const openPairingLink = vi.fn(() => true)
const accessLink = encodePairingOffer({
v: 2,
endpoint: 'ws://192.168.1.10:6768',
deviceToken: 'device-token',
publicKeyB64: 'public-key'
})
expect(
handleOscLink(
accessLink,
{ ...plainEvent(), ...keys },
{
worktreeId: 'wt-1',
worktreePath: '/repo',
openPairingLink
}
)
).toBe(true)
expect(openPairingLink).toHaveBeenCalledExactlyOnceWith(accessLink)
})
it('offers add and copy actions for a plain pairing-link click', () => {
const request = vi.fn()
const openPairingLink = vi.fn(() => true)
const accessLink = encodePairingOffer({
v: 2,
endpoint: 'wss://server.example.test/runtime',
deviceToken: 'device-token',
publicKeyB64: 'public-key'
})
expect(
handleOscLink(accessLink, plainEvent(), {
worktreeId: 'wt-1',
worktreePath: '/repo',
linkActionContext: actionContext(request),
openPairingLink
})
).toBe(true)
expect(request).toHaveBeenCalledWith(
expect.objectContaining({
destination: accessLink,
kind: 'url',
primary: expect.objectContaining({ label: 'Add host' })
})
)
request.mock.calls[0][0].primary.run()
expect(openPairingLink).toHaveBeenCalledExactlyOnceWith(accessLink)
})
it('rejects unrelated custom-protocol OSC links', () => {
const openPairingLink = vi.fn(() => true)
expect(
handleOscLink(
'orca://unknown?code=secret',
{ ...plainEvent(), metaKey: true },
{
worktreeId: 'wt-1',
worktreePath: '/repo',
openPairingLink
}
)
).toBe(false)
expect(openPairingLink).not.toHaveBeenCalled()
})
})
@@ -12,8 +12,13 @@ import {
type TerminalLinkRoutingPreferenceRequester
} from './terminal-url-link-hit-testing'
import type { HttpLinkSourceOwner } from '@/lib/http-link-routing'
import type { TerminalLinkActionContext } from './terminal-link-action-request'
import {
requestTerminalLinkAction,
type TerminalLinkActionContext
} from './terminal-link-action-request'
import { handleTerminalFileLink } from './terminal-file-link-actions'
import { isTerminalPairingLink, openTerminalPairingLink } from './terminal-pairing-link-actions'
import { translate } from '@/i18n/i18n'
type TerminalLinkEvent = Pick<MouseEvent, 'metaKey' | 'ctrlKey'> &
Partial<
@@ -55,6 +60,7 @@ export function handleOscLink(
requestOpenLinksInAppPreference?: TerminalLinkRoutingPreferenceRequester
linkActionContext?: TerminalLinkActionContext | null
actionDestinations?: TerminalHttpLinkActionDestinations
openPairingLink?: (accessLink: string) => boolean
}
): boolean {
if (!isDesktopOscLinkActivation(event)) {
@@ -124,6 +130,31 @@ export function handleOscLink(
)
}
if (parsed.protocol === 'orca:' && isTerminalPairingLink(rawText)) {
const openPairingLink = (): boolean => {
return (deps.openPairingLink ?? openTerminalPairingLink)(rawText)
}
const runPairingLink = (): void => {
openPairingLink()
}
if (isTerminalLinkDirectActivation(event)) {
return finish(openPairingLink())
}
return finish(
requestTerminalLinkAction(event as MouseEvent, deps.linkActionContext, {
destination: rawText,
kind: 'url',
primary: {
label: translate(
'auto.components.terminal.pane.TerminalLinkActionPopover.addHost',
'Add host'
),
run: runPairingLink
}
})
)
}
if (parsed.protocol === 'file:') {
// Why: file:// URIs should open inside Orca, not via the OS default editor
// (shell.openPath). We extract the path from the URI and route it through
@@ -0,0 +1,53 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { encodePairingOffer } from '../../../../shared/pairing'
import { openTerminalPairingLink } from './terminal-pairing-link-actions'
const mocks = vi.hoisted(() => ({
openSettingsPage: vi.fn(),
openSettingsTarget: vi.fn()
}))
vi.mock('@/store', () => ({
useAppStore: {
getState: () => mocks
}
}))
describe('openTerminalPairingLink', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('opens the remote server form with the access link prefilled', () => {
const accessLink = encodePairingOffer({
v: 2,
endpoint: 'ws://192.168.1.10:6768',
deviceToken: 'device-token',
publicKeyB64: 'public-key'
})
expect(openTerminalPairingLink(accessLink)).toBe(true)
expect(mocks.openSettingsTarget).toHaveBeenCalledWith({
pane: 'servers',
repoId: null,
intent: 'add-remote-orca-server',
accessLink
})
expect(mocks.openSettingsPage).toHaveBeenCalledOnce()
})
it('does not route malformed or mobile-only pairing links', () => {
const mobileLink = encodePairingOffer({
v: 2,
endpoint: 'ws://192.168.1.10:6768',
deviceToken: 'device-token',
publicKeyB64: 'public-key',
scope: 'mobile'
})
expect(openTerminalPairingLink('orca://unknown?code=secret')).toBe(false)
expect(openTerminalPairingLink(mobileLink)).toBe(false)
expect(mocks.openSettingsTarget).not.toHaveBeenCalled()
expect(mocks.openSettingsPage).not.toHaveBeenCalled()
})
})
@@ -0,0 +1,21 @@
import { parseHostAccessLink } from '../../../../shared/remote-pairing-address'
import { useAppStore } from '@/store'
export function openTerminalPairingLink(accessLink: string): boolean {
if (!isTerminalPairingLink(accessLink)) {
return false
}
const { openSettingsPage, openSettingsTarget } = useAppStore.getState()
openSettingsTarget({
pane: 'servers',
repoId: null,
intent: 'add-remote-orca-server',
accessLink
})
openSettingsPage()
return true
}
export function isTerminalPairingLink(accessLink: string): boolean {
return parseHostAccessLink(accessLink).ok
}
+2 -1
View File
@@ -2875,7 +2875,8 @@
"copyLink": "Copy link",
"copied": "Copied",
"copiedLink": "Copied link",
"copyLinkFailed": "Failed to copy link"
"copyLinkFailed": "Failed to copy link",
"addHost": "Add host"
},
"TerminalSessionStateSaveFailureDialog": {
"6bee0c8f17": "Open Disk Space Analyzer",
@@ -68,6 +68,7 @@ export type SettingsNavigationTarget = {
hostId?: ExecutionHostId
sectionId?: string
intent?: (typeof SETTINGS_NAV_INTENTS)[number]
accessLink?: string
}
export function isSettingsNavigationTarget(value: unknown): value is SettingsNavigationTarget {
@@ -83,7 +84,9 @@ export function isSettingsNavigationTarget(value: unknown): value is SettingsNav
(typeof target.hostId === 'string' && parseExecutionHostId(target.hostId) !== null)) &&
(target.sectionId === undefined || typeof target.sectionId === 'string') &&
(target.intent === undefined ||
(typeof target.intent === 'string' && SETTINGS_NAV_INTENT_SET.has(target.intent)))
(typeof target.intent === 'string' && SETTINGS_NAV_INTENT_SET.has(target.intent))) &&
(target.accessLink === undefined ||
(target.intent === 'add-remote-orca-server' && typeof target.accessLink === 'string'))
)
}
+24
View File
@@ -1985,6 +1985,30 @@ describe('createUISlice settings navigation', () => {
})
})
it('accepts an access-link prefill only for the remote server intent', () => {
const store = createUIStore()
const accessLink = 'orca://pair?code=secret'
store.getState().openSettingsTarget({
pane: 'servers',
repoId: null,
intent: 'add-remote-orca-server',
accessLink
})
expect(store.getState().settingsNavigationTarget).toEqual({
pane: 'servers',
repoId: null,
intent: 'add-remote-orca-server',
accessLink
})
const openSettingsTarget = store.getState().openSettingsTarget as unknown as (
target: unknown
) => void
expect(() => openSettingsTarget({ pane: 'servers', repoId: null, accessLink })).toThrowError(
'openSettingsTarget received an invalid navigation target'
)
})
it('rejects malformed settings targets before storing them', () => {
const store = createUIStore()
const openSettingsTarget = store.getState().openSettingsTarget as unknown as (