mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 09:07:40 +00:00
* fix(native-chat): retire acknowledged send echoes after catch-up * test: follow phone lint rules in reconnect regression * test: format reconnect message expectation * Keep current chat requests independent of loaded history * Read current chat requests in bounded cursor pieces * Fix current request subscription static checks * Match generated request schema import ordering * fix(native-chat): correct current request type boundaries * Exercise current request reads in wire compatibility inventory * Align current request fixtures with provider commit and RPC surface * test(native-chat): include current request browser capability * Keep the mobile chat session hook within its line budget * Format mobile current request readers * fix(native-chat): reopen once for a current-request version whose read keeps failing A deterministic read failure reopened the subscription, whose first frame re-read the same version and reset the backoff, so the pane resubscribed every 750 ms forever. * test(native-chat): type the current-request snapshot fixture * test(native-chat): wait for the hydrated open before driving the subscription
137 lines
5.6 KiB
TypeScript
137 lines
5.6 KiB
TypeScript
import { expect, vi } from 'vitest'
|
|
import { RuntimeSubscriptionRegistry } from '../../../src/main/runtime/runtime-subscription-registry'
|
|
import { AGENT_SESSION_CREATE_MESSAGE_RUNTIME_CAPABILITY } from '../../../src/shared/agent-session-create-capabilities'
|
|
import {
|
|
attachParams,
|
|
ATTENTION_READ,
|
|
paramsFor,
|
|
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> = {}): unknown {
|
|
const subscriptions = new RuntimeSubscriptionRegistry()
|
|
return {
|
|
getRuntimeId: () => 'runtime-1',
|
|
getClientSettings: () => ({ experimentalStructuredNativeChat: true }),
|
|
ensureStructuredAgentSessionHost: async () => undefined,
|
|
getStructuredAgentSessionCreateSupport: async () => ({ supported: true }),
|
|
structuredAgentSessionLaunchSeedOptions: () => undefined,
|
|
resolveStructuredAgentSessionCreateIntent: async () => {
|
|
const {
|
|
envelope: _envelope,
|
|
providerHandle: _providerHandle,
|
|
...resolved
|
|
} = attachParams(null)
|
|
return resolved
|
|
},
|
|
publishStructuredAgentSessionTab: () => {},
|
|
registerSubscriptionCleanup: subscriptions.register.bind(subscriptions),
|
|
registerOwnedSubscriptionCleanup: subscriptions.registerOwned.bind(subscriptions),
|
|
cleanupSubscription: subscriptions.cleanup.bind(subscriptions),
|
|
cleanupSubscriptionsByPrefix: subscriptions.cleanupByPrefix.bind(subscriptions),
|
|
...overrides
|
|
}
|
|
}
|
|
|
|
/** Every reply one call produced. Streaming methods answer more than once, and a
|
|
* refusal has to arrive as a reply rather than 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
|
|
}
|
|
|
|
/**
|
|
* The one thing this suite exists to guarantee, written once and applied per
|
|
* build: every method the manifest declares is not merely registered but reaches
|
|
* its host method on this call, answers, and answers with its declared result.
|
|
*
|
|
* Written as a helper rather than inline because a build passing it is the claim,
|
|
* and each skew that registers the surface owes the same claim — a check that
|
|
* covers one method leaves the rest registered-but-unusable behind a green suite.
|
|
*/
|
|
export async function expectDeclaredSurfaceExecutes(
|
|
build: AgentSessionWireBuild,
|
|
hostCalls: Record<string, ReturnType<typeof vi.fn>>,
|
|
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
|
|
// Two methods share one host method, so "has been called" would already be
|
|
// true from the earlier one: only this call's own delta pins the pairing.
|
|
const before = expectedHostMethod ? hostCalls[expectedHostMethod].mock.calls.length : 0
|
|
const otherBefore = otherCreateMethod ? hostCalls[otherCreateMethod].mock.calls.length : 0
|
|
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.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) {
|
|
// The declared answer, not merely a non-refusal: a handler that is
|
|
// registered and returns an execution error, or hands back someone else's
|
|
// envelope, fails here rather than passing as "reached the host".
|
|
expect(replies, `${build.label}: ${method} must answer exactly once`).toHaveLength(1)
|
|
expect(replies[0], `${build.label}: ${method} answered off-contract`).toMatchObject({
|
|
ok: true,
|
|
result
|
|
})
|
|
}
|
|
}
|
|
}
|