mirror of
https://github.com/stablyai/orca.git
synced 2026-10-13 08:02:52 +00:00
Fix onboarding folder agent startup seeding
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -4,7 +4,7 @@ import {
|
||||
buildDismissedOnboardingFolderAgentStartup,
|
||||
buildOnboardingFolderAgentStartup,
|
||||
shouldSeedFolderAgentAfterDismissedOnboarding
|
||||
} from './onboarding-folder-agent-startup'
|
||||
} from '@/lib/onboarding-folder-agent-startup'
|
||||
|
||||
describe('buildOnboardingFolderAgentStartup', () => {
|
||||
it('queues the persisted default agent with onboarding telemetry', () => {
|
||||
@@ -43,20 +43,38 @@ describe('buildOnboardingFolderAgentStartup', () => {
|
||||
|
||||
it('seeds after a dismissed onboarding run before any project was added', () => {
|
||||
expect(
|
||||
shouldSeedFolderAgentAfterDismissedOnboarding({
|
||||
...getDefaultOnboardingState(),
|
||||
outcome: 'dismissed'
|
||||
})
|
||||
shouldSeedFolderAgentAfterDismissedOnboarding(
|
||||
{
|
||||
...getDefaultOnboardingState(),
|
||||
outcome: 'dismissed'
|
||||
},
|
||||
false
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('does not seed after another project was already added outside onboarding', () => {
|
||||
expect(
|
||||
shouldSeedFolderAgentAfterDismissedOnboarding(
|
||||
{
|
||||
...getDefaultOnboardingState(),
|
||||
outcome: 'dismissed'
|
||||
},
|
||||
true
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('does not seed after onboarding already added a project', () => {
|
||||
expect(
|
||||
shouldSeedFolderAgentAfterDismissedOnboarding({
|
||||
...getDefaultOnboardingState(),
|
||||
outcome: 'dismissed',
|
||||
checklist: { ...getDefaultOnboardingState().checklist, addedFolder: true }
|
||||
})
|
||||
shouldSeedFolderAgentAfterDismissedOnboarding(
|
||||
{
|
||||
...getDefaultOnboardingState(),
|
||||
outcome: 'dismissed',
|
||||
checklist: { ...getDefaultOnboardingState().checklist, addedFolder: true }
|
||||
},
|
||||
false
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
@@ -68,7 +86,8 @@ describe('buildOnboardingFolderAgentStartup', () => {
|
||||
defaultTuiAgent: 'codex',
|
||||
agentCmdOverrides: { codex: 'echo onboarding-folder-agent' }
|
||||
},
|
||||
{ ...getDefaultOnboardingState(), outcome: 'dismissed' }
|
||||
{ ...getDefaultOnboardingState(), outcome: 'dismissed' },
|
||||
false
|
||||
)
|
||||
).toEqual({
|
||||
command: 'echo onboarding-folder-agent',
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
export {
|
||||
buildDismissedOnboardingFolderAgentStartup,
|
||||
buildOnboardingFolderAgentStartup,
|
||||
shouldSeedFolderAgentAfterDismissedOnboarding,
|
||||
type OnboardingFolderAgentStartup
|
||||
} from '@/lib/onboarding-folder-agent-startup'
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useAppStore } from '@/store'
|
||||
import { activateAndRevealWorktree } from '@/lib/worktree-activation'
|
||||
import { buildDismissedOnboardingFolderAgentStartup } from '@/lib/onboarding-folder-agent-startup'
|
||||
|
||||
const NonGitFolderDialog = React.memo(function NonGitFolderDialog() {
|
||||
const activeModal = useAppStore((s) => s.activeModal)
|
||||
@@ -26,6 +27,8 @@ const NonGitFolderDialog = React.memo(function NonGitFolderDialog() {
|
||||
if (connectionId && folderPath) {
|
||||
void (async () => {
|
||||
try {
|
||||
const stateBeforeAdd = useAppStore.getState()
|
||||
const hadProjectBeforeAdd = stateBeforeAdd.repos.length > 0
|
||||
const result = await window.api.repos.addRemote({
|
||||
connectionId,
|
||||
remotePath: folderPath,
|
||||
@@ -46,7 +49,15 @@ const NonGitFolderDialog = React.memo(function NonGitFolderDialog() {
|
||||
// worktree reveals it in the sidebar and opens the workspace.
|
||||
const folderWorktree = useAppStore.getState().worktreesByRepo[repo.id]?.[0]
|
||||
if (folderWorktree) {
|
||||
activateAndRevealWorktree(folderWorktree.id)
|
||||
const onboarding = await window.api.onboarding.get().catch(() => null)
|
||||
// Why: SSH users can hit this dialog from Add Project after
|
||||
// dismissing onboarding, bypassing the local addNonGitFolder path.
|
||||
const startup = buildDismissedOnboardingFolderAgentStartup(
|
||||
useAppStore.getState().settings,
|
||||
onboarding,
|
||||
hadProjectBeforeAdd
|
||||
)
|
||||
activateAndRevealWorktree(folderWorktree.id, startup ? { startup } : undefined)
|
||||
}
|
||||
} catch (err) {
|
||||
// This code path calls addRemote directly (not through the store),
|
||||
|
||||
@@ -47,10 +47,12 @@ export function buildOnboardingFolderAgentStartup(
|
||||
}
|
||||
|
||||
export function shouldSeedFolderAgentAfterDismissedOnboarding(
|
||||
onboarding: OnboardingState | null
|
||||
onboarding: OnboardingState | null,
|
||||
hasExistingProject: boolean
|
||||
): boolean {
|
||||
return (
|
||||
onboarding?.outcome === 'dismissed' &&
|
||||
!hasExistingProject &&
|
||||
!onboarding.checklist.addedRepo &&
|
||||
!onboarding.checklist.addedFolder
|
||||
)
|
||||
@@ -58,9 +60,10 @@ export function shouldSeedFolderAgentAfterDismissedOnboarding(
|
||||
|
||||
export function buildDismissedOnboardingFolderAgentStartup(
|
||||
settings: GlobalSettings | null,
|
||||
onboarding: OnboardingState | null
|
||||
onboarding: OnboardingState | null,
|
||||
hasExistingProject: boolean
|
||||
): OnboardingFolderAgentStartup | undefined {
|
||||
if (!shouldSeedFolderAgentAfterDismissedOnboarding(onboarding)) {
|
||||
if (!shouldSeedFolderAgentAfterDismissedOnboarding(onboarding, hasExistingProject)) {
|
||||
return undefined
|
||||
}
|
||||
return buildOnboardingFolderAgentStartup(settings)
|
||||
|
||||
@@ -2,12 +2,21 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'
|
||||
import { createTestStore, makeWorktree } from './store-test-helpers'
|
||||
import { workItemsCacheKey } from './github'
|
||||
import type { Repo } from '../../../../shared/types'
|
||||
import { getDefaultOnboardingState, getDefaultSettings } from '../../../../shared/constants'
|
||||
import {
|
||||
createCompatibleRuntimeStatusResponseIfNeeded,
|
||||
type RuntimeEnvironmentCallRequest
|
||||
} from '../../runtime/runtime-compatibility-test-fixture'
|
||||
import { clearRuntimeCompatibilityCacheForTests } from '../../runtime/runtime-rpc-client'
|
||||
|
||||
const worktreeActivation = vi.hoisted(() => ({
|
||||
activateAndRevealWorktree: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../../lib/worktree-activation', () => ({
|
||||
activateAndRevealWorktree: worktreeActivation.activateAndRevealWorktree
|
||||
}))
|
||||
|
||||
const localRepo: Repo = {
|
||||
id: 'local-repo',
|
||||
path: '/local',
|
||||
@@ -32,6 +41,7 @@ const reposUpdate = vi.fn()
|
||||
const reposReorder = vi.fn()
|
||||
const worktreesList = vi.fn()
|
||||
const onboardingGet = vi.fn()
|
||||
const onboardingUpdate = vi.fn()
|
||||
const ptyKill = vi.fn()
|
||||
const runtimeEnvironmentCall = vi.fn()
|
||||
const runtimeEnvironmentTransportCall = vi.fn()
|
||||
@@ -46,6 +56,8 @@ beforeEach(() => {
|
||||
reposReorder.mockReset()
|
||||
worktreesList.mockReset()
|
||||
onboardingGet.mockReset()
|
||||
onboardingUpdate.mockReset()
|
||||
worktreeActivation.activateAndRevealWorktree.mockReset()
|
||||
ptyKill.mockReset()
|
||||
runtimeEnvironmentCall.mockReset()
|
||||
runtimeEnvironmentTransportCall.mockReset()
|
||||
@@ -66,7 +78,8 @@ beforeEach(() => {
|
||||
list: worktreesList
|
||||
},
|
||||
onboarding: {
|
||||
get: onboardingGet
|
||||
get: onboardingGet,
|
||||
update: onboardingUpdate
|
||||
},
|
||||
pty: { kill: ptyKill },
|
||||
runtimeEnvironments: { call: runtimeEnvironmentTransportCall }
|
||||
@@ -178,6 +191,58 @@ describe('repo slice runtime routing', () => {
|
||||
expect(runtimeEnvironmentCall).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('only seeds the onboarding default agent for the first dismissed-onboarding folder', async () => {
|
||||
const firstFolderRepo = {
|
||||
...localRepo,
|
||||
id: 'folder-1',
|
||||
path: '/first',
|
||||
displayName: 'First'
|
||||
}
|
||||
const secondFolderRepo = {
|
||||
...localRepo,
|
||||
id: 'folder-2',
|
||||
path: '/second',
|
||||
displayName: 'Second'
|
||||
}
|
||||
reposAdd
|
||||
.mockResolvedValueOnce({ repo: firstFolderRepo })
|
||||
.mockResolvedValueOnce({ repo: secondFolderRepo })
|
||||
worktreesList.mockImplementation(({ repoId }: { repoId: string }) => [
|
||||
makeWorktree({ id: `${repoId}::/folder`, repoId })
|
||||
])
|
||||
onboardingGet.mockResolvedValue({ ...getDefaultOnboardingState(), outcome: 'dismissed' })
|
||||
const store = createTestStore()
|
||||
store.setState({
|
||||
settings: {
|
||||
...getDefaultSettings('/tmp/orca-workspaces'),
|
||||
defaultTuiAgent: 'codex'
|
||||
}
|
||||
})
|
||||
|
||||
await store.getState().addNonGitFolder('/first')
|
||||
await store.getState().addNonGitFolder('/second')
|
||||
|
||||
expect(worktreeActivation.activateAndRevealWorktree).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'folder-1::/folder',
|
||||
{
|
||||
startup: {
|
||||
command: 'codex',
|
||||
telemetry: {
|
||||
agent_kind: 'codex',
|
||||
launch_source: 'onboarding',
|
||||
request_kind: 'new'
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
expect(worktreeActivation.activateAndRevealWorktree).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'folder-2::/folder',
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('removes repos through the active remote runtime environment', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValue({
|
||||
id: 'rpc-3',
|
||||
|
||||
@@ -155,6 +155,7 @@ export const createRepoSlice: StateCreator<AppState, [], [], RepoSlice> = (set,
|
||||
|
||||
addNonGitFolder: async (path) => {
|
||||
try {
|
||||
const hadProjectBeforeAdd = get().repos.length > 0
|
||||
const repo = await get().addRepoPath(path, 'folder')
|
||||
if (!repo) {
|
||||
return null
|
||||
@@ -173,7 +174,11 @@ export const createRepoSlice: StateCreator<AppState, [], [], RepoSlice> = (set,
|
||||
// Why: a new user can dismiss the wizard, then immediately add their
|
||||
// first folder from Landing. That path skips onboarding's completeRepo
|
||||
// hook, so carry the selected default agent into the first terminal here.
|
||||
const startup = buildDismissedOnboardingFolderAgentStartup(get().settings, onboarding)
|
||||
const startup = buildDismissedOnboardingFolderAgentStartup(
|
||||
get().settings,
|
||||
onboarding,
|
||||
hadProjectBeforeAdd
|
||||
)
|
||||
activateAndRevealWorktree(folderWorktree.id, startup ? { startup } : undefined)
|
||||
}
|
||||
return repo
|
||||
|
||||
Reference in New Issue
Block a user