mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
Explicit worktree reads and session acknowledgements share the existing desktop dismissal authority. Desktop and phone copies preserve transport, physical workspace and session/cursor identity; Linux retains dismissible notification handles after banners expire. Mailbox consumption keeps its existing semantics. Fixes #37328 STA-13409 Co-authored-by: Pr1p <zwq19980411@gmail.com>
228 lines
9.2 KiB
TypeScript
228 lines
9.2 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/agent-session/agent-session-runtime-capabilities'
|
|
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/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<OrcaRuntimeService['acknowledgeNotificationAttention']>()
|
|
const runtime = runtimeStub({ acknowledgeNotificationAttention: 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({
|
|
reads: [
|
|
{
|
|
...ATTENTION_READ,
|
|
scope: {
|
|
executionHostId: 'local',
|
|
wslDistro: null,
|
|
workspaceId: WORKSPACE,
|
|
workspaceKind: 'git-worktree'
|
|
}
|
|
}
|
|
]
|
|
})
|
|
}
|
|
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')
|
|
)
|
|
}
|
|
}
|