mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
* fix(native-chat): save every chat reply and tool output whole; bound only the copy sent to clients Orca cut Codex replies, every agent's reasoning, plans, prompts and all tool output to a 16 KB head when it saved them, and threw the rest away. The limit existed so a reconnecting client is not replayed a huge row, but it was applied at save time, so every transport limit became permanent data loss. The journal now keeps what the agent said. One outbound form, applied to every history page, live batch and catch-up, sends a client the same 16 KB head of each tool output and diff it always got, and marks the item `shortened`. A new capability-gated `agentSession.item` RPC returns the whole item, and the desktop and phone show "Show full output" / "Show full message" when the copy was cut. Items past half a page are shortened and marked instead of replaced with "too large to show". Two bounds stay, both named: a 4 MiB ceiling per text (head and tail), applied where every provider's rows enter the write queue so one write can never stall a chat, and a preview on Grok/OpenCode/OMP/Pi calls while they run, whose closing row is built from the whole value the translator held. * test(native-chat): an over-budget history item is shortened and marked, not replaced * fix(native-chat): format, keep the sink and projection under the line limit, update method count and tests * fix(chat): preserve complete writes and keep detail current * test(chat): align final tail reservation with captured content * Prototype source-batch journal ownership for Claude * Pin the resolved iterator promise in ownership experiment * Revert "Pin the resolved iterator promise in ownership experiment" This reverts commitbdb49ab738. * Revert "Prototype source-batch journal ownership for Claude" This reverts commite5ed387f0c. * Preserve large received chat content while pacing input * Verify received chat writes wait for journal persistence * Close test journal databases before removing temporary roots * Exercise item detail in the cross-version chat wire manifest * Pin complete saved output and fitted client publication * Exercise Pi exit retention with real journal write pressure * Settle terminal web test unmount before DOM teardown * Reuse existing readers and contract support in session tests * Reuse measured chat item sizes in history pages * fix(chat): derive page form type from reader contract * fix(chat): open phone user messages while loading full text * test(native-chat): reuse imperative context provider construction * test(native-chat): format full-content disclosure fixture * ci: preserve unit shard diagnostics after a timeout * ci: retain unit shard output when tests time out * ci: expose unit shard watchdog and output stages * Revert "ci: expose unit shard watchdog and output stages" This reverts commit0b4991b144. * Revert "ci: retain unit shard output when tests time out" This reverts commit87eec4c04f. * Revert "ci: preserve unit shard diagnostics after a timeout" This reverts commit1888c656bb. * test: verify complete Claude tool output without large failure diffs * Simplify reply extraction in cross-version session test * Restore full message recovery for grouped phone thoughts * Target recovery button in grouped thought fixture * test(native-chat): count agentSession.item alongside main's new structured method * fix(chat): reuse strict session subscription schema * test(chat): open Codex before checking received content
151 lines
6.2 KiB
TypeScript
151 lines
6.2 KiB
TypeScript
import { expect, vi } from 'vitest'
|
|
import { AGENT_SESSION_ITEM_DETAIL_RUNTIME_CAPABILITY } from '../../../src/shared/protocol-version'
|
|
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: () => {},
|
|
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
|
|
}
|
|
}
|
|
|
|
/** 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
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
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')
|
|
)
|
|
}
|
|
}
|