Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions config/reliability-gates.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -948,12 +948,13 @@
"invariant": "One structured-send operation id causes at most one provider dispatch. A recorded or transport-ambiguous send reuses that id across retry, caller reconnect, client remount, and journal recovery; only a terminal rejection may rotate to a first delivery.",
"oracle": "Inject adapter acknowledgement loss, RPC response loss, caller replacement, logical-client close after response, auth recovery with a written request, missing journal submissions, legacy pending rows, stale fences, operation expiry, mobile remount, and durable-journal capacity. Assert one provider dispatch or one operation id for every ambiguous retry, fresh identity only after rejection, and no eviction of ambiguous mobile ids.",
"commands": [
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts",
"ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/mobile-structured-send-operation-journal.test.ts ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts"
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/shared/structured-agent-session-send-retention.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts",
"ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/mobile-structured-send-operation-journal.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts"
],
"testFiles": [
"src/shared/agent-session-operation-ledger.test.ts",
"src/shared/structured-agent-session-send-disposition.test.ts",
"src/shared/structured-agent-session-send-retention.test.ts",
"src/main/runtime/agent-session-operation-admission.test.ts",
"src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts",
"src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts",
Expand All @@ -967,7 +968,6 @@
"mobile/src/session/mobile-native-chat-image-attachment.test.ts",
"mobile/src/session/use-mobile-native-chat-image-attachments.test.ts",
"mobile/src/session/mobile-structured-send-operation-journal.test.ts",
"mobile/src/session/mobile-structured-send-delivery.test.ts",
"mobile/src/session/use-mobile-structured-agent-session-send.test.tsx",
"mobile/src/session/use-mobile-structured-agent-session.test.tsx",
"mobile/src/transport/mobile-relay-rpc-session.test.ts",
Expand Down Expand Up @@ -1033,22 +1033,22 @@
],
"evidenceRuns": [
{
"date": "2026-09-12",
"date": "2026-10-02",
"runner": "local",
"platform": "macos",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-disposition.test.ts src/shared/structured-agent-session-send-retention.test.ts src/main/runtime/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-refusal-retry.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts src/main/runtime/orchestration/structured-pointer-operation-id.test.ts src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts",
"result": "passed",
"durationSeconds": 22.1,
"summary": "Thirteen focused host, shared, renderer, and orchestration files passed 148 tests."
"durationSeconds": 29.3,
"summary": "Thirteen focused host, shared, renderer, and orchestration files passed 196 tests."
},
{
"date": "2026-09-12",
"date": "2026-10-02",
"runner": "local",
"platform": "macos",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/mobile-structured-send-operation-journal.test.ts ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts",
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/mobile-structured-send-operation-journal.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/transport/mobile-relay-rpc-session.test.ts ../mobile/src/transport/rpc-client-delivery-ambiguity.test.ts ../mobile/src/transport/stable-logical-rpc-client.test.ts",
"result": "passed",
"durationSeconds": 2.37,
"summary": "Ten focused mobile session, attachment, and transport files passed 113 tests."
"durationSeconds": 2.68,
"summary": "Eight focused mobile session, attachment, and transport files passed 112 tests."
}
],
"runtimeBudget": {
Expand Down
3 changes: 2 additions & 1 deletion mobile/src/session/mobile-native-chat-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { isRpcDeliveryUnknown } from '../transport/rpc-delivery-ambiguity'
import { isLogicalClientCutoverError } from '../transport/stable-logical-rpc-client'
import { nativeChatTerminalWrite } from './mobile-session-write-operations'
import { typeAgentTuiCommand } from '../../../src/shared/agent-tui-command-typing'
import type { StructuredAgentSessionSendOutcome } from '../../../src/shared/structured-agent-session-send-retention'

/** What a native-chat write takes, named from an operation so no module names the raw port. */
export type MobileNativeChatRpcSender = Parameters<typeof nativeChatTerminalWrite.request>[0]
Expand Down Expand Up @@ -31,7 +32,7 @@ type MobileNativeChatSendArgs = {
* frame was written) — callers must not present it as a definite send failure.
* 'queued' = structured lane only: the host holds the message as a queued
* draft, so it shows as a card above the composer, never a transcript echo. */
export type MobileNativeChatSendOutcome = 'accepted' | 'rejected' | 'unknown' | 'queued'
export type MobileNativeChatSendOutcome = StructuredAgentSessionSendOutcome

/** What a terminal write can answer: the PTY lane has no draft queue. */
export type MobileNativeChatWriteOutcome = Exclude<MobileNativeChatSendOutcome, 'queued'>
Expand Down
6 changes: 2 additions & 4 deletions mobile/src/session/mobile-structured-agent-session-cancel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import type { AgentSessionCancelResult } from '../../../src/shared/agent-session
import type { AgentJournalRenderItem } from '../../../src/shared/agent-session-journal-types'
import type { StructuredAgentSessionState } from '../../../src/shared/structured-agent-session-reducer'
import { activeStructuredAgentSessionTurnId } from '../../../src/shared/structured-agent-session-live-turn'
import type { StructuredAgentSessionMutationCallResult } from '../../../src/shared/structured-agent-session-mutation'
import type { RpcClient } from '../transport/rpc-client'
import {
requestStructuredAgentSessionMutation,
type StructuredAgentSessionMutationCallResult
} from './mobile-structured-agent-session-rpc'
import { requestStructuredAgentSessionMutation } from './mobile-structured-agent-session-rpc'

type PromptIdentity = { itemId: string; expectedRevision: number }

Expand Down
19 changes: 5 additions & 14 deletions mobile/src/session/mobile-structured-agent-session-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {
AgentSessionMutationResult,
AgentSessionWireRefusalCode
} from '../../../src/shared/agent-session-wire'
import { structuredAgentSessionPayloadFingerprint } from '../../../src/shared/structured-agent-session-mutation'
import type { AgentSessionMutationResult } from '../../../src/shared/agent-session-wire'
import {
structuredAgentSessionPayloadFingerprint,
type StructuredAgentSessionMutationCallResult
} from '../../../src/shared/structured-agent-session-mutation'
import {
agentSessionRefusalNotice,
agentSessionWriteFailureNotice,
Expand All @@ -24,15 +24,6 @@ import { MOBILE_NATIVE_CHAT_MIN_WRITE_TIMEOUT_MS } from './mobile-native-chat-se

export const STRUCTURED_SEND_TIMEOUT_MS = 15_000

export type StructuredAgentSessionMutationCallResult<TValue> =
| { status: 'accepted'; value: TValue }
| { status: 'refused'; code: AgentSessionWireRefusalCode; message: string }
/** `hostRejectedByRequestSchema`: the host's schema turned this request away before running
* it, so the same request can never be accepted there. An auth refusal does not set it:
* it says nothing about an earlier delivery of the same id. */
| { status: 'failed'; message: string; hostRejectedByRequestSchema?: true }
| { status: 'unknown' }

export type StructuredAgentSessionMutationResult<TValue> =
| { status: 'accepted'; value: TValue; sameFence: boolean }
| { status: 'rejected' }
Expand Down
42 changes: 11 additions & 31 deletions mobile/src/session/mobile-structured-agent-session-send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,23 @@ import {
structuredAgentSessionSendBody,
type StructuredAgentSessionAttachment
} from '../../../src/shared/structured-agent-session-outbox'
import { structuredAgentSessionPayloadFingerprint } from '../../../src/shared/structured-agent-session-mutation'
import {
structuredAgentSessionDomainFingerprint,
structuredAgentSessionPayloadFingerprint
} from '../../../src/shared/structured-agent-session-mutation'
structuredAgentSessionSendDelivery,
structuredAgentSessionSendIntentKey
} from '../../../src/shared/structured-agent-session-send-retention'
import type { RpcClient } from '../transport/rpc-client'
import type { MobileNativeChatSendOutcome } from './mobile-native-chat-send'
import {
requestStructuredAgentSessionMutation,
timeoutForDeadline
} from './mobile-structured-agent-session-rpc'
import { structuredSessionOperationId } from './structured-session-operation-id'
import { mobileStructuredSendDelivery } from './mobile-structured-send-delivery'
import {
bypassedMobileStructuredSendOperationId,
clearMobileStructuredSendOperation,
forgetBypassedMobileStructuredSendOperation,
getOrCreateMobileStructuredSendOperation,
mobileStructuredSendOperationKey,
rememberBypassedMobileStructuredSendOperation
} from './mobile-structured-send-operation-journal'

Expand Down Expand Up @@ -57,31 +56,12 @@ export async function sendMobileStructuredAgentSessionMessage(input: {
sessionId: input.sessionId,
fields: { body: requestedBody }
})
const intentFields = {
text: input.text.trimEnd(),
attachments: input.attachments.map(
(attachment) =>
attachment.contentFingerprint ??
structuredAgentSessionDomainFingerprint({
domain: 'mobile.nativeChat.image.preview',
sessionId: '',
fields: { previewUri: attachment.previewUri }
})
)
}
// `delivery` is part of the intent key, never a stored journal field: the
// immediate key is exactly today's, so an older build still reads the journal.
const operationKeyFor = (delivery: 'queue-if-active' | undefined): string =>
mobileStructuredSendOperationKey({
sessionKey: input.sessionKey,
intentFingerprint: structuredAgentSessionDomainFingerprint({
domain: 'mobile.agentSession.send.intent',
sessionId: input.sessionKey,
fields: delivery ? { ...intentFields, delivery } : intentFields
})
})
const queuedOperationKey = operationKeyFor('queue-if-active')
const immediateOperationKey = operationKeyFor(undefined)
const intent = { sessionKey: input.sessionKey, text: input.text, attachments: input.attachments }
const queuedOperationKey = structuredAgentSessionSendIntentKey({
...intent,
delivery: 'queue-if-active'
})
const immediateOperationKey = structuredAgentSessionSendIntentKey(intent)
const requestedOperationKey = input.delivery ? queuedOperationKey : immediateOperationKey
const attachmentPaths = input.attachments.map((attachment) => attachment.path)
const resendKey = input.resendOperationKey ?? requestedOperationKey
Expand Down Expand Up @@ -144,7 +124,7 @@ export async function sendMobileStructuredAgentSessionMessage(input: {
clientOperationId: operation.operationId,
timeoutMs
})
const outcome = mobileStructuredSendDelivery(result, operation.retained)
const outcome = structuredAgentSessionSendDelivery(result, operation.retained)
if (input.bypassRetainedRecord && outcome.operationIdSpent) {
forgetBypassedMobileStructuredSendOperation(operationKey, operation.operationId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {
clearMobileStructuredSettledSendOperations,
getOrCreateMobileStructuredSendOperation as getOrCreatePersistedOperation,
mobileStructuredSendCallerFingerprint,
mobileStructuredSendOperationKey,
resetMobileStructuredSendOperationJournalForTests
} from './mobile-structured-send-operation-journal'
import { structuredAgentSessionSendOperationKey } from '../../../src/shared/structured-agent-session-send-retention'
import { readMirroredStorage } from '../storage/mirrored-storage-keys'

const NOW = 1_900_000_000_000
Expand Down Expand Up @@ -156,7 +156,7 @@ describe('mobile structured send operation journal', () => {
it('clears an ack-lost id only when its journal submission settles', async () => {
const sessionKey = 'host-a:session-a'
const payloadFingerprint = 'c'.repeat(64)
const operationKey = mobileStructuredSendOperationKey({
const operationKey = structuredAgentSessionSendOperationKey({
sessionKey,
intentFingerprint: payloadFingerprint
})
Expand Down Expand Up @@ -190,7 +190,7 @@ describe('mobile structured send operation journal', () => {
it("clears an id a draft hand-off names, whatever that hand-off's state", async () => {
const sessionKey = 'host-a:session-a'
const payloadFingerprint = '9'.repeat(64)
const operationKey = mobileStructuredSendOperationKey({
const operationKey = structuredAgentSessionSendOperationKey({
sessionKey,
intentFingerprint: payloadFingerprint
})
Expand Down Expand Up @@ -223,7 +223,7 @@ describe('mobile structured send operation journal', () => {
it('does not clear a newer id for an older matching-payload submission', async () => {
const sessionKey = 'host-a:session-a'
const payloadFingerprint = 'e'.repeat(64)
const operationKey = mobileStructuredSendOperationKey({
const operationKey = structuredAgentSessionSendOperationKey({
sessionKey,
intentFingerprint: payloadFingerprint
})
Expand Down Expand Up @@ -411,21 +411,6 @@ describe('mobile structured send operation journal', () => {
).rejects.toThrow('journal is full')
})

it('hashes the session scope and payload instead of retaining message bodies', () => {
const first = mobileStructuredSendOperationKey({
sessionKey: 'host-a:session-a',
intentFingerprint: 'message-body-fingerprint'
})
const second = mobileStructuredSendOperationKey({
sessionKey: 'host-b:session-a',
intentFingerprint: 'message-body-fingerprint'
})

expect(first).toMatch(/^[0-9a-f]{64}$/)
expect(second).not.toBe(first)
expect(first).not.toContain('message-body')
})

it('fails closed when a retained id crosses authenticated caller identities', async () => {
await getOrCreatePersistedOperation({
operationKey: OPERATION_KEY,
Expand Down
11 changes: 0 additions & 11 deletions mobile/src/session/mobile-structured-send-operation-journal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,6 @@ type OperationJournal = z.infer<typeof OperationJournalSchema>

const mutations: { tail: Promise<void> } = { tail: Promise.resolve() }

export function mobileStructuredSendOperationKey(input: {
sessionKey: string
intentFingerprint: string
}): string {
return structuredAgentSessionDomainFingerprint({
domain: 'mobile.agentSession.send.operation',
sessionId: input.sessionKey,
fields: { intentFingerprint: input.intentFingerprint }
})
}

export function mobileStructuredSendCallerFingerprint(callerIdentity: string): string {
return structuredAgentSessionDomainFingerprint({
domain: 'mobile.agentSession.send.caller',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { AgentSessionSubscribeEvent } from '../../../src/shared/agent-sessi
import type { RpcClient } from '../transport/rpc-client'
import { markRpcDeliveryUnknown } from '../transport/rpc-delivery-ambiguity'
import { resetMobileStructuredSendOperationJournalForTests } from './mobile-structured-send-operation-journal'
import { structuredSendResultFixture } from './structured-agent-send-result.test-fixture'
import { structuredSendResultFixture } from '../../../src/shared/structured-agent-send-result.test-fixture'
import { useMobileStructuredAgentSession } from './use-mobile-structured-agent-session'

const asyncStorage = vi.hoisted(() => ({
Expand Down
Loading
Loading