From 0ed6db77cf545975d3971a45f954edb9651ea91b Mon Sep 17 00:00:00 2001
From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Date: Thu, 13 Aug 2026 01:12:19 -0700
Subject: [PATCH] fix(mobile): open agent-cited external chat files (#14166)
* fix(mobile): open agent-cited external chat files
* fix(mobile): keep cited external files read-only
* refactor(mobile): derive cited-file mode from provenance
* fix(mobile): accept sentence-final cited paths
* fix(mobile): preserve cited SSH grant scope
* refactor(file-links): share location suffix parsing
---
.../h/[hostId]/files/preview/[worktreeId].tsx | 5 +
.../app/h/[hostId]/session/[worktreeId].tsx | 4 +
.../markdown-file-path-detection.ts | 14 +-
.../src/files/MobileFilePreviewScreen.test.ts | 15 +
mobile/src/files/MobileFilePreviewScreen.tsx | 3 +-
.../files/mobile-file-preview-editability.ts | 8 +-
.../files/mobile-file-preview-request.test.ts | 60 ++++
.../src/files/mobile-file-preview-request.ts | 3 +
.../files/mobile-file-preview-route.test.ts | 4 +
mobile/src/files/mobile-file-preview-route.ts | 6 +
.../files/mobile-file-preview-source.test.ts | 19 +-
.../src/files/mobile-file-preview-source.ts | 27 +-
.../mobile-terminal-artifact-grant-refresh.ts | 12 +-
mobile/src/session/mobile-file-tap-open.ts | 11 +-
.../mobile-native-chat-open-file.test.ts | 48 +++
.../use-mobile-file-tap-handlers.test.ts | 10 +-
.../session/use-mobile-file-tap-handlers.ts | 10 +
mobile/src/terminal/terminal-path-tap.ts | 24 +-
.../native-chat-file-provenance.test.ts | 129 ++++++++
.../native-chat-file-provenance.ts | 74 +++++
src/main/runtime/orca-runtime-files.test.ts | 275 +++++++++++++++++-
src/main/runtime/orca-runtime-files.ts | 165 ++++++++---
src/main/runtime/orca-runtime.ts | 8 +
.../files-terminal-path-resolution.test.ts | 28 +-
src/main/runtime/rpc/methods/files.ts | 9 +-
.../src/lib/explicit-file-link-target.ts | 27 +-
src/shared/file-link-location.test.ts | 31 ++
src/shared/file-link-location.ts | 19 ++
src/shared/runtime-types.ts | 7 +
29 files changed, 948 insertions(+), 107 deletions(-)
create mode 100644 src/main/native-chat/native-chat-file-provenance.test.ts
create mode 100644 src/main/native-chat/native-chat-file-provenance.ts
create mode 100644 src/shared/file-link-location.test.ts
create mode 100644 src/shared/file-link-location.ts
diff --git a/mobile/app/h/[hostId]/files/preview/[worktreeId].tsx b/mobile/app/h/[hostId]/files/preview/[worktreeId].tsx
index 65412d51958..df60f8fc117 100644
--- a/mobile/app/h/[hostId]/files/preview/[worktreeId].tsx
+++ b/mobile/app/h/[hostId]/files/preview/[worktreeId].tsx
@@ -10,6 +10,11 @@ export default function MobileFilePreviewRoute() {
source?: string | string[]
absolutePath?: string | string[]
grantId?: string | string[]
+ terminal?: string | string[]
+ pathText?: string | string[]
+ cwd?: string | string[]
+ nativeChatTab?: string | string[]
+ nativeChatSession?: string | string[]
line?: string | string[]
column?: string | string[]
name?: string | string[]
diff --git a/mobile/app/h/[hostId]/session/[worktreeId].tsx b/mobile/app/h/[hostId]/session/[worktreeId].tsx
index 4accc0466ab..123a1212eb0 100644
--- a/mobile/app/h/[hostId]/session/[worktreeId].tsx
+++ b/mobile/app/h/[hostId]/session/[worktreeId].tsx
@@ -3105,6 +3105,10 @@ export default function SessionScreen() {
hostId,
worktreeId,
worktreeName: routeWorktreeName,
+ nativeChatSessionId:
+ activeSessionTab?.type === 'terminal'
+ ? (activeSessionTab.agentStatus?.providerSession?.id ?? null)
+ : null,
activeHandleRef,
terminalCwdRef,
openBrowser: (url) => void handleCreateBrowserRef.current?.(url),
diff --git a/mobile/src/components/markdown-file-path-detection.ts b/mobile/src/components/markdown-file-path-detection.ts
index 5e320a3f10c..c51151b9ebf 100644
--- a/mobile/src/components/markdown-file-path-detection.ts
+++ b/mobile/src/components/markdown-file-path-detection.ts
@@ -3,6 +3,8 @@
// viewer). We deliberately favor precision over recall: a missed path is a minor
// annoyance, but a false positive on prose or a version number is a broken tap.
+import { parseFileLinkLocation } from '../../../src/shared/file-link-location'
+
export type FilePathSegment =
| { type: 'text'; value: string }
| { type: 'file'; value: string; path: string }
@@ -101,8 +103,6 @@ function hasMidTokenAt(candidate: string): boolean {
return /[^\\/]@/.test(candidate)
}
-const LINE_SUFFIX_PATTERN = /^(.+?):([1-9]\d*)(?::([1-9]\d*))?$/
-
/**
* Split an agent-style `path:line(:col)` citation into its parts. Windows drive
* colons are safe: only a trailing all-digit suffix is treated as a line ref.
@@ -112,14 +112,14 @@ export function splitFilePathLineSuffix(pathText: string): {
line: number | null
column: number | null
} {
- const match = LINE_SUFFIX_PATTERN.exec(pathText)
- if (!match) {
+ const parsed = parseFileLinkLocation(pathText)
+ if (!parsed || (parsed.line === null && parsed.column === null)) {
return { path: pathText, line: null, column: null }
}
return {
- path: match[1]!,
- line: Number.parseInt(match[2]!, 10),
- column: match[3] ? Number.parseInt(match[3], 10) : null
+ path: parsed.pathText,
+ line: parsed.line,
+ column: parsed.column
}
}
diff --git a/mobile/src/files/MobileFilePreviewScreen.test.ts b/mobile/src/files/MobileFilePreviewScreen.test.ts
index 1595454a12a..fa61f7351c6 100644
--- a/mobile/src/files/MobileFilePreviewScreen.test.ts
+++ b/mobile/src/files/MobileFilePreviewScreen.test.ts
@@ -39,6 +39,21 @@ describe('MobileFilePreviewScreen', () => {
).toBe(false)
})
+ it('treats native-chat artifact grants as read-only', () => {
+ expect(
+ isEditableMobileTerminalArtifactPreview(
+ {
+ status: 'ready',
+ kind: 'text',
+ content: '
Result
',
+ truncated: false,
+ byteLength: 15
+ },
+ true
+ )
+ ).toBe(false)
+ })
+
it('keeps dirty terminal artifact drafts protected while preview is waiting for reconnect', () => {
const sourceKey = sourceKeyForPreview({
source: 'terminalArtifact',
diff --git a/mobile/src/files/MobileFilePreviewScreen.tsx b/mobile/src/files/MobileFilePreviewScreen.tsx
index 0879e02ec7e..3f56890243a 100644
--- a/mobile/src/files/MobileFilePreviewScreen.tsx
+++ b/mobile/src/files/MobileFilePreviewScreen.tsx
@@ -181,7 +181,8 @@ export function MobileFilePreviewScreen({ route }: Props) {
)
const meta = previewParams ? `${worktreeLabel} - ${displayPath}` : 'Preview'
const isEditableTerminalArtifact =
- previewSource?.source === 'terminalArtifact' && isEditableMobileTerminalArtifactPreview(preview)
+ previewSource?.source === 'terminalArtifact' &&
+ isEditableMobileTerminalArtifactPreview(preview, previewSource.readOnly === true)
const canSaveArtifact =
isEditableTerminalArtifact &&
draftSourceKeyRef.current === previewSourceKey &&
diff --git a/mobile/src/files/mobile-file-preview-editability.ts b/mobile/src/files/mobile-file-preview-editability.ts
index ff0fa18d3cb..d8ed196e5cb 100644
--- a/mobile/src/files/mobile-file-preview-editability.ts
+++ b/mobile/src/files/mobile-file-preview-editability.ts
@@ -3,7 +3,13 @@ import type {
MobileFilePreviewSource
} from './mobile-file-preview-request'
-export function isEditableMobileTerminalArtifactPreview(preview: MobileFilePreviewResult): boolean {
+export function isEditableMobileTerminalArtifactPreview(
+ preview: MobileFilePreviewResult,
+ readOnly = false
+): boolean {
+ if (readOnly) {
+ return false
+ }
return (
(preview.status === 'ready' && preview.kind !== 'image' && !preview.truncated) ||
preview.status === 'empty'
diff --git a/mobile/src/files/mobile-file-preview-request.test.ts b/mobile/src/files/mobile-file-preview-request.test.ts
index f7f77fabd19..fe15cad0b07 100644
--- a/mobile/src/files/mobile-file-preview-request.test.ts
+++ b/mobile/src/files/mobile-file-preview-request.test.ts
@@ -179,6 +179,47 @@ describe('mobile-file-preview-request', () => {
})
})
+ it('refreshes a native-chat artifact grant with the same transcript provenance', async () => {
+ const client = clientWithResponses([
+ fail('terminal_file_grant_expired'),
+ ok({
+ exists: true,
+ isDirectory: false,
+ openTarget: {
+ kind: 'absolute-file',
+ absolutePath: '/Users/ada/orca-plans/result.html',
+ grantId: 'grant-2',
+ readOnly: true
+ }
+ }),
+ ok({ content: 'Result
', truncated: false, byteLength: 15 })
+ ])
+
+ const onTerminalArtifactSourceRefreshed = vi.fn()
+ await loadMobileFilePreview(
+ client,
+ {
+ source: 'terminalArtifact',
+ worktreeId: 'wt-1',
+ absolutePath: '/Users/ada/orca-plans/result.html',
+ grantId: 'grant-1',
+ pathText: '~/orca-plans/result.html',
+ nativeChatContext: { tabId: 'tab-1', sessionId: 'session-1' }
+ },
+ undefined,
+ { onTerminalArtifactSourceRefreshed }
+ )
+
+ expect(client.sendRequest).toHaveBeenNthCalledWith(2, 'files.resolveTerminalPath', {
+ worktree: 'id:wt-1',
+ pathText: '~/orca-plans/result.html',
+ nativeChatContext: { tabId: 'tab-1', sessionId: 'session-1' }
+ })
+ expect(onTerminalArtifactSourceRefreshed).toHaveBeenCalledWith(
+ expect.objectContaining({ grantId: 'grant-2', readOnly: true })
+ )
+ })
+
it.each([
['terminal_file_grant_expired'],
['terminal_file_grant_mismatch'],
@@ -242,6 +283,25 @@ describe('mobile-file-preview-request', () => {
})
})
+ it('does not send writes for read-only native-chat artifacts', async () => {
+ const client = clientWith(ok({ ok: true }))
+
+ await expect(
+ saveMobileTerminalArtifactPreview(
+ client,
+ {
+ source: 'terminalArtifact',
+ worktreeId: 'wt-1',
+ absolutePath: '/Users/ada/orca-plans/result.html',
+ grantId: 'grant-1',
+ readOnly: true
+ },
+ 'Changed
'
+ )
+ ).resolves.toMatchObject({ status: 'error' })
+ expect(client.sendRequest).not.toHaveBeenCalled()
+ })
+
it('does not refresh and retry a failed terminal artifact save without a base content check', async () => {
const client = clientWithResponses([fail('terminal_file_grant_mismatch')])
diff --git a/mobile/src/files/mobile-file-preview-request.ts b/mobile/src/files/mobile-file-preview-request.ts
index ff6cd755c50..63b61326654 100644
--- a/mobile/src/files/mobile-file-preview-request.ts
+++ b/mobile/src/files/mobile-file-preview-request.ts
@@ -116,6 +116,9 @@ export async function saveMobileTerminalArtifactPreview(
content: string,
options: TerminalArtifactSaveOptions = {}
): Promise {
+ if (source.readOnly) {
+ return previewError('This file is read-only')
+ }
let writeSource = source
if (typeof options.baseContent === 'string') {
const verified = await verifyTerminalArtifactBaseContent(
diff --git a/mobile/src/files/mobile-file-preview-route.test.ts b/mobile/src/files/mobile-file-preview-route.test.ts
index ac1888ed46f..784313464ee 100644
--- a/mobile/src/files/mobile-file-preview-route.test.ts
+++ b/mobile/src/files/mobile-file-preview-route.test.ts
@@ -65,6 +65,8 @@ describe('mobile-file-preview-route', () => {
terminal: 'term-1',
pathText: 'result.json',
cwd: '/tmp/run',
+ nativeChatTab: 'tab-1',
+ nativeChatSession: 'session-1',
line: '12',
column: '3'
})
@@ -80,6 +82,8 @@ describe('mobile-file-preview-route', () => {
terminal: 'term-1',
pathText: 'result.json',
cwd: '/tmp/run',
+ nativeChatTab: 'tab-1',
+ nativeChatSession: 'session-1',
line: '12',
column: '3'
}
diff --git a/mobile/src/files/mobile-file-preview-route.ts b/mobile/src/files/mobile-file-preview-route.ts
index 1b277de7faa..f0e7dfbbf49 100644
--- a/mobile/src/files/mobile-file-preview-route.ts
+++ b/mobile/src/files/mobile-file-preview-route.ts
@@ -10,6 +10,8 @@ export type MobileFilePreviewRouteParams = {
terminal?: string
pathText?: string
cwd?: string
+ nativeChatTab?: string
+ nativeChatSession?: string
line?: string
column?: string
name?: string
@@ -35,6 +37,8 @@ type RawPreviewRouteParams = {
terminal?: MobileFilePreviewParamValue
pathText?: MobileFilePreviewParamValue
cwd?: MobileFilePreviewParamValue
+ nativeChatTab?: MobileFilePreviewParamValue
+ nativeChatSession?: MobileFilePreviewParamValue
line?: MobileFilePreviewParamValue
column?: MobileFilePreviewParamValue
name?: MobileFilePreviewParamValue
@@ -76,6 +80,8 @@ export function normalizeMobileFilePreviewRouteParams(
terminal: optionalSingleParam(params.terminal),
pathText: optionalSingleParam(params.pathText),
cwd: optionalSingleParam(params.cwd),
+ nativeChatTab: optionalSingleParam(params.nativeChatTab),
+ nativeChatSession: optionalSingleParam(params.nativeChatSession),
line: optionalSingleParam(params.line),
column: optionalSingleParam(params.column),
name: optionalSingleParam(params.name),
diff --git a/mobile/src/files/mobile-file-preview-source.test.ts b/mobile/src/files/mobile-file-preview-source.test.ts
index b5fec782cfc..9c326fd0278 100644
--- a/mobile/src/files/mobile-file-preview-source.test.ts
+++ b/mobile/src/files/mobile-file-preview-source.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
-import { sourceKeyForPreview } from './mobile-file-preview-source'
+import { previewSourceFromRoute, sourceKeyForPreview } from './mobile-file-preview-source'
describe('mobile-file-preview-source', () => {
it('uses structured preview keys so colons in paths cannot collide', () => {
@@ -25,4 +25,21 @@ describe('mobile-file-preview-source', () => {
sourceKeyForPreview({ source: 'worktree', worktreeId: 'wt:1', relativePath: 'a:b.ts' })
).toBe(JSON.stringify(['worktree', 'wt:1', 'a:b.ts']))
})
+
+ it('makes native-chat artifact previews read-only', () => {
+ expect(
+ previewSourceFromRoute({
+ hostId: 'host-1',
+ worktreeId: 'wt-1',
+ source: 'terminalArtifact',
+ absolutePath: '/tmp/result.html',
+ grantId: 'grant-1',
+ nativeChatTab: 'tab-1',
+ nativeChatSession: 'session-1'
+ })
+ ).toMatchObject({
+ nativeChatContext: { tabId: 'tab-1', sessionId: 'session-1' },
+ readOnly: true
+ })
+ })
})
diff --git a/mobile/src/files/mobile-file-preview-source.ts b/mobile/src/files/mobile-file-preview-source.ts
index b1b23757a6e..2c1f01631e6 100644
--- a/mobile/src/files/mobile-file-preview-source.ts
+++ b/mobile/src/files/mobile-file-preview-source.ts
@@ -15,7 +15,16 @@ export function previewSourceFromRoute(
grantId: params.grantId,
...(params.terminal ? { terminalHandle: params.terminal } : {}),
...(params.pathText ? { pathText: params.pathText } : {}),
- ...(params.cwd ? { cwd: params.cwd } : {})
+ ...(params.cwd ? { cwd: params.cwd } : {}),
+ ...(params.nativeChatTab && params.nativeChatSession
+ ? {
+ nativeChatContext: {
+ tabId: params.nativeChatTab,
+ sessionId: params.nativeChatSession
+ },
+ readOnly: true as const
+ }
+ : {})
}
}
if (!params.relativePath) {
@@ -28,12 +37,12 @@ export function sourceKeyForPreview(source: MobileFilePreviewSource | null): str
if (!source) {
return null
}
- return source.source === 'terminalArtifact'
- ? JSON.stringify([
- 'terminal',
- source.worktreeId,
- source.absolutePath,
- source.terminalHandle ?? ''
- ])
- : JSON.stringify(['worktree', source.worktreeId, source.relativePath])
+ if (source.source !== 'terminalArtifact') {
+ return JSON.stringify(['worktree', source.worktreeId, source.relativePath])
+ }
+ const key = ['terminal', source.worktreeId, source.absolutePath, source.terminalHandle ?? '']
+ if (source.nativeChatContext) {
+ key.push(source.nativeChatContext.tabId, source.nativeChatContext.sessionId)
+ }
+ return JSON.stringify(key)
}
diff --git a/mobile/src/files/mobile-terminal-artifact-grant-refresh.ts b/mobile/src/files/mobile-terminal-artifact-grant-refresh.ts
index a8864119be4..cbe5ffdebd8 100644
--- a/mobile/src/files/mobile-terminal-artifact-grant-refresh.ts
+++ b/mobile/src/files/mobile-terminal-artifact-grant-refresh.ts
@@ -1,3 +1,4 @@
+import type { RuntimeNativeChatFileContext } from '../../../src/shared/runtime-types'
import type { RpcClient } from '../transport/rpc-client'
import type { RpcFailure, RpcResponse, RpcSuccess } from '../transport/types'
import { isTerminalArtifactGrantError } from './terminal-artifact-grant-error'
@@ -12,6 +13,8 @@ export type MobileTerminalArtifactPreviewSource = {
terminalHandle?: string
pathText?: string
cwd?: string
+ nativeChatContext?: RuntimeNativeChatFileContext
+ readOnly?: true
}
export type TerminalArtifactRetryOptions = {
@@ -32,7 +35,8 @@ export async function refreshTerminalArtifactSourceAfterGrantFailure(
worktree: `id:${source.worktreeId}`,
pathText: source.pathText ?? source.absolutePath,
...(source.cwd ? { cwd: source.cwd } : {}),
- ...(source.terminalHandle ? { terminal: source.terminalHandle } : {})
+ ...(source.terminalHandle ? { terminal: source.terminalHandle } : {}),
+ ...(source.nativeChatContext ? { nativeChatContext: source.nativeChatContext } : {})
})
if (!refreshed.ok) {
return null
@@ -51,7 +55,9 @@ export async function refreshTerminalArtifactSourceAfterGrantFailure(
grantId: result.openTarget.grantId,
...(source.terminalHandle ? { terminalHandle: source.terminalHandle } : {}),
...(source.pathText ? { pathText: source.pathText } : {}),
- ...(source.cwd ? { cwd: source.cwd } : {})
+ ...(source.cwd ? { cwd: source.cwd } : {}),
+ ...(source.nativeChatContext ? { nativeChatContext: source.nativeChatContext } : {}),
+ ...(source.readOnly || result.openTarget.readOnly === true ? { readOnly: true as const } : {})
}
}
@@ -68,7 +74,7 @@ function isTerminalArtifactGrantFailure(
function isTerminalArtifactResolution(result: unknown): result is {
exists: true
isDirectory: false
- openTarget: { kind: 'absolute-file'; absolutePath: string; grantId: string }
+ openTarget: { kind: 'absolute-file'; absolutePath: string; grantId: string; readOnly?: true }
} {
if (!result || typeof result !== 'object') {
return false
diff --git a/mobile/src/session/mobile-file-tap-open.ts b/mobile/src/session/mobile-file-tap-open.ts
index 627d9a5ff39..13c306af649 100644
--- a/mobile/src/session/mobile-file-tap-open.ts
+++ b/mobile/src/session/mobile-file-tap-open.ts
@@ -1,5 +1,6 @@
import type {
RuntimeFileOpenResult,
+ RuntimeNativeChatFileContext,
RuntimeTerminalPathResolution
} from '../../../src/shared/runtime-types'
import { filesystemPathToFileUri } from '../../../src/shared/file-uri-path'
@@ -22,6 +23,7 @@ export type OpenMobileFileTapOptions = {
terminalHandle?: string | null
pathText: string
cwd?: string | null
+ nativeChatContext?: RuntimeNativeChatFileContext | null
line: number | null
column: number | null
pushPreviewRoute: (href: ReturnType) => void
@@ -80,7 +82,8 @@ async function openMobileFileTapAsync(
...(options.terminalHandle && options.terminalHandle.trim().length > 0
? { terminal: options.terminalHandle }
: {}),
- ...(options.cwd && options.cwd.trim().length > 0 ? { cwd: options.cwd } : {})
+ ...(options.cwd && options.cwd.trim().length > 0 ? { cwd: options.cwd } : {}),
+ ...(options.nativeChatContext ? { nativeChatContext: options.nativeChatContext } : {})
},
{ timeoutMs: 10_000 }
)
@@ -116,6 +119,12 @@ async function openMobileFileTapAsync(
...(options.terminalHandle && options.terminalHandle.trim().length > 0
? { terminal: options.terminalHandle }
: {}),
+ ...(options.nativeChatContext
+ ? {
+ nativeChatTab: options.nativeChatContext.tabId,
+ nativeChatSession: options.nativeChatContext.sessionId
+ }
+ : {}),
name: displayNameFromPath(resolved.openTarget.absolutePath),
...(options.line !== null ? { line: String(options.line) } : {}),
...(options.column !== null ? { column: String(options.column) } : {}),
diff --git a/mobile/src/session/mobile-native-chat-open-file.test.ts b/mobile/src/session/mobile-native-chat-open-file.test.ts
index d44530e5cd0..bef222721ac 100644
--- a/mobile/src/session/mobile-native-chat-open-file.test.ts
+++ b/mobile/src/session/mobile-native-chat-open-file.test.ts
@@ -65,6 +65,54 @@ describe('openMobileNativeChatFileTap', () => {
)
})
+ it('carries the native-chat session identity into resolution and grant refresh', async () => {
+ const sendRequest = vi.fn(async () =>
+ ok({
+ worktree: 'wt-1',
+ relativePath: null,
+ absolutePath: '/Users/ada/orca-plans/result.html',
+ exists: true,
+ isDirectory: false,
+ openTarget: {
+ kind: 'absolute-file',
+ provider: 'local',
+ absolutePath: '/Users/ada/orca-plans/result.html',
+ grantId: 'grant-1',
+ readOnly: true
+ }
+ })
+ )
+ const options = baseOptions({ sendRequest })
+
+ openMobileNativeChatFileTap({
+ ...options,
+ pathText: '~/orca-plans/result.html',
+ nativeChatContext: { tabId: 'tab-1', sessionId: 'session-1' }
+ })
+ await Promise.resolve()
+
+ expect(sendRequest).toHaveBeenCalledWith(
+ 'files.resolveTerminalPath',
+ {
+ worktree: 'id:wt-1',
+ pathText: '~/orca-plans/result.html',
+ crossWorkspace: true,
+ nativeChatContext: { tabId: 'tab-1', sessionId: 'session-1' }
+ },
+ { timeoutMs: 10_000 }
+ )
+ expect(options.pushPreviewRoute).toHaveBeenCalledWith({
+ pathname: '/h/[hostId]/files/preview/[worktreeId]',
+ params: expect.objectContaining({
+ source: 'terminalArtifact',
+ absolutePath: '/Users/ada/orca-plans/result.html',
+ grantId: 'grant-1',
+ nativeChatTab: 'tab-1',
+ nativeChatSession: 'session-1'
+ })
+ })
+ })
+
it('parses a :line:col citation and opens the mobile preview route', async () => {
const sendRequest = vi.fn(async () => worktreeFileResolution('src/app.ts'))
const options = baseOptions({ sendRequest })
diff --git a/mobile/src/session/use-mobile-file-tap-handlers.test.ts b/mobile/src/session/use-mobile-file-tap-handlers.test.ts
index 0cfa617f269..21f07562459 100644
--- a/mobile/src/session/use-mobile-file-tap-handlers.test.ts
+++ b/mobile/src/session/use-mobile-file-tap-handlers.test.ts
@@ -34,12 +34,13 @@ describe('useMobileFileTapHandlers', () => {
hostId: 'host-1',
worktreeId: 'wt-1',
worktreeName: 'Orca',
+ nativeChatSessionId: 'session-1',
activeHandleRef: { current: 'terminal-1' as string | null },
terminalCwdRef: { current: new Map([['terminal-1', '/repo/sub']]) },
openBrowser: vi.fn(),
fetchSessionTabs: vi.fn(async () => {}),
getSessionTabs: () => [],
- getActiveSessionTabId: () => null,
+ getActiveSessionTabId: () => 'terminal-tab',
getActiveSessionTabType: () => 'terminal',
switchSessionTab: vi.fn(),
scheduleDelayedAction: vi.fn(),
@@ -131,7 +132,12 @@ describe('useMobileFileTapHandlers', () => {
expect(sendRequest).toHaveBeenCalledWith(
'files.resolveTerminalPath',
- { worktree: 'id:wt-1', pathText: 'mobile/src/x.ts', crossWorkspace: true },
+ {
+ worktree: 'id:wt-1',
+ pathText: 'mobile/src/x.ts',
+ crossWorkspace: true,
+ nativeChatContext: { tabId: 'terminal-tab', sessionId: 'session-1' }
+ },
{ timeoutMs: 10_000 }
)
expect(options.reportChatTapFailure).toHaveBeenCalledWith("Couldn't open mobile/src/x.ts:12")
diff --git a/mobile/src/session/use-mobile-file-tap-handlers.ts b/mobile/src/session/use-mobile-file-tap-handlers.ts
index 276be5b566f..67995115f45 100644
--- a/mobile/src/session/use-mobile-file-tap-handlers.ts
+++ b/mobile/src/session/use-mobile-file-tap-handlers.ts
@@ -10,6 +10,7 @@ type MobileFileTapHandlerOptions = {
hostId: string
worktreeId: string
worktreeName?: string
+ nativeChatSessionId: string | null
activeHandleRef: MutableRefObject
terminalCwdRef: MutableRefObject