Files
orca/tests/e2e/cross-version-wire/structured-agent-session-surface-execution.ts
T
Brennan Benson 7e2f29aaf1 Open native chats on the phone in place (#36315)
* Open native chats on the phone in place

* Preserve desktop host and agent when opening chats

* Preserve chat opening and captured lifecycle contracts

* test: cover sender focus and unconfirmed chat close outcomes

* test: exercise structured worker CLI lifecycle through runtime socket

* test: use checked parked mail observation in CLI integration

* test: validate provider notification correlation identities

* test: construct checked lifecycle and navigation fixtures

* Keep cross-program session proofs in their test program

* Localize mobile chat opening feedback and complete runtime contracts

* Use the mocked text component identity in History feedback proof

* Temporarily expose grown E2E type diagnostics

* Keep E2E timer and fetch fixtures in their runtime domains

* Reconcile decreased E2E wait pattern counts

* Preserve live test clocks and expose remaining type diagnostics

* Keep mobile session data types independent of native views

* Complete structured session wire surface fixtures

* Expose existing grown E2E type diagnostics temporarily

* Preserve checked wire fixture tab mock signatures

* Preserve worker redrive when automatic close is refused

* Honor automatic close admission before settlement and retirement

* test: retain History reveal and close publication race

* Match automation reveal callback to activation result

* Align CLI integration fixture with current settings and output

* Prevent retained chats from reopening removed workspaces

* Import notification types directly from their owner

* Unify local removal fences for accepted worktree aliases

* Keep cross-surface authority tests in the combined test project

* Initialize coordinator mail test workspace ownership

* Register the retained automation chat test workspace

* Use checked inputs in worker start failure test

* Preserve the automation test repository host literal
2026-10-31 18:57:24 -07:00

216 lines
8.9 KiB
TypeScript

import { availableRevealWorkspace } from '../../../src/main/runtime/session-tabs/structured-workspace-authority.test-fixture'
import { expect, vi } from 'vitest'
import type { structuredHostStub } from './structured-agent-session-host-fixture'
import { AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY } from '../../../src/shared/protocol-version'
import type { OrcaRuntimeService } from '../../../src/main/runtime/orca-runtime'
import type { StructuredSessionSurfaceRuntime } from '../../../src/main/runtime/agent-session/structured-agent-session-close'
import { RuntimeSubscriptionRegistry } from '../../../src/main/runtime/runtime-subscription-registry'
import { resetStructuredAgentSessionClosesBeforeCreateForTests } from '../../../src/main/runtime/rpc/methods/structured-agent-session-close-before-create'
import { AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session-create-capabilities'
import {
attachParams,
ATTENTION_READ,
paramsFor,
SESSION,
WORKSPACE,
STRUCTURED_CALLS
} from './structured-agent-session-surface-manifest'
import type {
AgentSessionWireBuild,
RpcClientIdentity,
RpcReply
} from './versioned-agent-session-wire'
export function runtimeStub(overrides: Record<string, unknown> = {}) {
// A fresh runtime remembers no chat closed before its create; scenarios reuse one session id.
resetStructuredAgentSessionClosesBeforeCreateForTests()
const subscriptions = new RuntimeSubscriptionRegistry()
let raw: ReturnType<
StructuredSessionSurfaceRuntime['currentLocalStructuredSessionPresentation']
> = {
worktreeId: WORKSPACE,
tabId: `tab-${SESSION}`
}
let saved = [{ ...raw }]
const presentation = {
currentLocalStructuredSessionPresentation: vi.fn<
StructuredSessionSurfaceRuntime['currentLocalStructuredSessionPresentation']
>((id) => (id === SESSION ? raw : null)),
captureStructuredAgentSessionPresentation: vi.fn<
StructuredSessionSurfaceRuntime['captureStructuredAgentSessionPresentation']
>((id) => (id === SESSION ? { raw, saved: [...saved] } : { raw: null, saved: [] })),
retireStructuredAgentSessionTabFromSnapshot: vi.fn<
StructuredSessionSurfaceRuntime['retireStructuredAgentSessionTabFromSnapshot']
>((id, tabId, worktreeId) => {
if (id !== SESSION || !raw || raw.tabId !== tabId || raw.worktreeId !== worktreeId) {
return false
}
raw = null
return true
}),
removeCapturedStructuredSessionSavedTabs: vi.fn<
StructuredSessionSurfaceRuntime['removeCapturedStructuredSessionSavedTabs']
>((id, targets) => {
if (id === SESSION && raw === null) {
saved = saved.filter(
(tab) =>
!targets.some(
(target) => target.tabId === tab.tabId && target.worktreeId === tab.worktreeId
)
)
}
})
} satisfies StructuredSessionSurfaceRuntime
return {
...presentation,
getRuntimeId: () => 'runtime-1',
getClientSettings: () => ({ experimentalStructuredNativeChat: true }),
ensureStructuredAgentSessionHost: async () => undefined,
prepareStructuredAgentSessionRevealWorkspace: vi.fn(availableRevealWorkspace),
getStructuredAgentSessionCreateSupport: async () => ({ supported: true }),
structuredAgentSessionLaunchSeedOptions: () => undefined,
resolveStructuredAgentSessionCreateIntent: async () => {
const {
envelope: _envelope,
providerHandle: _providerHandle,
...resolved
} = attachParams(null)
return resolved
},
publishStructuredAgentSessionTab: vi.fn<OrcaRuntimeService['publishStructuredAgentSessionTab']>(
async (input) =>
input.capture?.({
tableId: raw?.tabId ?? null,
presentation: { raw, saved: [...saved] }
})
),
subscribeStructuredAgentAvailability: (read: (agents: unknown[]) => void) => {
read([])
return () => {}
},
registerSubscriptionCleanup: subscriptions.register.bind(subscriptions),
registerOwnedSubscriptionCleanup: subscriptions.registerOwned.bind(subscriptions),
cleanupSubscription: subscriptions.cleanup.bind(subscriptions),
cleanupSubscriptionsByPrefix: subscriptions.cleanupByPrefix.bind(subscriptions),
...overrides
}
}
// Collect every streaming reply: refusals must arrive explicitly, never as silence.
export async function callBuild(
build: AgentSessionWireBuild,
method: string,
params: unknown,
client: RpcClientIdentity,
runtime: unknown = runtimeStub()
): Promise<RpcReply[]> {
const replies: RpcReply[] = []
await build.createDispatcher(runtime).dispatchStreaming(
{ id: `request-${method}`, authToken: 'cross-version-token', method, params },
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the build's RPC dispatcher serializes its own reply; result/error shape is asserted by each surface case.
(raw) => replies.push(JSON.parse(raw) as RpcReply),
client
)
return replies
}
// Require every declared method to reach its host and return its declared result in each build pairing.
export async function expectDeclaredSurfaceExecutes(
build: AgentSessionWireBuild,
hostCalls: ReturnType<typeof structuredHostStub>,
clientCapabilities: readonly string[]
): Promise<void> {
const retirement = vi.fn()
const runtime = runtimeStub({ retireStructuredAttention: retirement })
for (const { method, hostMethod, result, clientCapability } of STRUCTURED_CALLS) {
const atRestCreate =
method === 'agentSession.create' &&
build.capabilities.includes(AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY) &&
clientCapabilities.includes(AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY)
const expectedHostMethod = atRestCreate ? 'create' : hostMethod
const otherCreateMethod =
method === 'agentSession.create' ? (atRestCreate ? 'attach' : 'create') : null
// Shared host methods need per-call deltas; an earlier call must not satisfy this assertion.
const before = expectedHostMethod ? hostCalls[expectedHostMethod].mock.calls.length : 0
const otherBefore = otherCreateMethod ? hostCalls[otherCreateMethod].mock.calls.length : 0
if (method === 'agentSession.readVisual') {
hostCalls.getConversation.mockReturnValueOnce(null)
}
const replies = await callBuild(
build,
method,
paramsFor(method),
{ clientKind: 'runtime', clientCapabilities },
runtime
)
if (clientCapability && !clientCapabilities.includes(clientCapability)) {
expect(replies).toMatchObject([
{
ok: false,
error: { message: expect.stringContaining('structured_agent_session_unsupported') }
}
])
if (expectedHostMethod) {
expect(hostCalls[expectedHostMethod].mock.calls.length).toBe(before)
}
continue
}
if (expectedHostMethod) {
expect(
hostCalls[expectedHostMethod].mock.calls.length - before,
`${build.label}: ${method} did not reach the host: ${JSON.stringify(replies)}`
).toBe(1)
}
if (otherCreateMethod) {
expect(hostCalls[otherCreateMethod].mock.calls.length - otherBefore).toBe(0)
}
if (method === 'agentSession.close') {
const target = { worktreeId: WORKSPACE, tabId: `tab-${SESSION}` }
expect(runtime.captureStructuredAgentSessionPresentation).toHaveBeenCalledExactlyOnceWith(
SESSION
)
expect(runtime.retireStructuredAgentSessionTabFromSnapshot).toHaveBeenCalledExactlyOnceWith(
SESSION,
target.tabId,
target.worktreeId
)
expect(runtime.removeCapturedStructuredSessionSavedTabs).toHaveBeenCalledExactlyOnceWith(
SESSION,
[target]
)
expect(hostCalls.getSessionTabId(SESSION)).toBeNull()
expect(hostCalls.notifySessionTabHidden).toHaveBeenCalledExactlyOnceWith(SESSION)
expect(runtime.captureStructuredAgentSessionPresentation(SESSION)).toEqual({
raw: null,
saved: []
})
}
if (method === 'agentSession.acknowledgeAttention') {
expect(retirement).toHaveBeenCalledExactlyOnceWith(ATTENTION_READ)
}
for (const reply of replies) {
expect(
reply,
`${build.label}: ${method} was refused: ${JSON.stringify(reply)}`
).toMatchObject({ ok: true })
}
if (result) {
// Assert the exact envelope so registered-but-failing handlers cannot pass.
expect(replies, `${build.label}: ${method} must answer exactly once`).toHaveLength(1)
expect(replies[0], `${build.label}: ${method} answered off-contract`).toMatchObject({
ok: true,
result
})
}
}
}
export function expectItemDetailNegotiation(builds: readonly AgentSessionWireBuild[]): void {
expect(builds[0]?.capabilities).toContain(AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY)
for (const build of builds) {
expect(build.capabilities.includes(AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY)).toBe(
build.methodNames.includes('agentSession.item')
)
}
}