diff --git a/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx b/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx
index b4be5638138..6d0224f4a4b 100644
--- a/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx
+++ b/mobile/src/components/pr-sidebar/PrTabBottomAction.tsx
@@ -1,5 +1,5 @@
import { ActivityIndicator, Pressable, Text, View } from 'react-native'
-import { GitMerge, GitPullRequest, type LucideIcon } from 'lucide-react-native'
+import { GitMerge, GitPullRequest, Sparkles, type LucideIcon } from 'lucide-react-native'
import { colors, feedback, pressedStyle } from '../../theme/mobile-theme'
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
@@ -7,19 +7,33 @@ import { resolveGitHubPRMergeMethods } from '../../../../src/shared/github/pull-
import { resolveGitHubPRMergeState } from '../../../../src/shared/github/pull-request-merge-state'
import { MOBILE_GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY } from '../../../../src/shared/source-control-runtime-capabilities'
import { useRouteCapability } from '../../transport/use-workspace-client'
+import { buildResolveConflictsPrompt } from '../../session/pr-ai-triage-prompt'
+import { useMobileLaunchAgentLabel } from '../../session/use-mobile-launch-agent-label'
+import { useHostProtocolGates } from '../host-protocol-gates-context'
+import { AgentLaunchNotice } from '../AgentLaunchNotice'
import { presentPrMerge } from './pr-actions-state'
import type { MobilePrTab } from './use-mobile-pr-tab'
import { prTabStyles as styles } from './pr-tab-styles'
type Tone = 'primary' | 'merge' | 'secondary'
-// The PR tab's foot: the diff-review dock always shows it, the hub only while its bar is on a local step.
+// The diff-review dock's bottom action; in the hub the next-step bar is the one bottom action instead.
export function PrTabBottomAction({ tab, bottomInset }: { tab: MobilePrTab; bottomInset: number }) {
useMobileColorScheme()
const t = useMobileTranslation()
const { state, actions } = tab
const canMarkReady = useRouteCapability(MOBILE_GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY)
const footerStyle = [styles.footer, { paddingBottom: Math.max(bottomInset, 12) }]
+ const conflicting =
+ state.kind === 'ready' && resolveGitHubPRMergeState(state.data.pr).kind === 'conflicts'
+ const { hostCapabilities } = useHostProtocolGates()
+ const agent = useMobileLaunchAgentLabel({
+ client: tab.client,
+ worktreeId: tab.worktreeId,
+ hostCapabilities,
+ actionId: 'resolveConflicts',
+ enabled: conflicting
+ })
if (state.kind === 'none') {
return (
@@ -82,6 +96,42 @@ export function PrTabBottomAction({ tab, bottomInset }: { tab: MobilePrTab; bott
const merge = presentPrMerge(effective, defaultMethod)
const decision = resolveGitHubPRMergeState(effective)
const autoMergeBusy = actions.isBusy({ kind: 'autoMerge' })
+ // The dock has no bar, so conflicts get the bar's own fix here, named by the button rather than a reason line.
+ if (decision.kind === 'conflicts') {
+ const notice = tab.triage.noticeFor('resolve-conflicts')
+ return (
+
+ {errorLine}
+
+
+ void tab.triage.launch('resolve-conflicts', () =>
+ buildResolveConflictsPrompt({
+ prNumber: pr.number,
+ baseRef: pr.conflictSummary?.baseRef ?? pr.baseRefName ?? null,
+ files: pr.conflictSummary?.files ?? [],
+ worktreeId: tab.worktreeId
+ })
+ )
+ }
+ busy={tab.triage.isBusy('resolve-conflicts')}
+ disabled={tab.triage.availability !== 'available'}
+ />
+
+ )
+ }
const reason = merge.hint ? (
diff --git a/mobile/src/source-control/MobileSourceControlPanel.tsx b/mobile/src/source-control/MobileSourceControlPanel.tsx
index 0385302c637..fc162b47d91 100644
--- a/mobile/src/source-control/MobileSourceControlPanel.tsx
+++ b/mobile/src/source-control/MobileSourceControlPanel.tsx
@@ -371,7 +371,7 @@ export function MobileSourceControlPanel({
{ready ? (
({
vi.mock('./MobileSourceControlNextStepBar', () => ({ MobileSourceControlNextStepBar: () => null }))
vi.mock('./use-mobile-source-control-next-step', () => ({
useMobileSourceControlNextStep: () => ({
- offersMerge: false,
view: { kind: 'hidden' },
notice: null,
agentNotice: null,
diff --git a/mobile/src/source-control/use-mobile-source-control-next-step.ts b/mobile/src/source-control/use-mobile-source-control-next-step.ts
index 2d1f52fdaa5..e483b80bee6 100644
--- a/mobile/src/source-control/use-mobile-source-control-next-step.ts
+++ b/mobile/src/source-control/use-mobile-source-control-next-step.ts
@@ -253,11 +253,10 @@ export function useMobileSourceControlNextStep({
: step.kind === 'resolve_conflicts'
? 'resolve-conflicts'
: null
- // On the PR tab the body's own AI button already shows this launcher's notice.
- const agentNotice =
- triageKey && !showWorkflowErrors
- ? { ...triage.noticeFor(triageKey), availability: triage.availability }
- : null
+ // The bar's agent button is the only one on either tab, so its launch notice shows under it.
+ const agentNotice = triageKey
+ ? { ...triage.noticeFor(triageKey), availability: triage.availability }
+ : null
const workflowError = showWorkflowErrors
? (state.actionError ?? state.commitFailureRecovery?.error ?? null)
: null