mirror of
https://github.com/stablyai/orca.git
synced 2026-10-12 16:03:03 +00:00
refactor(agent-launch): drop the unshipped viewMode field and trusted local caller id
Both were inert in step 1 and existed only for step 2. agent.launch will become a public plugin API, so every wire field is permanent once shipped; a top-level viewMode reads as "choose terminal vs chat", which the host decides. Step 2 introduces placement and view intent under a placement object instead.
This commit is contained in:
@@ -337,8 +337,7 @@ async function createTerminalSurface(
|
||||
worktreeId,
|
||||
agent: intent.agent,
|
||||
...(startupPrompt ? { startupPrompt } : {}),
|
||||
...terminalLaunchInputs(intent),
|
||||
...(intent.viewMode ? { viewMode: intent.viewMode } : {})
|
||||
...terminalLaunchInputs(intent)
|
||||
})
|
||||
return {
|
||||
outcome: {
|
||||
|
||||
@@ -35,8 +35,6 @@ export type AgentLaunchSurfaceFactory = {
|
||||
launchSource?: string
|
||||
/** The caller-minted pane to create; refused with `AgentLaunchPaneAlreadyLiveError` if live. */
|
||||
paneKey?: string
|
||||
/** How the tab first shows, as the caller would have opened it. */
|
||||
viewMode?: 'terminal' | 'chat'
|
||||
}): Promise<{
|
||||
handle: string
|
||||
/** The pane this create minted; a factory whose runtime reports none omits it, never invents. */
|
||||
|
||||
@@ -12,8 +12,6 @@ import { AGENT_LAUNCH_RUNTIME_CAPABILITY } from '../../shared/agent-launch-runti
|
||||
type AdvertisedClient = {
|
||||
clientKind?: 'mobile' | 'runtime'
|
||||
clientCapabilities?: readonly RuntimeCapability[]
|
||||
trustedLocalCallerId?: string
|
||||
pairedDeviceId?: string
|
||||
}
|
||||
|
||||
const { handlers, advertised } = vi.hoisted(
|
||||
@@ -59,7 +57,6 @@ vi.mock('../runtime/rpc/dispatcher', () => ({
|
||||
|
||||
const { registerRuntimeHandlers } = await import('./runtime')
|
||||
const { supportsAgentLaunch } = await import('../runtime/rpc/methods/agent-launch')
|
||||
const { agentLaunchOperationCallerKey } = await import('../runtime/rpc/methods/agent-launch-replay')
|
||||
|
||||
function rendererEvent() {
|
||||
const mainFrame = {}
|
||||
@@ -129,20 +126,4 @@ describe('desktop renderer reaching agent.launch on its own main process', () =>
|
||||
)
|
||||
expect(supportsAgentLaunch(streaming)).toBe(true)
|
||||
})
|
||||
|
||||
// Why: the desktop has no paired device, so without its own host-set identity every
|
||||
// replay-protected launch it sent was refused with agent_session_identity_required.
|
||||
it('gives a replay-protected launch a caller identity of its own', () => {
|
||||
invoke('runtime:call', {
|
||||
method: 'agent.launchReplay',
|
||||
params: { agent: 'claude', target: { kind: 'existing', worktree: 'id:wt-7' } }
|
||||
})
|
||||
|
||||
const client = onlyAdvertisedClient(advertised.unary)
|
||||
expect(agentLaunchOperationCallerKey(client)).toBe('trusted-local:desktop-renderer')
|
||||
// Negative control: the same client kind with no host-set identity is still refused.
|
||||
expect(() =>
|
||||
agentLaunchOperationCallerKey({ clientKind: client.clientKind, pairedDeviceId: undefined })
|
||||
).toThrow('agent_session_identity_required')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -78,7 +78,6 @@ export function registerRuntimeHandlers(runtime: OrcaRuntimeService): void {
|
||||
},
|
||||
{
|
||||
clientId: 'desktop-renderer',
|
||||
trustedLocalCallerId: 'desktop-renderer',
|
||||
clientKind: 'runtime',
|
||||
connectionId: desktopSenders.connectionIdFor(event.sender),
|
||||
clientCapabilities: DESKTOP_RENDERER_RUNTIME_CLIENT_CAPABILITIES
|
||||
@@ -121,7 +120,6 @@ export function registerRuntimeHandlers(runtime: OrcaRuntimeService): void {
|
||||
{
|
||||
signal: controller.signal,
|
||||
clientId: 'desktop-renderer',
|
||||
trustedLocalCallerId: 'desktop-renderer',
|
||||
clientKind: 'runtime',
|
||||
connectionId,
|
||||
clientCapabilities: DESKTOP_RENDERER_RUNTIME_CLIENT_CAPABILITIES
|
||||
|
||||
@@ -75,9 +75,6 @@ export type RpcContext = {
|
||||
clientId?: string
|
||||
// Why: navigation is keyed by revocable device identity, never by the bearer credential or transient socket id.
|
||||
pairedDeviceId?: string
|
||||
// Why: an in-process caller (the desktop renderer over IPC) has no paired device, yet its identity
|
||||
// is fixed by the host, so replay-protected operations can key on it.
|
||||
trustedLocalCallerId?: string
|
||||
// Why: lets handlers gate mobile payload truncation to phones only; undefined for in-process callers → treat as full-class (no clip).
|
||||
clientKind?: 'mobile' | 'runtime'
|
||||
// Why: negotiation is bound to the authenticated socket, never asserted by a destructive request.
|
||||
|
||||
@@ -8,8 +8,6 @@ export type RpcDispatchStreamingOptions = {
|
||||
signal?: AbortSignal
|
||||
clientId?: string
|
||||
pairedDeviceId?: string
|
||||
/** Set only by an in-process transport (desktop IPC); never derived from anything a peer sends. */
|
||||
trustedLocalCallerId?: string
|
||||
clientKind?: 'mobile' | 'runtime'
|
||||
clientCapabilities?: readonly RuntimeCapability[]
|
||||
updateClientCapabilities?: (capabilities: readonly RuntimeCapability[]) => void
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { z } from 'zod'
|
||||
import { RpcDispatcher } from './dispatcher'
|
||||
import { defineMethod, type RpcRequest } from './core'
|
||||
import type { OrcaRuntimeService } from '../orca-runtime'
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the dispatcher reads only getRuntimeId on this path; any other runtime member it touched would throw rather than read a wrong value.
|
||||
const RUNTIME = { getRuntimeId: () => 'test-runtime' } as OrcaRuntimeService
|
||||
|
||||
const INSPECT = defineMethod({
|
||||
name: 'test.inspectCaller',
|
||||
params: z.object({}),
|
||||
handler: (_params, { trustedLocalCallerId }) => ({
|
||||
trustedLocalCallerId: trustedLocalCallerId ?? null
|
||||
})
|
||||
})
|
||||
|
||||
function request(): RpcRequest {
|
||||
return { id: 'req-1', authToken: 'tok', method: 'test.inspectCaller', params: {} }
|
||||
}
|
||||
|
||||
describe('the in-process caller identity a transport vouches for', () => {
|
||||
it('reaches a unary handler from the transport that set it', async () => {
|
||||
const dispatcher = new RpcDispatcher({ runtime: RUNTIME, methods: [INSPECT] })
|
||||
|
||||
const response = await dispatcher.dispatch(request(), {
|
||||
clientKind: 'runtime',
|
||||
trustedLocalCallerId: 'desktop-renderer'
|
||||
})
|
||||
|
||||
expect(response).toMatchObject({
|
||||
ok: true,
|
||||
result: { trustedLocalCallerId: 'desktop-renderer' }
|
||||
})
|
||||
})
|
||||
|
||||
it('is absent for a transport that set none, whatever the request carries', async () => {
|
||||
const dispatcher = new RpcDispatcher({ runtime: RUNTIME, methods: [INSPECT] })
|
||||
|
||||
const response = await dispatcher.dispatch(
|
||||
{ ...request(), params: { trustedLocalCallerId: 'desktop-renderer' } },
|
||||
{ clientKind: 'runtime' }
|
||||
)
|
||||
|
||||
expect(response).toMatchObject({ ok: true, result: { trustedLocalCallerId: null } })
|
||||
})
|
||||
})
|
||||
@@ -116,7 +116,6 @@ export class RpcDispatcher {
|
||||
: undefined,
|
||||
requestId: request.id,
|
||||
clientId: options?.clientId,
|
||||
trustedLocalCallerId: options?.trustedLocalCallerId,
|
||||
clientKind: options?.clientKind,
|
||||
clientCapabilities: options?.clientCapabilities,
|
||||
updateClientCapabilities: options?.updateClientCapabilities,
|
||||
|
||||
@@ -29,16 +29,11 @@ import type { AgentLaunchParams } from './agent-launch-schemas'
|
||||
|
||||
/** Remote replay needs the paired-device subject because its bearer credential can rotate. */
|
||||
export function agentLaunchOperationCallerKey(
|
||||
context: Pick<RpcContext, 'pairedDeviceId' | 'clientKind' | 'trustedLocalCallerId'>
|
||||
context: Pick<RpcContext, 'pairedDeviceId' | 'clientKind'>
|
||||
): string {
|
||||
if (context.clientKind === undefined) {
|
||||
return 'trusted-local:runtime'
|
||||
}
|
||||
// The desktop renderer is in-process like the CLI, but advertises capabilities as a client.
|
||||
const trustedLocalCallerId = context.trustedLocalCallerId?.trim()
|
||||
if (trustedLocalCallerId) {
|
||||
return `trusted-local:${trustedLocalCallerId}`
|
||||
}
|
||||
const pairedDeviceId = context.pairedDeviceId?.trim()
|
||||
if (!pairedDeviceId) {
|
||||
throw new Error('agent_session_identity_required')
|
||||
|
||||
@@ -118,7 +118,6 @@ export function agentLaunchSurfaceFactory(
|
||||
cwd,
|
||||
launchSource,
|
||||
paneKey,
|
||||
viewMode,
|
||||
options
|
||||
}) => {
|
||||
const launchPreferences = toAgentLaunchPreferences(options)
|
||||
@@ -144,8 +143,6 @@ export function agentLaunchSurfaceFactory(
|
||||
// A live reserved pane would be attached, not launched into, so the runtime refuses it.
|
||||
...(paneKey ? { ...paneIdentity(paneKey), requireFreshPane: true } : {}),
|
||||
...(launchSource ? { launchSource } : {}),
|
||||
// Not `presentation`: a focused create takes the renderer path, which drops the pane identity.
|
||||
...(viewMode ? { viewMode } : {}),
|
||||
onPtySpawnDispatched: terminalSpawn.onPtySpawnDispatched
|
||||
})
|
||||
const terminal = await created.catch(terminalSpawn.rethrow)
|
||||
|
||||
@@ -80,16 +80,6 @@ describe('a terminal launch into an existing workspace', () => {
|
||||
expect(terminalOptions(runtime)).toMatchObject({ launchPreferences: PICKS })
|
||||
})
|
||||
|
||||
it('opens the tab in the view mode the caller asked for, without asking for a focused create', async () => {
|
||||
const runtime = runtimeStub({ settings: TERMINAL_ONLY })
|
||||
|
||||
await launch({ ...EXISTING_LAUNCH, viewMode: 'chat' }, runtime)
|
||||
|
||||
// A focused create takes the renderer path, which drops the caller's pane identity.
|
||||
expect(terminalOptions(runtime)).toMatchObject({ viewMode: 'chat' })
|
||||
expect(terminalOptions(runtime)).not.toHaveProperty('presentation')
|
||||
})
|
||||
|
||||
it('drops options that are not launch preferences', async () => {
|
||||
const runtime = runtimeStub({ settings: TERMINAL_ONLY })
|
||||
|
||||
|
||||
@@ -105,7 +105,6 @@ async function agentLaunchIntent(
|
||||
...(params.cwd ? { cwd: params.cwd } : {}),
|
||||
...(params.launchSource ? { launchSource: params.launchSource } : {}),
|
||||
...(params.paneKey ? { paneKey: params.paneKey } : {}),
|
||||
...(params.viewMode ? { viewMode: params.viewMode } : {}),
|
||||
...(params.sessionId ? { sessionId: params.sessionId } : {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,6 @@ export class RpcStreamingDispatcher {
|
||||
subscriptionRegistrationVersion,
|
||||
clientId: options?.clientId,
|
||||
pairedDeviceId: options?.pairedDeviceId,
|
||||
trustedLocalCallerId: options?.trustedLocalCallerId,
|
||||
clientKind: options?.clientKind,
|
||||
clientCapabilities: options?.clientCapabilities,
|
||||
updateClientCapabilities: options?.updateClientCapabilities,
|
||||
@@ -193,7 +192,6 @@ export class RpcStreamingDispatcher {
|
||||
connectionId: options?.connectionId,
|
||||
clientId: options?.clientId,
|
||||
pairedDeviceId: options?.pairedDeviceId,
|
||||
trustedLocalCallerId: options?.trustedLocalCallerId,
|
||||
clientKind: options?.clientKind,
|
||||
clientCapabilities: options?.clientCapabilities,
|
||||
updateClientCapabilities: options?.updateClientCapabilities,
|
||||
|
||||
@@ -93,8 +93,6 @@ export type AgentLaunchIntent = {
|
||||
/** The `tabId:leafId` a terminal launch creates its pane under, for a caller that places its own
|
||||
* tabs. Not a route input; refused when that pane is already live. */
|
||||
paneKey?: string
|
||||
/** How a terminal launched into an existing workspace first shows its tab. Not a route input. */
|
||||
viewMode?: 'terminal' | 'chat'
|
||||
/** The caller-minted id of the chat session a structured launch creates. Not a route input;
|
||||
* refused when that session already exists. */
|
||||
sessionId?: string
|
||||
|
||||
@@ -102,12 +102,6 @@ export const AgentLaunchFields = z.object({
|
||||
)
|
||||
}, 'Malformed launch pane key')
|
||||
.optional(),
|
||||
/**
|
||||
* How a terminal launch in an existing workspace first shows its tab, so the tab opens as the
|
||||
* caller would have opened it. Presentation only; a chat and a new workspace's startup terminal
|
||||
* ignore it, and an older host strips it.
|
||||
*/
|
||||
viewMode: z.enum(['terminal', 'chat']).optional(),
|
||||
/**
|
||||
* The id of the chat session a structured launch creates, minted by the caller so it knows which
|
||||
* conversation it started before the reply arrives. Refused when a session with this id already
|
||||
|
||||
Reference in New Issue
Block a user