fix(desktop): pin the project '+' new chat to the profile its tree is shown under
workspace-session-target.ts never set $newChatProfile, so a new session started from a project's "+" reached desktopSessionCreateParams with no intent and fell back to $activeGatewayProfile — which an in-flight profile swap can move between the click and Send, landing session.create on the wrong backend (#79005 flaw 3, second path). Pin the tree's profile (projectProfile()) via the same intent write newSessionInProfile uses. Fixes #79005
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { $activeGatewayProfile, $newChatProfile } from '@/store/profile'
|
||||
import { $projectScope, $projectTree, ALL_PROJECTS } from '@/store/projects'
|
||||
import {
|
||||
$currentBranch,
|
||||
@@ -22,6 +23,8 @@ describe('startWorkspaceSession', () => {
|
||||
setNewChatWorkspaceTarget(undefined)
|
||||
$projectScope.set(ALL_PROJECTS)
|
||||
$projectTree.set([])
|
||||
$activeGatewayProfile.set('default')
|
||||
$newChatProfile.set(null)
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
@@ -107,4 +110,22 @@ describe('startWorkspaceSession', () => {
|
||||
expect($newChatWorkspaceTarget.get()).toBeNull()
|
||||
expect($currentCwd.get()).toBe('')
|
||||
})
|
||||
|
||||
// #79005 flaw 3: the project "+" must pin the profile the tree is shown
|
||||
// under; otherwise session.create reads $activeGatewayProfile after a swap.
|
||||
it('pins the new chat to the profile the project tree is displayed under', () => {
|
||||
$activeGatewayProfile.set('work')
|
||||
$newChatProfile.set(null)
|
||||
|
||||
startWorkspaceSession({
|
||||
activeSessionIdRef: { current: null },
|
||||
path: '/workspace-work',
|
||||
requestGateway: vi.fn(() => new Promise<never>(() => {})),
|
||||
startFreshSessionDraft: vi.fn()
|
||||
})
|
||||
|
||||
$activeGatewayProfile.set('personal')
|
||||
|
||||
expect($newChatProfile.get()).toBe('work')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { MutableRefObject } from 'react'
|
||||
|
||||
import { followActiveSessionCwd, resolveNewSessionCwd } from '@/store/projects'
|
||||
import { pinNewChatProfile } from '@/store/profile'
|
||||
import { followActiveSessionCwd, projectProfile, resolveNewSessionCwd } from '@/store/projects'
|
||||
import {
|
||||
$newChatWorkspaceTargetGeneration,
|
||||
type NewChatWorkspaceTarget,
|
||||
@@ -26,6 +27,16 @@ export function startWorkspaceSession({
|
||||
requestGateway,
|
||||
startFreshSessionDraft
|
||||
}: WorkspaceSessionOptions): void {
|
||||
// The project tree is rendered under one profile; the "+" belongs to it.
|
||||
// Pin that intent now — otherwise desktopSessionCreateParams falls back to
|
||||
// $activeGatewayProfile, which a still-settling profile swap can move
|
||||
// between this click and Send (#79005). All-profiles view has no owner.
|
||||
const profile = projectProfile()
|
||||
|
||||
if (profile) {
|
||||
pinNewChatProfile(profile)
|
||||
}
|
||||
|
||||
// Home's "+" passes path=null on purpose ("no folder"). That must stay
|
||||
// detached — do NOT fall through to resolveNewSessionCwd(), which can still
|
||||
// return a default/remembered project folder and re-attach the last repo
|
||||
|
||||
@@ -857,6 +857,18 @@ function activateOnCurrentSource(target: string): Promise<void> {
|
||||
return connectionId ? ensureGatewayAgent(connectionId, target) : ensureGatewayProfile(target)
|
||||
}
|
||||
|
||||
// Pin the next new chat to `name` (legacy profile-only door) so session.create
|
||||
// reads the profile the user clicked "+" under, not whatever
|
||||
// $activeGatewayProfile holds once an in-flight profile swap settles (#79005).
|
||||
export function pinNewChatProfile(name: string): string {
|
||||
const target = normalizeProfileKey(name)
|
||||
$newChatProfile.set(target)
|
||||
$newChatRoute.set(null)
|
||||
captureNewChatSource(profilePickConnectionId(target))
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
// Start a fresh session in `name` WITHOUT collapsing the "All profiles" browse
|
||||
// view. Unlike selectProfile, it leaves $showAllProfiles untouched, so the
|
||||
// unified sidebar stays put — used by the per-profile "+" in the all-profiles
|
||||
@@ -864,10 +876,7 @@ function activateOnCurrentSource(target: string): Promise<void> {
|
||||
// is in. Points new chats at the profile and opens its backend so the next
|
||||
// message lands in the right place.
|
||||
export function newSessionInProfile(name: string): void {
|
||||
const target = normalizeProfileKey(name)
|
||||
$newChatProfile.set(target)
|
||||
$newChatRoute.set(null)
|
||||
captureNewChatSource(profilePickConnectionId(target))
|
||||
const target = pinNewChatProfile(name)
|
||||
requestFreshSession()
|
||||
// #81094: surface the failed dial instead of failing silently.
|
||||
void activateOnCurrentSource(target).catch((error: unknown) => {
|
||||
|
||||
@@ -349,7 +349,7 @@ async function gatewayRequest<T>(method: string, params: Record<string, unknown>
|
||||
return gateway.request<T>(method, params)
|
||||
}
|
||||
|
||||
function projectProfile(): null | string {
|
||||
export function projectProfile(): null | string {
|
||||
const profile = normalizeProfileKey($activeGatewayProfile.get())
|
||||
|
||||
return $profileScope.get() === ALL_PROFILES || profile === ALL_PROFILES ? null : profile
|
||||
|
||||
Reference in New Issue
Block a user