refactor: group mobile chat composer ownership (#33390)

* refactor: group mobile chat composer ownership

* refactor: use explicit type imports in moved chat adapters
This commit is contained in:
Neil
2026-10-24 21:37:52 -07:00
committed by GitHub
parent c25fad76df
commit 0a664b01b6
39 changed files with 105 additions and 111 deletions
+5 -4
View File
@@ -2018,7 +2018,7 @@
"oracle": "Inject adapter/RPC acknowledgement loss, caller replacement, logical-client close after response, auth recovery with a written request, missing host submissions, legacy pending rows, stale fences, expiry, mobile remount, and phone storage refusal. Assert at most one dispatch for repeated transmission of one operation. For a later phone Send, assert a fresh id and second delivery while the original host entry remains unknown; equal images use current uploaded paths and delivery fields follow current host capabilities.",
"commands": [
"ORCA_BACKGROUND_LAUNCH=1 pnpm test src/shared/agent-session-operation-ledger.test.ts src/shared/structured-agent-session-send-evidence.test.ts src/main/runtime/agent-session/ownership/agent-session-operation-admission.test.ts src/main/native-chat/agent-session-wire/structured-agent-session-send-idempotency.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/structured-agent-session-message-sender.test.ts src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx src/renderer/src/lib/launch-structured-agent-session.test.ts",
"ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-structured-send-new-action.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-queued.test.tsx ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-newer-approval-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-read-failure.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsx ../mobile/src/session/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/use-mobile-structured-native-chat-send-bridge.test.ts ../mobile/src/mobile-web-shell/page-storage-keys.test.ts ../mobile/src/mobile-web-shell/bridge/page-async-storage.test.ts ../mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx ../mobile/src/mobile-web-shell/bridge-host-init.test.ts ../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 --maxWorkers=2"
"ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-structured-send-new-action.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-queued.test.tsx ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-newer-approval-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-read-failure.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsx ../mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/use-mobile-structured-native-chat-send-bridge.test.ts ../mobile/src/mobile-web-shell/page-storage-keys.test.ts ../mobile/src/mobile-web-shell/bridge/page-async-storage.test.ts ../mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx ../mobile/src/mobile-web-shell/bridge-host-init.test.ts ../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 --maxWorkers=2"
],
"testFiles": [
"src/shared/agent-session-operation-ledger.test.ts",
@@ -2032,8 +2032,8 @@
"src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts",
"src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx",
"src/renderer/src/lib/launch-structured-agent-session.test.ts",
"mobile/src/session/mobile-native-chat-image-attachment.test.ts",
"mobile/src/session/use-mobile-native-chat-image-attachments.test.ts",
"mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.test.ts",
"mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts",
"mobile/src/session/mobile-structured-send-new-action.test.ts",
"mobile/src/session/mobile-structured-send-delivery.test.ts",
"mobile/src/session/use-mobile-structured-agent-session-send.test.tsx",
@@ -2118,7 +2118,8 @@
"command": "ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-structured-send-new-action.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-queued.test.tsx ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-newer-approval-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-read-failure.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsx ../mobile/src/session/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/use-mobile-structured-native-chat-send-bridge.test.ts ../mobile/src/mobile-web-shell/page-storage-keys.test.ts ../mobile/src/mobile-web-shell/bridge/page-async-storage.test.ts ../mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx ../mobile/src/mobile-web-shell/bridge-host-init.test.ts ../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 --maxWorkers=2",
"result": "passed",
"durationSeconds": 5.42,
"summary": "Nineteen focused mobile files passed 241 tests for fresh Send identity, remount, attachment paths, storage refusal, mixed host capabilities, queued cards, and written-request ambiguity."
"summary": "Nineteen focused mobile files passed 241 tests for fresh Send identity, remount, attachment paths, storage refusal, mixed host capabilities, queued cards, and written-request ambiguity.",
"currentCommand": "ORCA_BACKGROUND_LAUNCH=1 pnpm --dir mobile test ../mobile/src/session/mobile-structured-send-new-action.test.ts ../mobile/src/session/use-mobile-structured-agent-session-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-queued.test.tsx ../mobile/src/session/mobile-structured-send-delivery.test.ts ../mobile/src/session/use-mobile-structured-agent-session-background-tasks.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-newer-approval-send.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-read-failure.test.tsx ../mobile/src/session/use-mobile-structured-agent-session.test.tsx ../mobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsx ../mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.test.ts ../mobile/src/session/native-chat/composer/use-mobile-native-chat-image-attachments.test.ts ../mobile/src/session/use-mobile-structured-native-chat-send-bridge.test.ts ../mobile/src/mobile-web-shell/page-storage-keys.test.ts ../mobile/src/mobile-web-shell/bridge/page-async-storage.test.ts ../mobile/src/mobile-web-shell/use-page-host-snapshot.test.tsx ../mobile/src/mobile-web-shell/bridge-host-init.test.ts ../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 --maxWorkers=2"
},
{
"date": "2026-10-13",
+12 -12
View File
@@ -5,7 +5,7 @@
"id": "b1",
"operation": "workspace.file-inventory",
"family": "legacy-inventory",
"sites": ["mobile/src/session/use-mobile-native-chat-file-search.ts"],
"sites": ["mobile/src/session/native-chat/composer/use-mobile-native-chat-file-search.ts"],
"schedules": ["a-b-a", "stale-inflight-cleanup"],
"steps": [
{
@@ -2508,7 +2508,7 @@
"id": "inventory-lifecycle",
"operation": "workspace.file-inventory",
"family": "legacy-inventory",
"sites": ["mobile/src/session/use-mobile-native-chat-file-search.ts"],
"sites": ["mobile/src/session/native-chat/composer/use-mobile-native-chat-file-search.ts"],
"schedules": ["reset", "unmount-remount", "blur-retained-route"],
"steps": [
{
@@ -2572,7 +2572,7 @@
"id": "inventory-repeat-query",
"operation": "workspace.file-inventory",
"family": "legacy-inventory",
"sites": ["mobile/src/session/use-mobile-native-chat-file-search.ts"],
"sites": ["mobile/src/session/native-chat/composer/use-mobile-native-chat-file-search.ts"],
"schedules": ["normalized-repeat", "cache-cancels-debounce", "cache-stales-inflight"],
"steps": [
{
@@ -16143,7 +16143,7 @@
"id": "native-chat-image-paste-single",
"operation": "nativeChat.image-paste",
"family": "nativeChat.image-paste",
"sites": ["mobile/src/session/mobile-native-chat-image-send.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-image-send.ts"],
"schedules": [],
"steps": [
{
@@ -16199,7 +16199,7 @@
"id": "native-chat-image-paste-two-images",
"operation": "nativeChat.image-paste",
"family": "nativeChat.image-paste",
"sites": ["mobile/src/session/mobile-native-chat-image-send.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-image-send.ts"],
"schedules": [],
"steps": [
{
@@ -16275,7 +16275,7 @@
"id": "native-chat-image-paste-stops-on-rejection",
"operation": "nativeChat.image-paste",
"family": "nativeChat.image-paste",
"sites": ["mobile/src/session/mobile-native-chat-image-send.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-image-send.ts"],
"schedules": [],
"steps": [
{
@@ -16331,7 +16331,7 @@
"id": "native-chat-image-paste-trailing-image",
"operation": "nativeChat.image-paste",
"family": "nativeChat.image-paste",
"sites": ["mobile/src/session/mobile-native-chat-image-send.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-image-send.ts"],
"schedules": [],
"steps": [
{
@@ -18983,7 +18983,7 @@
"id": "native-chat-image-upload-start-refused",
"operation": "nativeChat.image-upload",
"family": "nativeChat.image-upload",
"sites": ["mobile/src/session/mobile-native-chat-image-attachment.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.ts"],
"schedules": [],
"steps": [
{
@@ -19013,7 +19013,7 @@
"id": "native-chat-image-upload-single",
"operation": "nativeChat.image-upload",
"family": "nativeChat.image-upload",
"sites": ["mobile/src/session/mobile-native-chat-image-attachment.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.ts"],
"schedules": [],
"steps": [
{
@@ -19066,7 +19066,7 @@
"id": "native-chat-image-upload-two",
"operation": "nativeChat.image-upload",
"family": "nativeChat.image-upload",
"sites": ["mobile/src/session/mobile-native-chat-image-attachment.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.ts"],
"schedules": [],
"steps": [
{
@@ -19156,7 +19156,7 @@
"id": "native-chat-image-upload-second-fails",
"operation": "nativeChat.image-upload",
"family": "nativeChat.image-upload",
"sites": ["mobile/src/session/mobile-native-chat-image-attachment.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.ts"],
"schedules": [],
"steps": [
{
@@ -19223,7 +19223,7 @@
"id": "native-chat-image-upload-cancelled",
"operation": "nativeChat.image-upload",
"family": "nativeChat.image-upload",
"sites": ["mobile/src/session/mobile-native-chat-image-attachment.ts"],
"sites": ["mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.ts"],
"schedules": [],
"steps": [
{
@@ -5,8 +5,8 @@ import { colors } from '../theme/mobile-theme'
import { NATIVE_CHAT_COMPOSER_PLACEHOLDER } from '../../../src/shared/native-chat-turn-status'
import { MobileNativeChatComposerMenu } from './MobileNativeChatComposerMenu'
import { MobileNativeChatComposerField } from './MobileNativeChatComposerField'
import { MobileNativeChatComposerAttachments } from './MobileNativeChatComposerAttachments'
import type { PendingNativeChatFile } from './mobile-native-chat-file-attachment'
import { MobileNativeChatComposerAttachments } from './native-chat/composer/MobileNativeChatComposerAttachments'
import type { PendingNativeChatFile } from './native-chat/composer/mobile-native-chat-file-attachment'
import { ActionSheetModal } from '../components/ActionSheetModal'
import { mobileNativeChatAttachActions } from './mobile-native-chat-attach-actions'
import {
@@ -14,7 +14,7 @@ import {
type MobileNativeChatPickerPresentation,
type MobileNativeChatSessionOptionPickersProps
} from './MobileNativeChatSessionOptionPickers'
import type { PendingNativeChatImage } from './mobile-native-chat-image-attachment'
import type { PendingNativeChatImage } from './native-chat/composer/mobile-native-chat-image-attachment'
import type { MobileComposerStructuredCatalog } from './mobile-native-chat-composer-menu'
import { useMobileNativeChatComposerMenu } from './use-mobile-native-chat-composer-menu'
import { keepHeldPressThroughLongPress } from './held-press-long-press'
@@ -3,7 +3,7 @@ import { StyleSheet, View } from 'react-native'
import { MobileNativeChatView, type MobileNativeChatInputLockReason } from './MobileNativeChatView'
import { foldMobileNativeChatMessages } from './mobile-native-chat-render-data'
import { useMobileNativeChatFoldReuse } from './mobile-native-chat-fold-reuse'
import type { MobileNativeChatImageAttachments } from './use-mobile-native-chat-image-attachments'
import type { MobileNativeChatImageAttachments } from './native-chat/composer/use-mobile-native-chat-image-attachments'
import type { MobileNativeChatController } from './use-mobile-native-chat-controller'
import type { MobileNativeChatStructuredComposer } from './MobileNativeChatComposer'
import { useMobileNativeChatStreamingBubble } from './use-mobile-native-chat-streaming-bubble'
+1 -1
View File
@@ -40,7 +40,7 @@ import {
} from './use-mobile-native-chat-input-lease'
import { MobileNativeChatLiveLine } from './MobileNativeChatLiveLine'
import { MobileNativeChatFloatingControls } from './MobileNativeChatFloatingControls'
import type { PendingNativeChatImage } from './mobile-native-chat-image-attachment'
import type { PendingNativeChatImage } from './native-chat/composer/mobile-native-chat-image-attachment'
import {
MobileNativeChatComposer,
type MobileNativeChatStructuredComposer
@@ -136,7 +136,7 @@ vi.mock('../use-mobile-native-chat-session-option-controller', () => ({
recordCommand: vi.fn()
})
}))
vi.mock('../use-mobile-native-chat-file-search', () => ({
vi.mock('../native-chat/composer/use-mobile-native-chat-file-search', () => ({
useMobileNativeChatFileSearch: () => ({ nativeChatFilePaths: [], loadNativeChatFiles: vi.fn() })
}))
vi.mock('../use-mobile-native-chat-composer-tray', () => ({
@@ -3,7 +3,7 @@ import { act, create } from 'react-test-renderer'
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
import { MobileNativeChatComposer } from './MobileNativeChatComposer'
import { useMobileNativeChatFileSearch } from './use-mobile-native-chat-file-search'
import { useMobileNativeChatFileSearch } from './native-chat/composer/use-mobile-native-chat-file-search'
import { stubRpcClient } from '../transport/rpc-client-test-stub'
vi.mock('react-native', async () => {
@@ -5,8 +5,7 @@ import {
normalizeImageTranscriptMessages,
normalizeNativeChatUserText,
normalizedNativeChatUserMessageText
} from './mobile-native-chat-image-transcript-markers'
export { normalizeNativeChatUserText as normalizeReconcileText } from './mobile-native-chat-image-transcript-markers'
} from '../../../src/shared/native-chat-image-transcript-markers'
/** An ack-lost ('unknown' outcome) send held until its transcript echo lands or
* the deadline surfaces the uncertainty. */
@@ -1,13 +0,0 @@
// Single-sources the marker logic (pure functions over shared types):
// Claude records an attached image as `[Image: source: /path]` (+ `[Image #N]`
// on the caption turn), and both render and echo reconciliation must
// agree with desktop on how those marker turns are interpreted.
export {
imageSourcePathFromText,
hasImagePromptMarker,
isImageSourceUserTurn,
normalizeImageTranscriptMessages,
normalizeNativeChatUserText,
normalizedNativeChatUserMessageText,
stripImagePromptMarker
} from '../../../src/shared/native-chat-image-transcript-markers'
@@ -1,5 +1,5 @@
import type { NativeChatMessage } from '../../../src/shared/native-chat-types'
import { normalizeReconcileText } from './mobile-native-chat-draft-reconcile'
import { normalizeNativeChatUserText as normalizeReconcileText } from '../../../src/shared/native-chat-image-transcript-markers'
import type { MobileNativeChatPendingMessage } from './mobile-native-chat-pending-echo'
/**
@@ -1,9 +1,7 @@
import { describe, expect, it } from 'vitest'
import type { NativeChatMessage } from '../../../src/shared/native-chat-types'
import {
countUserTextOccurrences,
normalizeReconcileText
} from './mobile-native-chat-draft-reconcile'
import { countUserTextOccurrences } from './mobile-native-chat-draft-reconcile'
import { normalizeNativeChatUserText as normalizeReconcileText } from '../../../src/shared/native-chat-image-transcript-markers'
import {
appendMobileNativeChatPending,
type MobileNativeChatSendOrigin
@@ -1,4 +1,4 @@
import { normalizeReconcileText } from './mobile-native-chat-draft-reconcile'
import { normalizeNativeChatUserText as normalizeReconcileText } from '../../../src/shared/native-chat-image-transcript-markers'
export type MobileNativeChatPendingMessage = {
id: string
@@ -2,9 +2,9 @@ import { nativeChatContinuationSendText } from '../../../src/shared/native-chat-
import type { NativeChatMessage } from '../../../src/shared/native-chat-types'
import {
countImageSourceTurnsAfter,
normalizeReconcileText,
normalizedUserText
} from './mobile-native-chat-draft-reconcile'
import { normalizeNativeChatUserText as normalizeReconcileText } from '../../../src/shared/native-chat-image-transcript-markers'
import type { MobileNativeChatPendingMessage } from './mobile-native-chat-pending-echo'
const SPACE = ' '
@@ -19,7 +19,7 @@ import {
type AgentType,
type NativeChatMessage
} from '../../../src/shared/native-chat-types'
import { isImageSourceUserTurn } from './mobile-native-chat-image-transcript-markers'
import { isImageSourceUserTurn } from '../../../src/shared/native-chat-image-transcript-markers'
import type { MobileNativeChatStatus } from './use-mobile-native-chat-session'
/** The centered empty-state copy for a chat with no messages, mirroring the
@@ -1,4 +1,4 @@
import { pasteMobileNativeChatImagePaths } from './mobile-native-chat-image-send'
import { pasteMobileNativeChatImagePaths } from './native-chat/composer/mobile-native-chat-image-send'
import type { MobileNativeChatRpcSender } from './mobile-native-chat-send'
// The condition tracked here — a bracketed image paste left sitting on the agent's
@@ -14,7 +14,7 @@ import {
} from './mobile-native-chat-send'
import { sendMobileTerminalQueryReply } from '../terminal/mobile-terminal-query-reply'
import { useMobileDiffReviewSendActions } from './use-mobile-diff-review-send-actions'
import { pasteMobileNativeChatImagePaths } from './mobile-native-chat-image-send'
import { pasteMobileNativeChatImagePaths } from './native-chat/composer/mobile-native-chat-image-send'
vi.mock('react-native', () => ({ Keyboard: { dismiss: vi.fn() } }))
vi.mock('../platform/haptics', () => ({ triggerError: vi.fn(), triggerSuccess: vi.fn() }))
@@ -8,8 +8,8 @@ import {
View
} from 'react-native'
import { FileText, X } from 'lucide-react-native'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { formatPreviewByteLength } from '../files/mobile-file-preview-response'
import { colors, radii, spacing } from '../../../theme/mobile-theme'
import { formatPreviewByteLength } from '../../../files/mobile-file-preview-response'
import type { PendingNativeChatImage } from './mobile-native-chat-image-attachment'
import type { PendingNativeChatFile } from './mobile-native-chat-file-attachment'
@@ -1,5 +1,8 @@
import { describe, expect, it } from 'vitest'
import { MobileFileTooLargeError, type PickedMobileFile } from '../platform/media-picker-contract'
import {
MobileFileTooLargeError,
type PickedMobileFile
} from '../../../platform/media-picker-contract'
import {
nativeChatTextWithFileReferences,
uploadPickedNativeChatFiles,
@@ -1,6 +1,9 @@
import { formatNativeChatFileReference } from '../../../src/shared/agent-image-paste'
import { MobileFileTooLargeError, type PickedMobileFile } from '../platform/media-picker-contract'
import type { AgentSessionAttachmentUploadCommitResult } from '../../../src/shared/agent-session-attachments'
import { formatNativeChatFileReference } from '../../../../../src/shared/agent-image-paste'
import {
MobileFileTooLargeError,
type PickedMobileFile
} from '../../../platform/media-picker-contract'
import type { AgentSessionAttachmentUploadCommitResult } from '../../../../../src/shared/agent-session-attachments'
/** A file in the composer: its chip shows from the pick, and `path` arrives once the host stored it. */
export type PendingNativeChatFile = {
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
import type { RpcResponse, RpcSuccess } from '../transport/types'
import type { RpcClient } from '../../../transport/rpc-client'
import type { RpcResponse, RpcSuccess } from '../../../transport/types'
import { uploadMobileNativeChatImages } from './mobile-native-chat-image-attachment'
function ok(id: string, result: unknown): RpcSuccess {
@@ -1,8 +1,8 @@
import { saveMobileClipboardImageAsTempFile } from './mobile-clipboard-image'
import type { MobileClipboardImageRpcSender } from './mobile-clipboard-image-operations'
import { saveMobileClipboardImageAsTempFile } from '../../mobile-clipboard-image'
import type { MobileClipboardImageRpcSender } from '../../mobile-clipboard-image-operations'
// Type-only import so this module (and its unit test) stays free of the expo/
// react-native picker chain; the concrete `pickImage` is injected by the hook.
import type { MobileImageSource, PickedMobileImage } from './mobile-image-source-picker'
import type { MobileImageSource, PickedMobileImage } from '../../../platform/media-picker-contract'
/** A picked-and-uploaded image held in the native-chat composer until submit.
* `path` is the host temp file pasted into the agent on send; `previewUri` is a
@@ -1,8 +1,8 @@
import { describe, expect, it, vi } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
import type { RpcResponse, RpcSuccess } from '../transport/types'
import type { RpcClient } from '../../../transport/rpc-client'
import type { RpcResponse, RpcSuccess } from '../../../transport/types'
import { pasteMobileNativeChatImagePaths } from './mobile-native-chat-image-send'
import { buildAgentTuiClearInputForText } from '../../../src/shared/agent-tui-input-clear'
import { buildAgentTuiClearInputForText } from '../../../../../src/shared/agent-tui-input-clear'
function sendResult(accepted: boolean, id = 'send'): RpcSuccess {
return { id, ok: true, result: { send: { accepted } }, _meta: { runtimeId: 'r' } }
@@ -1,11 +1,11 @@
import { agentImagePasteWrites } from '../../../src/shared/agent-image-paste'
import { buildMobileImagePastePayload } from './mobile-clipboard-image'
import { agentImagePasteWrites } from '../../../../../src/shared/agent-image-paste'
import { buildMobileImagePastePayload } from '../../mobile-clipboard-image'
import {
MOBILE_NATIVE_CHAT_MIN_WRITE_TIMEOUT_MS,
openMobileNativeChatSendBudget
} from './mobile-native-chat-send'
import { nativeChatTerminalWrite } from './mobile-session-write-operations'
import type { MobileNativeChatRpcSender } from './mobile-native-chat-send'
} from '../../mobile-native-chat-send'
import { nativeChatTerminalWrite } from '../../mobile-session-write-operations'
import type { MobileNativeChatRpcSender } from '../../mobile-native-chat-send'
// Give the agent TUI a beat to register each bracketed image paste before the
// message text + Enter arrive, so the image attaches instead of being treated as
@@ -1,7 +1,7 @@
import { useCallback, useRef, useState } from 'react'
import type { RpcClient } from '../transport/rpc-client'
import { useMediaPicker } from '../platform/media-picker'
import { uploadMobileAgentSessionAttachment } from './mobile-agent-session-attachment-upload'
import type { RpcClient } from '../../../transport/rpc-client'
import { useMediaPicker } from '../../../platform/media-picker'
import { uploadMobileAgentSessionAttachment } from '../../mobile-agent-session-attachment-upload'
import { withScopeAttachments } from './mobile-native-chat-image-scope-state'
import {
uploadPickedNativeChatFiles,
@@ -1,7 +1,7 @@
import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
import type { RpcClient } from '../../../transport/rpc-client'
import { useMobileNativeChatFileSearch } from './use-mobile-native-chat-file-search'
type SearchState = ReturnType<typeof useMobileNativeChatFileSearch>
@@ -1,14 +1,14 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import type { RpcClient } from '../transport/rpc-client'
import type { RpcClient } from '../../../transport/rpc-client'
import {
GenerationScopedRequestOwner,
type RequestScope
} from '../transport/generation-scoped-request-owner'
} from '../../../transport/generation-scoped-request-owner'
import {
nativeChatFileInventoryRead,
nativeChatFileSearchRead
} from './mobile-session-read-operations'
import { rankSuggestions } from './mobile-native-chat-autocomplete'
} from '../../mobile-session-read-operations'
import { rankSuggestions } from '../../mobile-native-chat-autocomplete'
const FILE_SEARCH_DEBOUNCE_MS = 120
const FILE_SEARCH_RESULT_LIMIT = 16
@@ -1,18 +1,18 @@
import { createElement } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import { buildAgentTuiClearInputForText } from '../../../src/shared/agent-tui-input-clear'
import { buildAgentTuiClearInputForText } from '../../../../../src/shared/agent-tui-input-clear'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
import type { RpcResponse, RpcSuccess } from '../transport/types'
import { resetMobileNativeChatStaleInputForTests } from './mobile-native-chat-stale-input'
import { resetMobileNativeChatTerminalWritesForTests } from './mobile-native-chat-terminal-write-lock'
import type { RpcClient } from '../../../transport/rpc-client'
import type { RpcResponse, RpcSuccess } from '../../../transport/types'
import { resetMobileNativeChatStaleInputForTests } from '../../mobile-native-chat-stale-input'
import { resetMobileNativeChatTerminalWritesForTests } from '../../mobile-native-chat-terminal-write-lock'
import { useMobileNativeChatImageAttachments } from './use-mobile-native-chat-image-attachments'
// Stubbed at the seam the hook now holds, which keeps the real expo/react-native chain out of the
// vitest transform (react-native ships Flow syntax rolldown cannot parse) and pins the call site:
// a hook that went back to importing the picker module directly would load that chain and fail.
const pick = vi.hoisted(() => vi.fn())
vi.mock('../platform/media-picker', () => ({ useMediaPicker: () => ({ pickImages: pick }) }))
vi.mock('../../../platform/media-picker', () => ({ useMediaPicker: () => ({ pickImages: pick }) }))
function ok(id: string, result: unknown): RpcSuccess {
return { id, ok: true, result, _meta: { runtimeId: 'r' } }
@@ -1,8 +1,8 @@
import { useCallback, useRef, useState } from 'react'
import { buildAgentTuiClearInputForText } from '../../../src/shared/agent-tui-input-clear'
import type { RpcClient } from '../transport/rpc-client'
import type { ConnectionState } from '../transport/types'
import type { MobileImageSource } from './mobile-image-source-picker'
import { buildAgentTuiClearInputForText } from '../../../../../src/shared/agent-tui-input-clear'
import type { RpcClient } from '../../../transport/rpc-client'
import type { ConnectionState } from '../../../transport/types'
import type { MobileImageSource } from '../../../platform/media-picker-contract'
import {
appendPendingNativeChatImages,
type PendingNativeChatImage
@@ -19,20 +19,20 @@ import {
import {
openMobileNativeChatSendBudget,
type MobileNativeChatSendOutcome
} from './mobile-native-chat-send'
} from '../../mobile-native-chat-send'
import {
clearMobileNativeChatInputStale,
healMobileNativeChatStaleInput,
isMobileNativeChatInputStale,
markMobileNativeChatInputStale
} from './mobile-native-chat-stale-input'
} from '../../mobile-native-chat-stale-input'
import {
acquireMobileNativeChatTerminalWrite,
releaseMobileNativeChatTerminalWrite
} from './mobile-native-chat-terminal-write-lock'
} from '../../mobile-native-chat-terminal-write-lock'
import { useMobileNativeChatImageUpload } from './use-mobile-native-chat-image-upload'
import type { PendingNativeChatFile } from './mobile-native-chat-file-attachment'
import type { StructuredMobileSendAttachment } from './use-mobile-structured-send-with-outcome'
import type { StructuredMobileSendAttachment } from '../../use-mobile-structured-send-with-outcome'
type CurrentRef<T> = { readonly current: T }
type ShowToast = (message: string, durationMs?: number) => void
@@ -1,12 +1,12 @@
import { useCallback, useLayoutEffect, useRef, useState } from 'react'
import { CLIPBOARD_IMAGE_TOO_LARGE_ERROR } from '../../../src/shared/clipboard-image'
import type { RpcClient } from '../transport/rpc-client'
import type { ConnectionState } from '../transport/types'
import { useMediaPicker } from '../platform/media-picker'
import { CLIPBOARD_IMAGE_TOO_LARGE_ERROR } from '../../../../../src/shared/clipboard-image'
import type { RpcClient } from '../../../transport/rpc-client'
import type { ConnectionState } from '../../../transport/types'
import { useMediaPicker } from '../../../platform/media-picker'
import {
ImageLibraryPermissionError,
type MobileImageSource
} from '../platform/media-picker-contract'
} from '../../../platform/media-picker-contract'
import {
uploadMobileNativeChatImages,
type PendingNativeChatImage
@@ -142,7 +142,7 @@ vi.mock('./mobile-native-chat-permission-send', () => ({
vi.mock('./use-mobile-native-chat-stop', () => ({
useMobileNativeChatStop: () => vi.fn()
}))
vi.mock('./use-mobile-native-chat-file-search', () => ({
vi.mock('./native-chat/composer/use-mobile-native-chat-file-search', () => ({
useMobileNativeChatFileSearch: () => ({ nativeChatFilePaths: [], loadNativeChatFiles: vi.fn() })
}))
// Partial: the stale-input heal reaches the real transport through image-send,
@@ -5,7 +5,7 @@ import type { MobileNativeChatTab } from './mobile-native-chat-eligibility'
import type { StructuredAgentSessionHostSupport } from './mobile-structured-agent-session-host-support'
import { useMobileNativeChatAskDismiss } from './use-mobile-native-chat-ask-dismiss'
import { useMobileNativeChatDrafts } from './use-mobile-native-chat-drafts'
import { useMobileNativeChatFileSearch } from './use-mobile-native-chat-file-search'
import { useMobileNativeChatFileSearch } from './native-chat/composer/use-mobile-native-chat-file-search'
import { useMobileNativeChatMessageSend } from './use-mobile-native-chat-message-send'
import { mobileNativeChatStreamPreview } from './mobile-native-chat-streaming-gate'
import { useMobileNativeChatSessionOptionController } from './use-mobile-native-chat-session-option-controller'
@@ -5,9 +5,9 @@ import {
countUserTextOccurrences,
findLandedImagePreviewEchoes,
mergeSentImagePreviews,
migrateImagePreviewMessageIds,
normalizeReconcileText
migrateImagePreviewMessageIds
} from './mobile-native-chat-draft-reconcile'
import { normalizeNativeChatUserText as normalizeReconcileText } from '../../../src/shared/native-chat-image-transcript-markers'
import { useMobileNativeChatUnconfirmedSends } from './use-mobile-native-chat-unconfirmed-sends'
import { rebaseMobileNativeChatPendingBaselines } from './mobile-native-chat-pending-baseline'
import { retireLandedMobileNativeChatPending } from './mobile-native-chat-pending-retirement'
@@ -2,7 +2,7 @@ import { useMemo } from 'react'
import type { AgentSessionConversationCommand } from '../../../src/shared/agent-session/agent-session-conversation-command'
import type { RpcClient } from '../transport/rpc-client'
import type { MobileNativeChatStructuredComposer } from './MobileNativeChatComposer'
import type { MobileNativeChatFileAttachments } from './use-mobile-native-chat-file-attachments'
import type { MobileNativeChatFileAttachments } from './native-chat/composer/use-mobile-native-chat-file-attachments'
import { useMobileStructuredSessionCommands } from './use-mobile-structured-session-commands'
const NO_COMMANDS: readonly AgentSessionConversationCommand[] = []
@@ -7,11 +7,11 @@ import { useMobileImageAttachment } from './use-mobile-image-attachment'
import {
useMobileNativeChatImageAttachments,
type MobileNativeChatImageAttachments
} from './use-mobile-native-chat-image-attachments'
} from './native-chat/composer/use-mobile-native-chat-image-attachments'
import {
useMobileNativeChatFileAttachments,
type MobileNativeChatFileAttachments
} from './use-mobile-native-chat-file-attachments'
} from './native-chat/composer/use-mobile-native-chat-file-attachments'
type CurrentRef<T> = { readonly current: T }
@@ -21,7 +21,7 @@ import { sendMobileStructuredAgentSessionMessage } from './mobile-structured-age
import type { MobileStructuredSends } from './use-mobile-structured-sends'
import { structuredSessionOperationId } from './structured-session-operation-id'
import { timeoutForDeadline } from './mobile-structured-agent-session-rpc'
import { nativeChatTextWithFileReferences } from './mobile-native-chat-file-attachment'
import { nativeChatTextWithFileReferences } from './native-chat/composer/mobile-native-chat-file-attachment'
import {
actionableStructuredApproval,
actionableStructuredQuestion
@@ -1,3 +1,5 @@
import type * as MobileNativeChatImageAttachment from '../../../session/native-chat/composer/mobile-native-chat-image-attachment'
import type * as MobileNativeChatImageSend from '../../../session/native-chat/composer/mobile-native-chat-image-send'
import type { MountAdapter } from '../recording-scenario'
import { mountFixture } from '../recorder-fixture-shape'
import type { operationModuleLoader } from '../operation-module-loader'
@@ -80,9 +82,9 @@ export function clipboardImageMountAdapters(
}
},
'nativeChat.image-upload': ({ client, effect }) => {
const upload = modules.load<
typeof import('../../../session/mobile-native-chat-image-attachment')
>('mobile/src/session/mobile-native-chat-image-attachment.ts').uploadMobileNativeChatImages
const upload = modules.load<typeof MobileNativeChatImageAttachment>(
'mobile/src/session/native-chat/composer/mobile-native-chat-image-attachment.ts'
).uploadMobileNativeChatImages
let uploaded: unknown = 'unuploaded'
let failure: unknown = null
return {
@@ -116,8 +118,8 @@ export function clipboardImageMountAdapters(
}
},
'nativeChat.image-paste': ({ client }) => {
const paste = modules.load<typeof import('../../../session/mobile-native-chat-image-send')>(
'mobile/src/session/mobile-native-chat-image-send.ts'
const paste = modules.load<typeof MobileNativeChatImageSend>(
'mobile/src/session/native-chat/composer/mobile-native-chat-image-send.ts'
).pasteMobileNativeChatImagePaths
let pasted: unknown = 'unpasted'
let failure: unknown = null
@@ -1,3 +1,4 @@
import type * as MobileNativeChatFileSearch from '../../../session/native-chat/composer/use-mobile-native-chat-file-search'
import { hookMount, performHookAction } from '../hook-mount'
import type { MountOptions } from '../mounted-operation-module'
import { operationModuleLoader } from '../operation-module-loader'
@@ -10,9 +11,9 @@ export function fileInventoryMountAdapters(
): Record<string, MountAdapter> {
return {
'workspace.file-inventory': ({ client }) => {
const useSearch = modules.load<
typeof import('../../../session/use-mobile-native-chat-file-search')
>('mobile/src/session/use-mobile-native-chat-file-search.ts').useMobileNativeChatFileSearch
const useSearch = modules.load<typeof MobileNativeChatFileSearch>(
'mobile/src/session/native-chat/composer/use-mobile-native-chat-file-search.ts'
).useMobileNativeChatFileSearch
const operations = options.reference
? modules
.load('mobile/src/session/native-host-session-native-chat-operations.ts')
+1 -1
View File
@@ -513,7 +513,7 @@ describe('loader write fence', () => {
// the scan found no holder and no loader to look inside.
expect(
holders.map(({ path }) => relative(mobileRoot, path).split(/[/\\]/).join('/'))
).toContain('src/session/use-mobile-native-chat-file-search.ts')
).toContain('src/session/native-chat/composer/use-mobile-native-chat-file-search.ts')
expect(
holders.reduce((total, { path, source }) => total + loaders(path, source).length, 0)
).toBeGreaterThan(0)