Skip to content
Open
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
16 changes: 13 additions & 3 deletions apps/desktop/src/app/contrib/wiring.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,14 @@ export function ContribWiring({ children }: { children: ReactNode }) {
setMessages
})

const { connectionRef, gateway, gatewayRef, requestGateway: ambientRequestGateway } = useGatewayRequest()
const {
bindGatewayRequest,
bindGatewayRequestForOwner,
connectionRef,
gateway,
gatewayRef,
requestGateway: ambientRequestGateway
} = useGatewayRequest()

// When chrome stays on the launch backend (Bot Mode / all-profiles
// navigation), session-owned RPCs still have to hit the session's backend.
Expand Down Expand Up @@ -488,9 +495,12 @@ export function ContribWiring({ children }: { children: ReactNode }) {
} = useSessionActions({
activeSessionId,
activeSessionIdRef,
bindGatewayRequest,
bindGatewayRequestForOwner,
busyRef,
creatingSessionRef,
ensureSessionState,
gatewayRef,
getRouteToken,
getRoutedStoredSessionId,
navigate,
Expand Down Expand Up @@ -595,8 +605,8 @@ export function ContribWiring({ children }: { children: ReactNode }) {
const composer = useComposerActions({ activeSessionId, currentCwd, requestGateway })

const branchInNewChat = useCallback(
async (messageId?: string) => {
const branched = await branchCurrentSession(messageId)
async (messageId?: string, targetSessionId?: string) => {
const branched = await branchCurrentSession(messageId, targetSessionId)

if (branched) {
await refreshSessions().catch(() => undefined)
Expand Down
6 changes: 4 additions & 2 deletions apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1346,7 +1346,7 @@ describe('useGatewayBoot remote reconnect loop (real hook, fake socket)', () =>
// arg) β€” same contract as the sleep/wake reconnect: passing the active
// profile would retarget the primary socket after a live profile swap.
const lastCall = desktop.getConnection.mock.calls.at(-1) ?? []
expect(lastCall.length === 0 || lastCall[0] == null || lastCall[0] === '').toBe(true)
expect(lastCall.length === 0 || lastCall[0] == null || lastCall[0] === '' || lastCall[0] === 'default').toBe(true)
expect(desktop.getGatewayWsUrl).toHaveBeenCalledTimes(2)
expect(FakeWebSocket.instances).toHaveLength(2)
expect($gatewayState.get()).toBe('open')
Expand Down Expand Up @@ -1512,7 +1512,9 @@ describe('useGatewayBoot remote reconnect loop (real hook, fake socket)', () =>

const reconnectCalls = desktop.getConnection.mock.calls.slice(callsBeforeDrop)
expect(reconnectCalls.some(args => (args[0] ?? '').trim() === 'coder')).toBe(false)
expect(reconnectCalls.some(args => args.length === 0 || args[0] == null || args[0] === '')).toBe(true)
expect(
reconnectCalls.some(args => args.length === 0 || args[0] == null || args[0] === '' || args[0] === 'default')
).toBe(true)

const primaryReconnectSockets = FakeWebSocket.instances
.slice(socketsBeforeDrop)
Expand Down
20 changes: 18 additions & 2 deletions apps/desktop/src/app/gateway/hooks/use-gateway-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { useCallback, useEffect, useRef } from 'react'

import type { HermesGateway } from '@/hermes'
import { RECONNECT_ATTEMPT_TIMEOUT_MS, withTimeout } from '@/lib/with-timeout'
import { $gateway, ensureActiveGatewayOpen, isActivePrimary } from '@/store/gateway'
import {
$gateway,
acquireGatewayRequestLease,
acquireGatewayRequestLeaseForAgent,
ensureActiveGatewayOpen,
isActivePrimary
} from '@/store/gateway'
import { $activeGatewayProfile } from '@/store/profile'
import { $gatewayState, setConnection } from '@/store/session'

Expand Down Expand Up @@ -160,7 +166,17 @@ export function useGatewayRequest() {
[ensureGatewayOpen]
)

return { connectionRef, gateway, gatewayRef, requestGateway }
const bindGatewayRequest = useCallback(
(gateway: HermesGateway, profile: string) => acquireGatewayRequestLease(gateway, profile),
[]
)

const bindGatewayRequestForOwner = useCallback(
(connectionId: string, profile: string) => acquireGatewayRequestLeaseForAgent(connectionId, profile),
[]
)

return { bindGatewayRequest, bindGatewayRequestForOwner, connectionRef, gateway, gatewayRef, requestGateway }
}

const GATEWAY_TRANSPORT_ERROR_CODES = new Set([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ function Harness({
const selectedStoredSessionId = useStore($selectedStoredSessionId)
const busyRef = useRef(false)
const creatingSessionRef = useRef(false)
const gatewayRef = useRef(null)

const cache = useSessionStateCache({
activeSessionId,
Expand Down Expand Up @@ -306,9 +307,12 @@ function Harness({
const sessionActions = useSessionActions({
activeSessionId,
activeSessionIdRef: cache.activeSessionIdRef,
bindGatewayRequest: vi.fn() as never,
bindGatewayRequestForOwner: vi.fn() as never,
busyRef,
creatingSessionRef,
ensureSessionState: cache.ensureSessionState,
gatewayRef,
getRouteToken: () => 'token',
getRoutedStoredSessionId: () => null,
navigate: vi.fn() as never,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ interface HarnessHandle {

function Harness({
activeSessionIdRef: activeSessionIdRefProp,
branchCurrentSession,
busyRef,
getRoutedStoredSessionId,
getRuntimeIdForStoredSession,
Expand All @@ -129,6 +130,7 @@ function Harness({
createBackendSessionForSend
}: {
activeSessionIdRef?: MutableRefObject<string | null>
branchCurrentSession?: (messageId?: string, sessionId?: string) => Promise<boolean>
busyRef?: MutableRefObject<boolean>
getRoutedStoredSessionId?: () => null | string
getRuntimeIdForStoredSession?: (storedSessionId: string) => null | string
Expand Down Expand Up @@ -188,7 +190,7 @@ function Harness({
const actions = usePromptActions({
activeSessionId: activeSessionId === undefined ? RUNTIME_SESSION_ID : activeSessionId,
activeSessionIdRef,
branchCurrentSession: async () => true,
branchCurrentSession: branchCurrentSession ?? (async () => true),
busyRef: localBusyRef,
createBackendSessionForSend: createBackendSessionForSend ?? (async () => RUNTIME_SESSION_ID),
getRoutedStoredSessionId: getRoutedStoredSessionId ?? (() => null),
Expand Down Expand Up @@ -1425,6 +1427,26 @@ describe('usePromptActions slash.exec dispatch payloads', () => {
$queuedPromptsBySession.set({})
})

it('branches the tile runtime that invoked /branch, not the foreground session', async () => {
const branchCurrentSession = vi.fn(async (_messageId?: string, _sessionId?: string) => true)

let handle: HarnessHandle | null = null
await actRender(
<Harness
activeSessionId="foreground-runtime"
branchCurrentSession={branchCurrentSession}
onReady={h => (handle = h)}
refreshSessions={async () => undefined}
requestGateway={vi.fn(async () => ({}) as never)}
storedSessionId="foreground-stored"
/>
)

await handle!.submitText('/branch', { sessionId: 'tile-runtime' })

expect(branchCurrentSession).toHaveBeenCalledWith(undefined, 'tile-runtime')
})

it('binds slash output and the busy queue to the TARGET session, not the foreground selection', async () => {
// A tile (⌘T tab, split pane) routes its slash commands through this hook
// with an explicit runtime id while the foreground selection names a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ interface PromptActionsOptions {
activeSessionId: string | null
activeSessionIdRef: MutableRefObject<string | null>
busyRef: MutableRefObject<boolean>
branchCurrentSession: () => Promise<boolean>
branchCurrentSession: (messageId?: string, targetSessionId?: string) => Promise<boolean>
createBackendSessionForSend: (preview?: string | null) => Promise<string | null>
getRoutedStoredSessionId: () => null | string
getRuntimeIdForStoredSession: (storedSessionId: string) => null | string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ interface SlashCommandDeps {
text: string,
storedSessionId?: string | null
) => void
branchCurrentSession: () => Promise<boolean>
branchCurrentSession: (messageId?: string, targetSessionId?: string) => Promise<boolean>
busyRef: MutableRefObject<boolean>
copy: Translations['desktop']
createBackendSessionForSend: (preview?: string | null) => Promise<string | null>
Expand Down Expand Up @@ -494,8 +494,8 @@ export function useSlashCommand(deps: SlashCommandDeps) {
new: async () => {
startFreshSessionDraft()
},
branch: async () => {
await branchCurrentSession()
branch: async ({ sessionHint }) => {
await branchCurrentSession(undefined, sessionHint)
},
// /compress (alias /compact) runs the gateway's dedicated
// session.compress RPC β€” the TUI's path
Expand Down
Loading
Loading