From 535c83a687fcdd679f9437c2178232c3ec24860e Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 7 Oct 2026 01:26:38 -0700 Subject: [PATCH] refactor(composer): delete the full-create path no caller reaches (#26052) * refactor(composer): delete the full-create path no caller reaches NewWorkspaceComposerModal is the only renderer of the composer card, and it overrides the card props' onCreate (the full-create submit) with its quick create and never reads useComposerState's submit. The full path (source and submit preparation, creation execution and its finalization, issue-command, startup and structured-launch helpers, the orchestration) was reachable only from its own tests. Delete it with them, and drop onCreate and submit from the composer contracts. * refactor(composer): delete code the full-create path left orphaned Removing the full-create path left code whose only readers were gone: - applyWorktreeMeta and the updateWorktreeMeta plumbing that fed it - createWorktree and setSidebarOpen on the composer target store - currentIssueCommand on the composer model - buildAgentPromptWithContext and getLinkedWorkItemPromptContext (only their own tests still called them) and those test cases - the "Selected agent is disabled" locale string in every catalog - the export on confirmRuntimeIssueCommandRead The 'full' create-gate mode goes too. Its only caller passed 'quick', so the default named a mode with no create action. That removes the option, the full gate, the issue-automation wait flag and the renderer issue command preload that ran only in 'full' mode. Quick create is unchanged. The quick path's name-retirement comment pointed at the deleted full submit path; it now carries that reasoning itself, including the mobile counterpart that must change with it. An e2e comment no longer names applyWorktreeMeta. --- .../components/NewWorkspaceComposerModal.tsx | 3 +- .../composer-state/assemble-composer-model.ts | 4 - .../composer-state/async-composer-state.ts | 1 - .../src/hooks/composer-state/async-model.ts | 1 - .../composer-state/composer-card-contract.ts | 1 - .../composer-state/composer-card-props.ts | 19 +- .../composer-state/composer-external-sync.ts | 5 - .../composer-navigation-actions.ts | 20 +- .../composer-state/composer-source-model.ts | 2 - .../composer-source-state-contract.ts | 5 +- .../composer-state/composer-source-state.ts | 3 +- .../composer-state/composer-store-actions.ts | 62 ---- .../composer-state/composer-submit-model.ts | 47 --- .../composer-submit-orchestration.ts | 100 ------ .../composer-submit-state-contract.ts | 4 - .../composer-state/composer-target-store.ts | 12 - .../composer-state/derived-composer-state.ts | 14 - .../src/hooks/composer-state/derived-model.ts | 2 - .../full-creation-execution.test.ts | 105 ------ .../composer-state/full-creation-execution.ts | 309 ------------------ .../full-creation-finalization.ts | 24 -- .../full-creation-issue-command.ts | 18 - .../composer-state/full-creation-startup.ts | 31 -- .../full-creation-structured-launch.ts | 21 -- .../full-submit-orchestration.test.ts | 69 ---- .../full-submit-orchestration.ts | 149 --------- .../composer-state/full-submit-preparation.ts | 271 --------------- .../full-submit-source-preparation.ts | 232 ------------- .../composer-state/host-runtime-effects.ts | 58 +--- .../quick-submit-source-preparation.ts | 6 +- .../composer-state/target-store-model.ts | 4 - .../useComposerState.integration.test.ts | 6 +- src/renderer/src/hooks/useComposerState.ts | 3 - src/renderer/src/i18n/locales/en.json | 1 - src/renderer/src/i18n/locales/es.json | 1 - src/renderer/src/i18n/locales/fr.json | 1 - src/renderer/src/i18n/locales/ja.json | 1 - src/renderer/src/i18n/locales/ko.json | 1 - src/renderer/src/i18n/locales/zh.json | 1 - .../src/lib/ensure-hooks-confirmed.ts | 2 +- .../src/lib/linked-work-item-context.test.ts | 61 ---- .../src/lib/linked-work-item-context.ts | 26 -- .../lib/new-workspace-create-gates.test.ts | 32 +- .../src/lib/new-workspace-create-gates.ts | 10 - src/renderer/src/lib/new-workspace.ts | 32 -- tests/e2e/worktree.spec.ts | 6 +- 46 files changed, 21 insertions(+), 1765 deletions(-) delete mode 100644 src/renderer/src/hooks/composer-state/full-creation-execution.test.ts delete mode 100644 src/renderer/src/hooks/composer-state/full-creation-execution.ts delete mode 100644 src/renderer/src/hooks/composer-state/full-creation-finalization.ts delete mode 100644 src/renderer/src/hooks/composer-state/full-creation-issue-command.ts delete mode 100644 src/renderer/src/hooks/composer-state/full-creation-startup.ts delete mode 100644 src/renderer/src/hooks/composer-state/full-creation-structured-launch.ts delete mode 100644 src/renderer/src/hooks/composer-state/full-submit-orchestration.test.ts delete mode 100644 src/renderer/src/hooks/composer-state/full-submit-orchestration.ts delete mode 100644 src/renderer/src/hooks/composer-state/full-submit-preparation.ts delete mode 100644 src/renderer/src/hooks/composer-state/full-submit-source-preparation.ts diff --git a/src/renderer/src/components/NewWorkspaceComposerModal.tsx b/src/renderer/src/components/NewWorkspaceComposerModal.tsx index 5d38cb0739c..71a33801d8d 100644 --- a/src/renderer/src/components/NewWorkspaceComposerModal.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerModal.tsx @@ -149,8 +149,7 @@ function QuickTabBody({ onCreated: onClose, isSubmissionCancelled, ...(modalData.telemetrySource ? { telemetrySource: modalData.telemetrySource } : {}), - enableIssueAutomation: modalData.enableIssueAutomation === true, - createGateMode: 'quick' + enableIssueAutomation: modalData.enableIssueAutomation === true }) // Why: the composer's built-in `onOpenAgentSettings` handler navigates to // the settings page and closes the modal. For the quick-create flow we want diff --git a/src/renderer/src/hooks/composer-state/assemble-composer-model.ts b/src/renderer/src/hooks/composer-state/assemble-composer-model.ts index d14476b5935..f585a0a251b 100644 --- a/src/renderer/src/hooks/composer-state/assemble-composer-model.ts +++ b/src/renderer/src/hooks/composer-state/assemble-composer-model.ts @@ -34,10 +34,6 @@ export function assembleComposerModel( ...source.issueSourceActions, ...source.composerNavigationActions, ...submit.folderSubmitOrchestration, - ...submit.fullSubmitSourcePreparation, - ...submit.fullSubmitPreparation, - ...submit.fullCreationExecution, - ...submit.fullSubmitOrchestration, ...submit.multipleCreateReset, ...submit.quickSubmitSourcePreparation, ...submit.quickSubmitPreparation, diff --git a/src/renderer/src/hooks/composer-state/async-composer-state.ts b/src/renderer/src/hooks/composer-state/async-composer-state.ts index f84baa51469..072550ea7ac 100644 --- a/src/renderer/src/hooks/composer-state/async-composer-state.ts +++ b/src/renderer/src/hooks/composer-state/async-composer-state.ts @@ -223,7 +223,6 @@ export function useComposerAsyncState(input: ComposerAsyncStateInput) { setCheckedHooksContextKey, loadedIssueCommand, setLoadedIssueCommand, - currentIssueCommand, issueCommandTemplate, hasLoadedIssueCommand, setupDecision, diff --git a/src/renderer/src/hooks/composer-state/async-model.ts b/src/renderer/src/hooks/composer-state/async-model.ts index 77c7664a536..4b6b710f933 100644 --- a/src/renderer/src/hooks/composer-state/async-model.ts +++ b/src/renderer/src/hooks/composer-state/async-model.ts @@ -17,7 +17,6 @@ export type ComposerAsyncModel = { setLoadedIssueCommand: React.Dispatch< React.SetStateAction<{ contextKey: string; result: IssueCommandReadResult } | null> > - currentIssueCommand: IssueCommandReadResult | null issueCommandTemplate: string hasLoadedIssueCommand: boolean setupDecision: 'skip' | 'run' | null diff --git a/src/renderer/src/hooks/composer-state/composer-card-contract.ts b/src/renderer/src/hooks/composer-state/composer-card-contract.ts index 5be3c98bf8b..64ef15be23f 100644 --- a/src/renderer/src/hooks/composer-state/composer-card-contract.ts +++ b/src/renderer/src/hooks/composer-state/composer-card-contract.ts @@ -96,7 +96,6 @@ export type ComposerCardActionProps = { onOpenAgentSettings: ComposerModel['handleOpenAgentSettings'] onToggleAdvanced: () => void createDisabled: boolean - onCreate: () => void onNoteChange: ComposerModel['setNote'] onBaseBranchChange: ComposerModel['handleBaseBranchChange'] onBaseBranchPrSelect: ComposerModel['handleBaseBranchPrSelect'] diff --git a/src/renderer/src/hooks/composer-state/composer-card-props.ts b/src/renderer/src/hooks/composer-state/composer-card-props.ts index 308dbbb65db..a5a2fb7bd66 100644 --- a/src/renderer/src/hooks/composer-state/composer-card-props.ts +++ b/src/renderer/src/hooks/composer-state/composer-card-props.ts @@ -1,8 +1,5 @@ import { getAttachmentLabel } from '@/lib/new-workspace' -import { - getFullComposerCreateDisabled, - getQuickComposerCreateDisabled -} from '@/lib/new-workspace-create-gates' +import { getQuickComposerCreateDisabled } from '@/lib/new-workspace-create-gates' import type { ComposerModel } from './composer-model' import type { ComposerCardActionProps, ComposerCardSourceProps } from './composer-card-contract' @@ -14,7 +11,6 @@ export function buildComposerCardProps(state: ComposerModel) { baseBranch, branchNameOverride, parentWorktreeId, - createGateMode, createError, createMultiple, creating, @@ -89,7 +85,6 @@ export function buildComposerCardProps(state: ComposerModel) { selectedRepoIsGit, selectedRepoProjectId, selectedRepoRequiresConnection, - shouldWaitForIssueAutomationCheck, sourceIntentBlocksCreate, sparseError, selectedRepoSshStatus, @@ -116,26 +111,19 @@ export function buildComposerCardProps(state: ComposerModel) { sparsePresets, sparseSelectedPresetId, startFromResetHint, - submit, tuiAgent } = state - const createGateInput = { + const repoCreateDisabled = getQuickComposerCreateDisabled({ repoId, workspaceSeedName, creating, - shouldWaitForSetupCheck, - shouldWaitForIssueAutomationCheck, sourceIntentBlocksCreate, requiresExplicitSetupChoice, hasSetupDecision: Boolean(setupDecision), selectedRepoRequiresConnection, sparseError - } - const repoCreateDisabled = - createGateMode === 'quick' - ? getQuickComposerCreateDisabled(createGateInput) - : getFullComposerCreateDisabled(createGateInput) + }) const createDisabled = isProjectGroupTarget ? folderCreateDisabled : repoCreateDisabled const cardProps: ComposerCardSourceProps & ComposerCardActionProps = { eligibleRepos: isProjectGroupTarget ? folderSourceRepos : eligibleRepos, @@ -216,7 +204,6 @@ export function buildComposerCardProps(state: ComposerModel) { createDisabled, projectError: isProjectGroupTarget ? pathStatusProjectError : projectError, creating, - onCreate: () => void submit(), baseBranch: isProjectGroupTarget ? undefined : baseBranch, onBaseBranchChange: isProjectGroupTarget ? () => {} : handleBaseBranchChange, onBaseBranchPrSelect: isProjectGroupTarget ? () => {} : handleBaseBranchPrSelect, diff --git a/src/renderer/src/hooks/composer-state/composer-external-sync.ts b/src/renderer/src/hooks/composer-state/composer-external-sync.ts index d3dba81665a..f7efc502f88 100644 --- a/src/renderer/src/hooks/composer-state/composer-external-sync.ts +++ b/src/renderer/src/hooks/composer-state/composer-external-sync.ts @@ -9,9 +9,7 @@ export function useComposerExternalSync(target: ComposerTargetState): ComposerEx const hostRuntimeEffects = useHostRuntimeEffects({ commitHookCheckIfCurrent: target.providerRuntimeSync.commitHookCheckIfCurrent, connectionId: target.workspaceIdentityState.connectionId, - createGateMode: target.composerTargetStore.createGateMode, disabledTuiAgents: target.workspaceIdentityState.disabledTuiAgents, - enableIssueAutomation: target.composerTargetStore.enableIssueAutomation, ensureDetectedAgents: target.workspaceIdentityState.ensureDetectedAgents, ensureRemoteDetectedAgents: target.workspaceIdentityState.ensureRemoteDetectedAgents, ensureRuntimeDetectedAgents: target.workspaceIdentityState.ensureRuntimeDetectedAgents, @@ -24,12 +22,9 @@ export function useComposerExternalSync(target: ComposerTargetState): ComposerEx repoIdRef: target.runtimeTargetSelection.repoIdRef, runtimeEnvironmentId: target.workspaceIdentityState.runtimeEnvironmentId, selectedRepoConnectionIdRef: target.asyncComposerState.selectedRepoConnectionIdRef, - selectedRepoExecutionHostId: target.runtimeTargetSelection.selectedRepoExecutionHostId, selectedRepoHookContextKey: target.runtimeTargetSelection.selectedRepoHookContextKey, selectedRepoIsGit: target.runtimeTargetSelection.selectedRepoIsGit, - selectedRepoSettingsRef: target.asyncComposerState.selectedRepoSettingsRef, selectedRepoSshStatus: target.runtimeTargetSelection.selectedRepoSshStatus, - setLoadedIssueCommand: target.asyncComposerState.setLoadedIssueCommand, setTuiAgent: target.workspaceIdentityState.setTuiAgent, settings: target.composerTargetStore.settings, tuiAgent: target.workspaceIdentityState.tuiAgent diff --git a/src/renderer/src/hooks/composer-state/composer-navigation-actions.ts b/src/renderer/src/hooks/composer-state/composer-navigation-actions.ts index c37c7487376..e66a62de458 100644 --- a/src/renderer/src/hooks/composer-state/composer-navigation-actions.ts +++ b/src/renderer/src/hooks/composer-state/composer-navigation-actions.ts @@ -12,12 +12,10 @@ type ComposerNavigationActionsInput = Pick< | 'setActiveRuntimeEnvironmentPreference' | 'smartNameJiraSourceContext' | 'sourceIntentBlocksCreate' - | 'updateWorktreeMeta' > import { useCallback } from 'react' import { getTaskSourceRuntimeSettings } from '../../../../shared/task-source-context' -import type { WorktreeMeta } from '../../../../shared/worktree/meta-types' export function useComposerNavigationActions(input: ComposerNavigationActionsInput) { const { @@ -30,8 +28,7 @@ export function useComposerNavigationActions(input: ComposerNavigationActionsInp selectedProjectGroup, setActiveRuntimeEnvironmentPreference, smartNameJiraSourceContext, - sourceIntentBlocksCreate, - updateWorktreeMeta + sourceIntentBlocksCreate } = input const handleOpenAgentSettings = useCallback((): void => { @@ -61,20 +58,6 @@ export function useComposerNavigationActions(input: ComposerNavigationActionsInp smartNameJiraSourceContext ]) - const applyWorktreeMeta = useCallback( - async (worktreeId: string, meta: Partial): Promise => { - if (Object.keys(meta).length === 0) { - return - } - try { - await updateWorktreeMeta(worktreeId, meta) - } catch { - console.error('Failed to update worktree meta after creation') - } - }, - [updateWorktreeMeta] - ) - const folderCreateDisabled = creating || sourceIntentBlocksCreate || @@ -85,7 +68,6 @@ export function useComposerNavigationActions(input: ComposerNavigationActionsInp return { handleOpenAgentSettings, handleOpenJiraSettings, - applyWorktreeMeta, folderCreateDisabled } } diff --git a/src/renderer/src/hooks/composer-state/composer-source-model.ts b/src/renderer/src/hooks/composer-state/composer-source-model.ts index f2f06fe06cc..bab47484826 100644 --- a/src/renderer/src/hooks/composer-state/composer-source-model.ts +++ b/src/renderer/src/hooks/composer-state/composer-source-model.ts @@ -1,6 +1,5 @@ import type { GitLabWorkItem } from '../../../../shared/gitlab-types' import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' -import type { WorktreeMeta } from '../../../../shared/worktree/meta-types' import type { GitPushTarget } from '../../../../shared/worktree/types' import type { JiraIssue } from '../../../../shared/jira-types' import type { TaskSourceContext } from '../../../../shared/task-source-context' @@ -17,7 +16,6 @@ export type ComposerSourceModel = { options?: { preserveBranchNameOverride?: boolean | undefined } ) => void applyLocalComposerDrop: (paths: string[], canApply?: () => boolean) => Promise - applyWorktreeMeta: (worktreeId: string, meta: Partial) => Promise canPrefetchSelectedRepoWorkItems: boolean folderCreateDisabled: boolean handleAddAttachment: () => Promise diff --git a/src/renderer/src/hooks/composer-state/composer-source-state-contract.ts b/src/renderer/src/hooks/composer-state/composer-source-state-contract.ts index 533d3cd959e..e4b95c140cf 100644 --- a/src/renderer/src/hooks/composer-state/composer-source-state-contract.ts +++ b/src/renderer/src/hooks/composer-state/composer-source-state-contract.ts @@ -46,9 +46,6 @@ export type ComposerSourceState = { > composerNavigationActions: Pick< ComposerModel, - | 'handleOpenAgentSettings' - | 'handleOpenJiraSettings' - | 'applyWorktreeMeta' - | 'folderCreateDisabled' + 'handleOpenAgentSettings' | 'handleOpenJiraSettings' | 'folderCreateDisabled' > } diff --git a/src/renderer/src/hooks/composer-state/composer-source-state.ts b/src/renderer/src/hooks/composer-state/composer-source-state.ts index b19e6ee75b8..4cda33be903 100644 --- a/src/renderer/src/hooks/composer-state/composer-source-state.ts +++ b/src/renderer/src/hooks/composer-state/composer-source-state.ts @@ -281,8 +281,7 @@ export function useComposerSourceState( setActiveRuntimeEnvironmentPreference: target.composerTargetStore.setActiveRuntimeEnvironmentPreference, smartNameJiraSourceContext: target.sourceContextState.smartNameJiraSourceContext, - sourceIntentBlocksCreate: target.workspaceIdentityState.sourceIntentBlocksCreate, - updateWorktreeMeta: target.composerTargetStore.updateWorktreeMeta + sourceIntentBlocksCreate: target.workspaceIdentityState.sourceIntentBlocksCreate }) return { sourceIdentityActions, diff --git a/src/renderer/src/hooks/composer-state/composer-store-actions.ts b/src/renderer/src/hooks/composer-state/composer-store-actions.ts index 3aa7d96f5d3..71edb7e419c 100644 --- a/src/renderer/src/hooks/composer-state/composer-store-actions.ts +++ b/src/renderer/src/hooks/composer-state/composer-store-actions.ts @@ -1,78 +1,17 @@ import type { UISlice } from '../../store/slices/ui' -import type { - CreateSparseCheckoutRequest, - CreateWorktreeArgs, - CreateWorktreeResult, - SetupDecision -} from '../../../../shared/worktree/create-types' -import type { WorktreeStartupLaunch } from '../../../../shared/worktree/launch-types' -import type { - WorkspaceLinkedItem, - GitPushTarget, - WorkspaceStatus -} from '../../../../shared/worktree/types' -import type { WorktreeMeta } from '../../../../shared/worktree/meta-types' import type { RepoUpdate } from '../../store/repos/repo-state' -import type { WorktreeMetaUpdateOptions } from '../../store/slices/worktree-helpers' import type { FolderWorkspace } from '../../../../shared/folder-workspace-types' import type { ExecutionHostId } from '../../../../shared/execution-host' import type { TaskSourceContext } from '../../../../shared/task-source-context' -import type { TuiAgent } from '../../../../shared/tui-agent' -import type { WorkspaceSource as WorkspaceCreateTelemetrySource } from '../../../../shared/workspace-source' export type ComposerStoreActions = { setNewWorkspaceDraft: (draft: NonNullable) => void clearNewWorkspaceDraft: () => void - createWorktree: ( - repoId: string, - name: string, - baseBranch?: string, - setupDecision?: SetupDecision, - sparseCheckout?: CreateSparseCheckoutRequest, - telemetrySource?: WorkspaceCreateTelemetrySource, - displayName?: string, - linkedIssue?: number, - linkedPR?: number, - pushTarget?: GitPushTarget, - createdWithAgent?: TuiAgent, - linkedLinearIssue?: string, - branchNameOverride?: string, - workspaceStatus?: WorkspaceStatus, - linkedGitLabMR?: number, - linkedGitLabIssue?: number, - startup?: WorktreeStartupLaunch, - pendingFirstAgentMessageRename?: boolean, - creationId?: string, - linkedLinearIssueWorkspaceId?: string | null, - linkedLinearIssueOrganizationUrlKey?: string | null, - linkedBitbucketPR?: number | null, - linkedAzureDevOpsPR?: number | null, - linkedGiteaPR?: number | null, - compareBaseRef?: string, - options?: { - automationProvenanceRequest?: CreateWorktreeArgs['automationProvenanceRequest'] - linkedWorkItem?: WorkspaceLinkedItem | null - linkedTaskSourceContext?: TaskSourceContext | null - startupDraft?: string - nameWasGenerated?: boolean - parentWorktreeId?: string - provisionedRoot?: { - runtimeId: string - executionHostId: ExecutionHostId - expectedPath: string - } - } - ) => Promise updateRepo: ( projectId: string, updates: RepoUpdate, options?: { hostId?: ExecutionHostId } ) => Promise - updateWorktreeMeta: ( - worktreeId: string, - updates: Partial, - options?: WorktreeMetaUpdateOptions - ) => Promise<{ ok: true } | { ok: false; error: string }> createFolderWorkspace: ( args: { projectGroupId: string @@ -86,7 +25,6 @@ export type ComposerStoreActions = { }, options?: { runtimeEnvironmentId?: string | null } ) => Promise - setSidebarOpen: (open: boolean) => void closeModal: () => void openSettingsPage: () => void openSettingsTarget: (target: NonNullable) => void diff --git a/src/renderer/src/hooks/composer-state/composer-submit-model.ts b/src/renderer/src/hooks/composer-state/composer-submit-model.ts index 7d75d60782d..351e5a23c07 100644 --- a/src/renderer/src/hooks/composer-state/composer-submit-model.ts +++ b/src/renderer/src/hooks/composer-state/composer-submit-model.ts @@ -3,8 +3,6 @@ import type { GitPushTarget } from '../../../../shared/worktree/types' import type { SetupDecision } from '../../../../shared/worktree/create-types' import type { Repo } from '../../../../shared/repo-types' import type { WorkspaceIntentName } from '../../../../shared/workspace-name' -import type { AgentStartupPlan } from '../../../../shared/tui-agent-startup' -import type { AgentStartedTelemetry } from '@/lib/worktree-startup-payload' import type { LinkedWorkItemSummary } from '@/lib/new-workspace' import type { WorktreeCreationRequest } from '@/lib/pending-worktree-creation' import type { PendingSmartGitHubSubmitResolution } from './source-selection-decisions' @@ -14,40 +12,6 @@ type SmartCreateNames = { displayName: string | undefined } -export type FullSubmitSource = { - submitLinkedWorkItem: LinkedWorkItemSummary | null - submitLinkedIssueNumber: number | null - submitLinkedPR: number | null - submitTitleName: WorkspaceIntentName | null - nameIsAutoManaged: boolean - smartGitHubCreateNames: SmartCreateNames - workspaceName: string - nameWasGenerated: boolean - submitBaseBranch: string | undefined - submitCompareBaseRef: string | undefined - submitPushTarget: GitPushTarget | undefined - submitBranchNameOverride: string | undefined - submitLinkedWorkItemProvider: LinkedWorkItemSummary['provider'] | null - submitStartupPrompt: string - submitShouldRunIssueAutomation: boolean -} - -export type PreparedFullSubmit = FullSubmitSource & { - effectiveSetupDecision: SetupDecision - issueCommandTrustDecision: 'run' | 'skip' - confirmedIssueCommandTemplate: string - linkedLinearIssue: string | undefined - linkedLinearIssueWorkspaceId: string | undefined - linkedLinearIssueOrganizationUrlKey: string | undefined - effectiveBranchNameOverride: string | undefined - createDisplayName: string | undefined - pendingFirstAgentMessageRename: boolean - startupPlan: AgentStartupPlan | null - shouldSeedInitialAgentStatus: boolean - composerTelemetry: AgentStartedTelemetry - backendStartup: WorktreeCreationRequest['startup'] -} - export type QuickSubmitSource = { submitLinkedWorkItem: LinkedWorkItemSummary | null agent: TuiAgent | null @@ -78,10 +42,6 @@ export type PreparedQuickSubmit = QuickSubmitSource & { } export type ComposerSubmitModel = { - executeFullCreation: ( - resolution: PendingSmartGitHubSubmitResolution, - repoId: string - ) => Promise executeQuickCreation: ( resolution: PendingSmartGitHubSubmitResolution, requestedAgent: TuiAgent | null, @@ -90,12 +50,6 @@ export type ComposerSubmitModel = { repoId: string, selectedRepo: Repo ) => Promise - prepareFullSubmit: ( - resolution: PendingSmartGitHubSubmitResolution - ) => Promise - prepareFullSubmitSource: ( - resolution: PendingSmartGitHubSubmitResolution - ) => FullSubmitSource | null prepareQuickSubmit: ( resolution: PendingSmartGitHubSubmitResolution, requestedAgent: TuiAgent | null, @@ -107,7 +61,6 @@ export type ComposerSubmitModel = { workspaceNameSeed: string ) => QuickSubmitSource | null resetForNextCreate: () => void - submit: () => Promise submitQuick: (agent: TuiAgent | null) => Promise submitFolderTarget: (requestedAgent: TuiAgent | null) => Promise } diff --git a/src/renderer/src/hooks/composer-state/composer-submit-orchestration.ts b/src/renderer/src/hooks/composer-state/composer-submit-orchestration.ts index 13cf2c225f2..d1f3d0cb9ed 100644 --- a/src/renderer/src/hooks/composer-state/composer-submit-orchestration.ts +++ b/src/renderer/src/hooks/composer-state/composer-submit-orchestration.ts @@ -3,10 +3,6 @@ import type { ComposerExternalSyncState } from './composer-external-sync-contrac import type { ComposerSourceState } from './composer-source-state-contract' import type { ComposerSubmitState } from './composer-submit-state-contract' import { useFolderSubmitOrchestration } from './folder-submit-orchestration' -import { useFullSubmitSourcePreparation } from './full-submit-source-preparation' -import { useFullSubmitPreparation } from './full-submit-preparation' -import { useFullCreationExecution } from './full-creation-execution' -import { useFullSubmitOrchestration } from './full-submit-orchestration' import { useMultipleCreateReset } from './multiple-create-reset' import { useQuickSubmitSourcePreparation } from './quick-submit-source-preparation' import { useQuickSubmitPreparation } from './quick-submit-preparation' @@ -45,98 +41,6 @@ export function useComposerSubmitOrchestration( taskSourceContext: target.sourceContextState.taskSourceContext, telemetrySource: target.composerTargetStore.telemetrySource }) - const fullSubmitSourcePreparation = useFullSubmitSourcePreparation({ - agentPrompt: target.sourceContextState.agentPrompt, - attachmentPaths: target.sourceContextState.attachmentPaths, - baseBranch: target.workspaceIdentityState.baseBranch, - branchNameOverride: target.workspaceIdentityState.branchNameOverride, - compareBaseRef: target.workspaceIdentityState.compareBaseRef, - decisions: target.composerTargetStore.decisions, - effectiveLinkedPR: target.derivedComposerState.effectiveLinkedPR, - enableIssueAutomation: target.composerTargetStore.enableIssueAutomation, - fallbackCreatureName: target.derivedComposerState.fallbackCreatureName, - hasLoadedIssueCommand: target.asyncComposerState.hasLoadedIssueCommand, - issueCommandTemplate: target.asyncComposerState.issueCommandTemplate, - lastAutoNameRef: target.asyncComposerState.lastAutoNameRef, - linkedGitLabMR: target.workspaceIdentityState.linkedGitLabMR, - linkedWorkItem: target.sourceContextState.linkedWorkItem, - name: target.sourceContextState.name, - parsedLinkedIssueNumber: target.derivedComposerState.parsedLinkedIssueNumber, - pushTarget: target.workspaceIdentityState.pushTarget, - workspaceSeedName: target.derivedComposerState.workspaceSeedName - }) - const fullSubmitPreparation = useFullSubmitPreparation({ - branchAutoNameRef: target.asyncComposerState.branchAutoNameRef, - branchNameOverridePreservesNameEdits: - target.workspaceIdentityState.branchNameOverridePreservesNameEdits, - currentIssueCommand: target.asyncComposerState.currentIssueCommand, - isSubmissionCancelled: target.composerTargetStore.isSubmissionCancelled, - issueCommandTemplate: target.asyncComposerState.issueCommandTemplate, - name: target.sourceContextState.name, - prepareFullSubmitSource: fullSubmitSourcePreparation.prepareFullSubmitSource, - repoId: target.initialTargetState.repoId, - resolvedSetupDecision: target.derivedComposerState.resolvedSetupDecision, - selectedRepo: target.runtimeTargetSelection.selectedRepo, - selectedRepoAgentLaunchPlatform: target.runtimeTargetSelection.selectedRepoAgentLaunchPlatform, - selectedRepoExecutionHostId: target.runtimeTargetSelection.selectedRepoExecutionHostId, - selectedRepoIsGit: target.runtimeTargetSelection.selectedRepoIsGit, - selectedRepoIsRemote: target.runtimeTargetSelection.selectedRepoIsRemote, - selectedRepoStartupShell: target.runtimeTargetSelection.selectedRepoStartupShell, - settings: target.composerTargetStore.settings, - smartNameMode: target.workspaceIdentityState.smartNameMode, - telemetrySource: target.composerTargetStore.telemetrySource, - tuiAgent: target.workspaceIdentityState.tuiAgent - }) - const fullCreationExecution = useFullCreationExecution({ - applyWorktreeMeta: source.composerNavigationActions.applyWorktreeMeta, - clearNewWorkspaceDraft: target.composerTargetStore.clearNewWorkspaceDraft, - createWorktree: target.composerTargetStore.createWorktree, - effectivePresetId: target.derivedComposerState.effectivePresetId, - isSubmissionCancelled: target.composerTargetStore.isSubmissionCancelled, - linkedGitLabIssue: target.workspaceIdentityState.linkedGitLabIssue, - linkedGitLabMR: target.workspaceIdentityState.linkedGitLabMR, - normalizedSparseDirectories: target.derivedComposerState.normalizedSparseDirectories, - note: target.sourceContextState.note, - onCreated: target.composerTargetStore.onCreated, - parentWorktreeId: target.workspaceIdentityState.parentWorktreeId, - persistDraft: target.composerTargetStore.persistDraft, - persistSetupAgentStartupPolicy: target.providerRuntimeSync.persistSetupAgentStartupPolicy, - prepareFullSubmit: fullSubmitPreparation.prepareFullSubmit, - resolvedInitialWorkspaceStatus: target.initialTargetState.resolvedInitialWorkspaceStatus, - selectedRepoExecutionHostId: target.runtimeTargetSelection.selectedRepoExecutionHostId, - selectedRepoIsGit: target.runtimeTargetSelection.selectedRepoIsGit, - setSidebarOpen: target.composerTargetStore.setSidebarOpen, - sparseEnabled: target.asyncComposerState.sparseEnabled, - taskSourceContext: target.sourceContextState.taskSourceContext, - telemetrySource: target.composerTargetStore.telemetrySource, - tuiAgent: target.workspaceIdentityState.tuiAgent - }) - const fullSubmitOrchestration = useFullSubmitOrchestration({ - disabledTuiAgents: target.workspaceIdentityState.disabledTuiAgents, - executeFullCreation: fullCreationExecution.executeFullCreation, - fallbackDefaultAgent: target.workspaceIdentityState.fallbackDefaultAgent, - isProjectGroupTarget: target.runtimeTargetSelection.isProjectGroupTarget, - isSubmissionCancelled: target.composerTargetStore.isSubmissionCancelled, - repoId: target.initialTargetState.repoId, - requiresExplicitSetupChoice: target.derivedComposerState.requiresExplicitSetupChoice, - resolvePendingSmartGitHubSubmit: - external.githubSubmitResolution.resolvePendingSmartGitHubSubmit, - selectedRepo: target.runtimeTargetSelection.selectedRepo, - selectedRepoRequiresConnection: target.runtimeTargetSelection.selectedRepoRequiresConnection, - setCreateError: target.asyncComposerState.setCreateError, - setCreating: target.asyncComposerState.setCreating, - setTuiAgent: target.workspaceIdentityState.setTuiAgent, - setupDecision: target.asyncComposerState.setupDecision, - shouldWaitForIssueAutomationCheck: - target.derivedComposerState.shouldWaitForIssueAutomationCheck, - shouldWaitForSetupCheck: target.derivedComposerState.shouldWaitForSetupCheck, - showProjectRequiredError: source.branchStartPointActions.showProjectRequiredError, - sourceIntentBlocksCreate: target.workspaceIdentityState.sourceIntentBlocksCreate, - sparseError: target.derivedComposerState.sparseError, - submitFolderTarget: folderSubmitOrchestration.submitFolderTarget, - tuiAgent: target.workspaceIdentityState.tuiAgent, - workspaceSeedName: target.derivedComposerState.workspaceSeedName - }) const multipleCreateReset = useMultipleCreateReset({ handleClearSmartNameSelection: source.issueSourceActions.handleClearSmartNameSelection, lastAutoNameRef: target.asyncComposerState.lastAutoNameRef, @@ -244,10 +148,6 @@ export function useComposerSubmitOrchestration( }) return { folderSubmitOrchestration, - fullSubmitSourcePreparation, - fullSubmitPreparation, - fullCreationExecution, - fullSubmitOrchestration, multipleCreateReset, quickSubmitSourcePreparation, quickSubmitPreparation, diff --git a/src/renderer/src/hooks/composer-state/composer-submit-state-contract.ts b/src/renderer/src/hooks/composer-state/composer-submit-state-contract.ts index d70c1e6a306..64db95eb8fa 100644 --- a/src/renderer/src/hooks/composer-state/composer-submit-state-contract.ts +++ b/src/renderer/src/hooks/composer-state/composer-submit-state-contract.ts @@ -1,10 +1,6 @@ import type { ComposerModel } from './composer-model' export type ComposerSubmitState = { folderSubmitOrchestration: Pick - fullSubmitSourcePreparation: Pick - fullSubmitPreparation: Pick - fullCreationExecution: Pick - fullSubmitOrchestration: Pick multipleCreateReset: Pick quickSubmitSourcePreparation: Pick quickSubmitPreparation: Pick diff --git a/src/renderer/src/hooks/composer-state/composer-target-store.ts b/src/renderer/src/hooks/composer-state/composer-target-store.ts index 9f85db4f380..c3495835d72 100644 --- a/src/renderer/src/hooks/composer-state/composer-target-store.ts +++ b/src/renderer/src/hooks/composer-state/composer-target-store.ts @@ -32,7 +32,6 @@ export type ComposerStateInput = { onRepoIdOverrideChange?: (value: string) => void telemetrySource?: WorkspaceCreateTelemetrySource enableIssueAutomation?: boolean - createGateMode?: 'full' | 'quick' } const NEVER_CANCEL_COMPOSER_SUBMIT = (): boolean => false @@ -55,7 +54,6 @@ export function useComposerTargetStore(options: ComposerStateInput, decisions: C onRepoIdOverrideChange, telemetrySource, enableIssueAutomation = true, - createGateMode = 'full', initialProjectGroupId } = options @@ -63,11 +61,8 @@ export function useComposerTargetStore(options: ComposerStateInput, decisions: C useShallow((s) => ({ setNewWorkspaceDraft: s.setNewWorkspaceDraft, clearNewWorkspaceDraft: s.clearNewWorkspaceDraft, - createWorktree: s.createWorktree, updateRepo: s.updateRepo, - updateWorktreeMeta: s.updateWorktreeMeta, createFolderWorkspace: s.createFolderWorkspace, - setSidebarOpen: s.setSidebarOpen, closeModal: s.closeModal, openSettingsPage: s.openSettingsPage, openSettingsTarget: s.openSettingsTarget, @@ -81,11 +76,8 @@ export function useComposerTargetStore(options: ComposerStateInput, decisions: C const { setNewWorkspaceDraft, clearNewWorkspaceDraft, - createWorktree, updateRepo, - updateWorktreeMeta, createFolderWorkspace, - setSidebarOpen, closeModal, openSettingsPage, openSettingsTarget, @@ -178,17 +170,13 @@ export function useComposerTargetStore(options: ComposerStateInput, decisions: C onRepoIdOverrideChange, telemetrySource, enableIssueAutomation, - createGateMode, initialProjectGroupId, decisions, actions, setNewWorkspaceDraft, clearNewWorkspaceDraft, - createWorktree, updateRepo, - updateWorktreeMeta, createFolderWorkspace, - setSidebarOpen, closeModal, openSettingsPage, openSettingsTarget, diff --git a/src/renderer/src/hooks/composer-state/derived-composer-state.ts b/src/renderer/src/hooks/composer-state/derived-composer-state.ts index 5595644da02..22a4078271f 100644 --- a/src/renderer/src/hooks/composer-state/derived-composer-state.ts +++ b/src/renderer/src/hooks/composer-state/derived-composer-state.ts @@ -128,18 +128,6 @@ export function useDerivedComposerState(input: DerivedComposerStateInput) { const linkedWorkItemProvider = linkedWorkItem ? getLinkedWorkItemProvider(linkedWorkItem) : null - // Why: sentinel-based Jira/Linear items must bypass repository issue templates. - const willApplyIssueCommandAsPrompt = - enableIssueAutomation && - !agentPrompt.trim() && - Boolean(linkedWorkItem) && - canUseIssueCommandForLinkedItemProvider(linkedWorkItemProvider) - - const shouldWaitForIssueAutomationCheck = - enableIssueAutomation && - (parsedLinkedIssueNumber !== null || willApplyIssueCommandAsPrompt) && - !hasLoadedIssueCommand - const requiresExplicitSetupChoice = Boolean(setupConfig) && setupPolicy === 'ask' const resolvedSetupDecision = @@ -258,8 +246,6 @@ export function useDerivedComposerState(input: DerivedComposerStateInput) { setupConfig, setupPolicy, linkedWorkItemProvider, - willApplyIssueCommandAsPrompt, - shouldWaitForIssueAutomationCheck, requiresExplicitSetupChoice, resolvedSetupDecision, isSetupCheckPending, diff --git a/src/renderer/src/hooks/composer-state/derived-model.ts b/src/renderer/src/hooks/composer-state/derived-model.ts index 906bc23ddbd..3fe36b53a83 100644 --- a/src/renderer/src/hooks/composer-state/derived-model.ts +++ b/src/renderer/src/hooks/composer-state/derived-model.ts @@ -17,8 +17,6 @@ export type ComposerDerivedModel = { setupConfig: SetupConfig | null setupPolicy: SetupRunPolicy linkedWorkItemProvider: 'github' | 'gitlab' | 'linear' | 'jira' | null - willApplyIssueCommandAsPrompt: boolean - shouldWaitForIssueAutomationCheck: boolean requiresExplicitSetupChoice: boolean resolvedSetupDecision: 'skip' | 'run' | null isSetupCheckPending: boolean diff --git a/src/renderer/src/hooks/composer-state/full-creation-execution.test.ts b/src/renderer/src/hooks/composer-state/full-creation-execution.test.ts deleted file mode 100644 index 1195a5fa82c..00000000000 --- a/src/renderer/src/hooks/composer-state/full-creation-execution.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -// @vitest-environment happy-dom - -import { act, renderHook } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' -import { - useFullCreationExecution, - type FullCreationExecutionInput -} from './full-creation-execution' -import type { PreparedFullSubmit } from './composer-submit-model' - -function deferred() { - let resolve!: (value: T) => void - const promise = new Promise((next) => { - resolve = next - }) - return { promise, resolve } -} - -describe('useFullCreationExecution cancellation', () => { - it('does not create after dismissal while the late startup-policy preflight is pending', async () => { - const startupPolicy = deferred() - let cancelled = false - const createWorktree = vi.fn() - const prepared = { - submitLinkedWorkItem: null, - submitLinkedIssueNumber: null, - submitLinkedPR: null, - submitTitleName: null, - nameIsAutoManaged: false, - smartGitHubCreateNames: { - workspaceName: 'workspace', - displayName: undefined - }, - workspaceName: 'workspace', - nameWasGenerated: false, - submitBaseBranch: 'main', - submitCompareBaseRef: undefined, - submitPushTarget: undefined, - submitBranchNameOverride: undefined, - submitLinkedWorkItemProvider: null, - submitStartupPrompt: '', - submitShouldRunIssueAutomation: false, - effectiveSetupDecision: 'skip', - issueCommandTrustDecision: 'skip', - confirmedIssueCommandTemplate: '', - linkedLinearIssue: undefined, - linkedLinearIssueWorkspaceId: undefined, - linkedLinearIssueOrganizationUrlKey: undefined, - effectiveBranchNameOverride: undefined, - createDisplayName: undefined, - pendingFirstAgentMessageRename: false, - startupPlan: null, - shouldSeedInitialAgentStatus: false, - composerTelemetry: { - agent_kind: 'claude-code', - launch_source: 'new_workspace_composer', - request_kind: 'new' - }, - backendStartup: undefined - } satisfies PreparedFullSubmit - const persistSetupAgentStartupPolicy = vi.fn(() => startupPolicy.promise) - const state = { - applyWorktreeMeta: vi - .fn() - .mockResolvedValue(), - clearNewWorkspaceDraft: vi.fn(), - createWorktree, - effectivePresetId: null, - isSubmissionCancelled: () => cancelled, - linkedGitLabIssue: null, - linkedGitLabMR: null, - normalizedSparseDirectories: [], - note: '', - onCreated: vi.fn>(), - parentWorktreeId: null, - persistDraft: false, - persistSetupAgentStartupPolicy, - prepareFullSubmit: vi - .fn() - .mockResolvedValue(prepared), - resolvedInitialWorkspaceStatus: undefined, - selectedRepoExecutionHostId: 'local', - selectedRepoIsGit: true, - setSidebarOpen: vi.fn(), - sparseEnabled: false, - taskSourceContext: null, - telemetrySource: undefined, - tuiAgent: 'claude' - } satisfies FullCreationExecutionInput - const hook = renderHook(() => useFullCreationExecution(state)) - - let creation!: Promise - act(() => { - creation = hook.result.current.executeFullCreation({ kind: 'none' }, 'repo-1') - }) - await act(() => Promise.resolve()) - expect(persistSetupAgentStartupPolicy).toHaveBeenCalledTimes(1) - - cancelled = true - startupPolicy.resolve(true) - await act(async () => creation) - - expect(createWorktree).not.toHaveBeenCalled() - }) -}) diff --git a/src/renderer/src/hooks/composer-state/full-creation-execution.ts b/src/renderer/src/hooks/composer-state/full-creation-execution.ts deleted file mode 100644 index 53e632dbb0a..00000000000 --- a/src/renderer/src/hooks/composer-state/full-creation-execution.ts +++ /dev/null @@ -1,309 +0,0 @@ -import type { ComposerModel } from './composer-model' - -export type FullCreationExecutionInput = Pick< - ComposerModel, - | 'applyWorktreeMeta' - | 'clearNewWorkspaceDraft' - | 'createWorktree' - | 'effectivePresetId' - | 'isSubmissionCancelled' - | 'linkedGitLabIssue' - | 'linkedGitLabMR' - | 'normalizedSparseDirectories' - | 'note' - | 'onCreated' - | 'parentWorktreeId' - | 'persistDraft' - | 'persistSetupAgentStartupPolicy' - | 'prepareFullSubmit' - | 'resolvedInitialWorkspaceStatus' - | 'selectedRepoExecutionHostId' - | 'selectedRepoIsGit' - | 'setSidebarOpen' - | 'sparseEnabled' - | 'taskSourceContext' - | 'telemetrySource' - | 'tuiAgent' -> - -import { useCallback } from 'react' -import type { PendingSmartGitHubSubmitResolution } from './source-selection-decisions' -import { translate } from '@/i18n/i18n' -import { settleComposerSubmit } from '@/lib/composer-submit-cancellation' -import { toFolderWorkspaceLinkedTask } from '@/components/sidebar/folder-workspace-composer-helpers' -import { ensureAgentStartupInTerminal } from '@/lib/new-workspace' -import { createBrowserUuid } from '@/lib/browser-uuid' -import { activateAndRevealWorktree } from '@/lib/worktree-activation' -import { seedNativeChatAppliedSessionOptions } from '@/components/native-chat/native-chat-session-option-cache' -import { queueWorkspaceActivationTerminalFocus } from '@/lib/workspace-activation-terminal-focus' -import { useAppStore } from '@/store' -import { planAgentSessionLaunch } from '@/lib/agent-session-launch-plan' -import { newAgentLaunchRequestId } from '@/lib/agent-launch-request-id' -import { beginFullCreationStructuredLaunch } from './full-creation-structured-launch' -import { finalizeFullCreation } from './full-creation-finalization' -import { buildFullCreationIssueCommand } from './full-creation-issue-command' -import { buildFullCreationStartup } from './full-creation-startup' - -export function useFullCreationExecution(input: FullCreationExecutionInput) { - const { - applyWorktreeMeta, - clearNewWorkspaceDraft, - createWorktree, - effectivePresetId, - isSubmissionCancelled, - linkedGitLabIssue, - linkedGitLabMR, - normalizedSparseDirectories, - note, - onCreated, - parentWorktreeId, - persistDraft, - persistSetupAgentStartupPolicy, - prepareFullSubmit, - resolvedInitialWorkspaceStatus, - selectedRepoExecutionHostId, - selectedRepoIsGit, - setSidebarOpen, - sparseEnabled, - taskSourceContext, - telemetrySource, - tuiAgent - } = input - - const executeFullCreation = useCallback( - async ( - smartGitHubResolution: PendingSmartGitHubSubmitResolution, - repoId: string - ): Promise => { - const prepared = await prepareFullSubmit(smartGitHubResolution) - - if (!prepared) { - return - } - - const { - submitLinkedWorkItem, - submitLinkedIssueNumber, - submitLinkedPR, - workspaceName, - nameWasGenerated, - nameIsAutoManaged, - submitBaseBranch, - submitCompareBaseRef, - submitPushTarget, - submitStartupPrompt, - submitShouldRunIssueAutomation, - effectiveSetupDecision, - issueCommandTrustDecision, - confirmedIssueCommandTemplate, - linkedLinearIssue, - linkedLinearIssueWorkspaceId, - linkedLinearIssueOrganizationUrlKey, - effectiveBranchNameOverride, - createDisplayName, - pendingFirstAgentMessageRename, - startupPlan, - shouldSeedInitialAgentStatus, - composerTelemetry, - backendStartup - } = prepared - - const startupPolicySettlement = await settleComposerSubmit( - persistSetupAgentStartupPolicy(), - isSubmissionCancelled - ) - - if (startupPolicySettlement.status === 'cancelled') { - return - } - - if (!startupPolicySettlement.value) { - throw new Error( - translate( - 'auto.hooks.useComposerState.setupAgentStartupPolicySaveFailed', - 'Failed to save setup startup behavior.' - ) - ) - } - - if (isSubmissionCancelled()) { - return - } - - const launchPlan = planAgentSessionLaunch(useAppStore.getState(), { - requestId: newAgentLaunchRequestId(), - agent: tuiAgent, - workspace: { - kind: selectedRepoIsGit ? 'git-worktree' : 'folder', - repoId, - executionHostId: selectedRepoExecutionHostId ?? undefined - }, - prompt: startupPlan?.draftPrompt ?? submitStartupPrompt, - promptDelivery: startupPlan?.draftPrompt ? 'draft' : 'auto-submit', - initialSessionOptions: startupPlan?.sessionOptions - }) - const structuredLaunch = launchPlan.route === 'structured-native-chat' - const effectiveBackendStartup = structuredLaunch ? undefined : backendStartup - - const result = await createWorktree( - repoId, - workspaceName, - selectedRepoIsGit ? submitBaseBranch : undefined, - effectiveSetupDecision, - selectedRepoIsGit && sparseEnabled - ? { - directories: normalizedSparseDirectories, - ...(effectivePresetId ? { presetId: effectivePresetId } : {}) - } - : undefined, - telemetrySource, - createDisplayName, - submitLinkedIssueNumber ?? undefined, - submitLinkedPR ?? undefined, - submitPushTarget, - tuiAgent, - linkedLinearIssue, - effectiveBranchNameOverride, - resolvedInitialWorkspaceStatus, - smartGitHubResolution.kind === 'none' ? (linkedGitLabMR ?? undefined) : undefined, - smartGitHubResolution.kind === 'none' ? (linkedGitLabIssue ?? undefined) : undefined, - effectiveBackendStartup, - pendingFirstAgentMessageRename, - undefined, - linkedLinearIssueWorkspaceId, - linkedLinearIssueOrganizationUrlKey, - undefined, - undefined, - undefined, - submitCompareBaseRef, - { - linkedWorkItem: toFolderWorkspaceLinkedTask(submitLinkedWorkItem), - linkedTaskSourceContext: taskSourceContext, - nameWasGenerated, - ...(createDisplayName - ? { displayNameKind: nameIsAutoManaged ? ('generated' as const) : ('user' as const) } - : {}), - ...(!structuredLaunch && !effectiveBackendStartup && startupPlan?.draftPrompt - ? { startupDraft: startupPlan.draftPrompt } - : {}), - ...(parentWorktreeId ? { parentWorktreeId } : {}) - } - ) - - const worktree = result.worktree - const issueCommand = buildFullCreationIssueCommand({ - shouldRun: submitShouldRunIssueAutomation && issueCommandTrustDecision === 'run', - template: confirmedIssueCommandTemplate, - issueNumber: submitLinkedIssueNumber, - artifactUrl: submitLinkedWorkItem?.url - }) - - const backendSpawnedStartup = result.startupTerminal?.spawned === true - - if (startupPlan && !backendSpawnedStartup && !startupPlan.launchToken) { - // Why: delayed delivery must target the exact pane from this queued startup, so both halves share one renderer-session token. - startupPlan.launchToken = createBrowserUuid() - } - - const startup = buildFullCreationStartup({ - startupPlan, - backendSpawnedStartup, - agent: tuiAgent, - shouldSeedInitialAgentStatus, - prompt: submitStartupPrompt, - telemetry: composerTelemetry - }) - - const activationHolder: { value: ReturnType } = { - value: false - } - const revealWorkspace = (): boolean => { - activationHolder.value = activateAndRevealWorktree(worktree.id, { - sidebarRevealBehavior: 'auto', - agent: tuiAgent, - setup: result.setup, - defaultTabs: result.defaultTabs, - issueCommand, - ...(backendSpawnedStartup ? { backendStartupTerminalSpawned: true } : {}), - ...(!structuredLaunch && startup ? { startup } : {}), - ...(structuredLaunch ? { providesInitialSurface: true } : {}) - }) - return activationHolder.value !== false - } - if (structuredLaunch) { - try { - beginFullCreationStructuredLaunch({ - plan: launchPlan, - worktreeId: worktree.id, - beforeOpen: revealWorkspace - }) - } catch (error) { - // Why: a failed reveal must not turn a structured route into a legacy terminal; the - // completed workspace remains usable and the launch surface can be retried there. - console.error('full creation: structured chat surface failed', worktree.id, error) - } - } - if (!structuredLaunch) { - revealWorkspace() - } - const structuredLaunchAccepted = structuredLaunch - const activation = activationHolder.value - - const trimmedNote = note.trim() - await applyWorktreeMeta(worktree.id, trimmedNote ? { comment: trimmedNote } : {}) - - if (!structuredLaunchAccepted && startupPlan) { - const optionScopeKey = - (activation !== false ? activation.primaryTabId : null) ?? result.startupTerminal?.tabId - if (optionScopeKey) { - seedNativeChatAppliedSessionOptions(optionScopeKey, tuiAgent, startupPlan.sessionOptions) - } - } - - if (!structuredLaunchAccepted && startupPlan && !backendSpawnedStartup) { - void ensureAgentStartupInTerminal({ - worktreeId: worktree.id, - primaryTabId: activation === false ? null : activation.primaryTabId, - startup: startupPlan - }) - } - - finalizeFullCreation({ - setSidebarOpen, - persistDraft, - clearNewWorkspaceDraft, - onCreated, - structuredLaunchAccepted, - worktreeId: worktree.id, - activation, - queueWorkspaceActivationTerminalFocus - }) - }, - [ - applyWorktreeMeta, - clearNewWorkspaceDraft, - createWorktree, - effectivePresetId, - isSubmissionCancelled, - linkedGitLabIssue, - linkedGitLabMR, - normalizedSparseDirectories, - note, - onCreated, - parentWorktreeId, - persistDraft, - persistSetupAgentStartupPolicy, - prepareFullSubmit, - resolvedInitialWorkspaceStatus, - selectedRepoExecutionHostId, - selectedRepoIsGit, - setSidebarOpen, - sparseEnabled, - taskSourceContext, - telemetrySource, - tuiAgent - ] - ) - - return { executeFullCreation } -} diff --git a/src/renderer/src/hooks/composer-state/full-creation-finalization.ts b/src/renderer/src/hooks/composer-state/full-creation-finalization.ts deleted file mode 100644 index 283a7b88aa8..00000000000 --- a/src/renderer/src/hooks/composer-state/full-creation-finalization.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { ActivateAndRevealResult } from '@/lib/worktree-activation' - -export function finalizeFullCreation(args: { - setSidebarOpen: (open: boolean) => void - persistDraft: boolean - clearNewWorkspaceDraft: () => void - onCreated?: () => void - structuredLaunchAccepted: boolean - worktreeId: string - activation: ActivateAndRevealResult | false - queueWorkspaceActivationTerminalFocus: ( - worktreeId: string, - activation: ActivateAndRevealResult | false - ) => void -}): void { - args.setSidebarOpen(true) - if (args.persistDraft) { - args.clearNewWorkspaceDraft() - } - args.onCreated?.() - if (!args.structuredLaunchAccepted) { - args.queueWorkspaceActivationTerminalFocus(args.worktreeId, args.activation) - } -} diff --git a/src/renderer/src/hooks/composer-state/full-creation-issue-command.ts b/src/renderer/src/hooks/composer-state/full-creation-issue-command.ts deleted file mode 100644 index 2494b99aa10..00000000000 --- a/src/renderer/src/hooks/composer-state/full-creation-issue-command.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { renderIssueCommandTemplate } from '@/lib/new-workspace' - -export function buildFullCreationIssueCommand(args: { - shouldRun: boolean - template: string - issueNumber: number | null | undefined - artifactUrl: string | null | undefined -}): { command: string } | undefined { - if (!args.shouldRun) { - return undefined - } - return { - command: renderIssueCommandTemplate(args.template, { - issueNumber: args.issueNumber ?? null, - artifactUrl: args.artifactUrl ?? null - }) - } -} diff --git a/src/renderer/src/hooks/composer-state/full-creation-startup.ts b/src/renderer/src/hooks/composer-state/full-creation-startup.ts deleted file mode 100644 index 43c21661a76..00000000000 --- a/src/renderer/src/hooks/composer-state/full-creation-startup.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { TuiAgent } from '../../../../shared/tui-agent' -import type { AgentStartupPlan } from '@/lib/tui-agent-startup' -import type { WorktreeStartupPayload } from '@/lib/worktree-startup-payload' - -export function buildFullCreationStartup(args: { - startupPlan: AgentStartupPlan | null - backendSpawnedStartup: boolean - agent: TuiAgent - shouldSeedInitialAgentStatus: boolean - prompt: string - telemetry: WorktreeStartupPayload['telemetry'] -}): WorktreeStartupPayload | undefined { - if (!args.startupPlan || args.backendSpawnedStartup) { - return undefined - } - return { - command: args.startupPlan.launchCommand, - ...(args.startupPlan.env ? { env: args.startupPlan.env } : {}), - launchConfig: args.startupPlan.launchConfig, - ...(args.startupPlan.launchToken ? { launchToken: args.startupPlan.launchToken } : {}), - launchAgent: args.agent, - ...(args.startupPlan.draftPrompt ? { draftPrompt: args.startupPlan.draftPrompt } : {}), - ...(args.startupPlan.startupCommandDelivery - ? { startupCommandDelivery: args.startupPlan.startupCommandDelivery } - : {}), - ...(args.shouldSeedInitialAgentStatus - ? { initialAgentStatus: { agent: args.agent, prompt: args.prompt.trim() } } - : {}), - telemetry: args.telemetry - } -} diff --git a/src/renderer/src/hooks/composer-state/full-creation-structured-launch.ts b/src/renderer/src/hooks/composer-state/full-creation-structured-launch.ts deleted file mode 100644 index 8cb0c5aff84..00000000000 --- a/src/renderer/src/hooks/composer-state/full-creation-structured-launch.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { AgentSessionLaunchPlan } from '@/lib/agent-session-launch-plan' -import { - beginStructuredAgentSessionProvisionalLaunch, - type StructuredAgentSessionProvisionalLaunch -} from '@/lib/structured-agent-session-provisional-tab' - -/** Full-create dialog: the structured launch plus what this flow did before structured chat - * existed. Returns null when no visible surface can be owned. */ -export function beginFullCreationStructuredLaunch(args: { - /** Planned before the worktree existed; `worktreeId` names the one that was created. */ - plan: AgentSessionLaunchPlan - worktreeId: string - beforeOpen: (sessionId?: string) => boolean | void -}): StructuredAgentSessionProvisionalLaunch | null { - return beginStructuredAgentSessionProvisionalLaunch({ - plan: args.plan, - hooks: {}, - target: { worktreeId: args.worktreeId }, - beforeOpen: args.beforeOpen - }) -} diff --git a/src/renderer/src/hooks/composer-state/full-submit-orchestration.test.ts b/src/renderer/src/hooks/composer-state/full-submit-orchestration.test.ts deleted file mode 100644 index a5020d0d5f6..00000000000 --- a/src/renderer/src/hooks/composer-state/full-submit-orchestration.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -// @vitest-environment happy-dom - -import { act, renderHook } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' -import { - useFullSubmitOrchestration, - type FullSubmitOrchestrationInput -} from './full-submit-orchestration' - -function deferred() { - let resolve!: (value: T) => void - const promise = new Promise((next) => { - resolve = next - }) - return { promise, resolve } -} - -describe('useFullSubmitOrchestration cancellation', () => { - it('settles creating but never crosses the irreversible creation boundary after dismissal', async () => { - const resolution = deferred<{ kind: 'none' }>() - let cancelled = false - const executeFullCreation = vi.fn() - const setCreating = vi.fn() - const state = { - disabledTuiAgents: [], - executeFullCreation, - fallbackDefaultAgent: 'claude', - isProjectGroupTarget: false, - isSubmissionCancelled: () => cancelled, - repoId: 'repo-1', - requiresExplicitSetupChoice: false, - resolvePendingSmartGitHubSubmit: () => resolution.promise, - selectedRepo: { - id: 'repo-1', - path: '/repos/repo-1', - displayName: 'Repo 1', - badgeColor: '#000000', - addedAt: 0 - }, - selectedRepoRequiresConnection: false, - setCreateError: vi.fn(), - setCreating, - setTuiAgent: vi.fn(), - setupDecision: null, - shouldWaitForIssueAutomationCheck: false, - shouldWaitForSetupCheck: false, - showProjectRequiredError: vi.fn(), - sourceIntentBlocksCreate: false, - sparseError: null, - submitFolderTarget: vi.fn(), - tuiAgent: 'claude', - workspaceSeedName: 'workspace' - } satisfies FullSubmitOrchestrationInput - const hook = renderHook(() => useFullSubmitOrchestration(state)) - - let submission!: Promise - act(() => { - submission = hook.result.current.submit() - }) - expect(setCreating).toHaveBeenCalledWith(true) - - cancelled = true - resolution.resolve({ kind: 'none' }) - await act(async () => submission) - - expect(executeFullCreation).not.toHaveBeenCalled() - expect(setCreating).toHaveBeenLastCalledWith(false) - }) -}) diff --git a/src/renderer/src/hooks/composer-state/full-submit-orchestration.ts b/src/renderer/src/hooks/composer-state/full-submit-orchestration.ts deleted file mode 100644 index 94b80b9056d..00000000000 --- a/src/renderer/src/hooks/composer-state/full-submit-orchestration.ts +++ /dev/null @@ -1,149 +0,0 @@ -import type { ComposerModel } from './composer-model' - -export type FullSubmitOrchestrationInput = Pick< - ComposerModel, - | 'disabledTuiAgents' - | 'executeFullCreation' - | 'fallbackDefaultAgent' - | 'isProjectGroupTarget' - | 'isSubmissionCancelled' - | 'repoId' - | 'requiresExplicitSetupChoice' - | 'resolvePendingSmartGitHubSubmit' - | 'selectedRepo' - | 'selectedRepoRequiresConnection' - | 'setCreateError' - | 'setCreating' - | 'setTuiAgent' - | 'setupDecision' - | 'shouldWaitForIssueAutomationCheck' - | 'shouldWaitForSetupCheck' - | 'showProjectRequiredError' - | 'sourceIntentBlocksCreate' - | 'sparseError' - | 'submitFolderTarget' - | 'tuiAgent' - | 'workspaceSeedName' -> - -import { useCallback } from 'react' -import { isTuiAgentEnabled } from '../../../../shared/tui-agent-selection' -import { toast } from 'sonner' -import { translate } from '@/i18n/i18n' -import { settleComposerSubmit } from '@/lib/composer-submit-cancellation' -import { - formatWorkspaceCreateError, - getWorkspaceCreateErrorToastMessage -} from '@/lib/workspace-create-error-format' - -export function useFullSubmitOrchestration(input: FullSubmitOrchestrationInput) { - const { - disabledTuiAgents, - executeFullCreation, - fallbackDefaultAgent, - isProjectGroupTarget, - isSubmissionCancelled, - repoId, - requiresExplicitSetupChoice, - resolvePendingSmartGitHubSubmit, - selectedRepo, - selectedRepoRequiresConnection, - setCreateError, - setCreating, - setTuiAgent, - setupDecision, - shouldWaitForIssueAutomationCheck, - shouldWaitForSetupCheck, - showProjectRequiredError, - sourceIntentBlocksCreate, - sparseError, - submitFolderTarget, - tuiAgent, - workspaceSeedName - } = input - - const submit = useCallback(async (): Promise => { - if (isProjectGroupTarget) { - await submitFolderTarget(tuiAgent) - return - } - - if (!repoId || !selectedRepo) { - showProjectRequiredError() - return - } - - if ( - !workspaceSeedName || - selectedRepoRequiresConnection || - shouldWaitForSetupCheck || - shouldWaitForIssueAutomationCheck || - sourceIntentBlocksCreate || - (requiresExplicitSetupChoice && !setupDecision) || - sparseError !== null - ) { - return - } - - if (!isTuiAgentEnabled(tuiAgent, disabledTuiAgents)) { - setTuiAgent(fallbackDefaultAgent) - toast.error( - translate( - 'auto.hooks.useComposerState.7eb3f44ff7', - 'Selected agent is disabled. Choose an enabled agent before creating.' - ) - ) - return - } - - setCreateError(null) - - setCreating(true) - try { - const smartGitHubSettlement = await settleComposerSubmit( - resolvePendingSmartGitHubSubmit(), - isSubmissionCancelled - ) - if (smartGitHubSettlement.status === 'cancelled') { - return - } - await executeFullCreation(smartGitHubSettlement.value, repoId) - } catch (error) { - if (isSubmissionCancelled()) { - return - } - const formattedError = formatWorkspaceCreateError(error) - setCreateError(formattedError) - toast.error(getWorkspaceCreateErrorToastMessage(formattedError)) - } finally { - setCreating(false) - } - }, [ - disabledTuiAgents, - executeFullCreation, - fallbackDefaultAgent, - isProjectGroupTarget, - isSubmissionCancelled, - repoId, - requiresExplicitSetupChoice, - resolvePendingSmartGitHubSubmit, - selectedRepo, - selectedRepoRequiresConnection, - setCreateError, - setCreating, - setTuiAgent, - setupDecision, - shouldWaitForIssueAutomationCheck, - shouldWaitForSetupCheck, - showProjectRequiredError, - sourceIntentBlocksCreate, - sparseError, - submitFolderTarget, - tuiAgent, - workspaceSeedName - ]) - - return { - submit - } -} diff --git a/src/renderer/src/hooks/composer-state/full-submit-preparation.ts b/src/renderer/src/hooks/composer-state/full-submit-preparation.ts deleted file mode 100644 index a1120fccf4e..00000000000 --- a/src/renderer/src/hooks/composer-state/full-submit-preparation.ts +++ /dev/null @@ -1,271 +0,0 @@ -import type { ComposerModel } from './composer-model' - -type FullSubmitPreparationInput = Pick< - ComposerModel, - | 'branchAutoNameRef' - | 'branchNameOverridePreservesNameEdits' - | 'currentIssueCommand' - | 'isSubmissionCancelled' - | 'issueCommandTemplate' - | 'name' - | 'prepareFullSubmitSource' - | 'repoId' - | 'resolvedSetupDecision' - | 'selectedRepo' - | 'selectedRepoAgentLaunchPlatform' - | 'selectedRepoExecutionHostId' - | 'selectedRepoIsGit' - | 'selectedRepoIsRemote' - | 'selectedRepoStartupShell' - | 'settings' - | 'smartNameMode' - | 'telemetrySource' - | 'tuiAgent' -> - -import { useCallback } from 'react' -import { settleComposerSubmit } from '@/lib/composer-submit-cancellation' -import { ensureHooksConfirmed, confirmRuntimeIssueCommandRead } from '@/lib/ensure-hooks-confirmed' -import { useAppStore } from '@/store' -import type { SetupDecision } from '../../../../shared/worktree/create-types' -import { resolveComposerBranchNameOverrideForCreate } from '../composer-branch-selection' -import { buildAgentStartupPlan } from '@/lib/tui-agent-startup' -import { - resolveTuiAgentLaunchArgs, - resolveTuiAgentLaunchEnv -} from '../../../../shared/tui-agent-launch-defaults' -import { resolveInitialNativeChatSessionOptions } from '@/components/native-chat/native-chat-launch-session-options' -import { isNativeChatTranscriptLocalReadable } from '@/lib/native-chat-transcript-readability' -import type { AgentStartedTelemetry } from '@/lib/worktree-startup-payload' -import { tuiAgentToAgentKind } from '@/lib/telemetry' -import type { PendingSmartGitHubSubmitResolution } from './source-selection-decisions' - -export function useFullSubmitPreparation(input: FullSubmitPreparationInput) { - const { - branchAutoNameRef, - branchNameOverridePreservesNameEdits, - currentIssueCommand, - isSubmissionCancelled, - issueCommandTemplate, - name, - prepareFullSubmitSource, - repoId, - resolvedSetupDecision, - selectedRepo, - selectedRepoAgentLaunchPlatform, - selectedRepoExecutionHostId, - selectedRepoIsGit, - selectedRepoIsRemote, - selectedRepoStartupShell, - settings, - smartNameMode, - telemetrySource, - tuiAgent - } = input - - const prepareFullSubmit = useCallback( - async (smartGitHubResolution: PendingSmartGitHubSubmitResolution) => { - const source = prepareFullSubmitSource(smartGitHubResolution) - if (!source) { - return null - } - const { - submitLinkedWorkItem, - submitTitleName, - nameIsAutoManaged, - smartGitHubCreateNames, - workspaceName, - submitBranchNameOverride, - submitLinkedWorkItemProvider, - submitStartupPrompt, - submitShouldRunIssueAutomation - } = source - - const setupTrustSettlement = await settleComposerSubmit( - selectedRepoIsGit - ? ensureHooksConfirmed( - useAppStore.getState(), - repoId, - 'setup', - selectedRepoExecutionHostId ?? undefined, - undefined, - isSubmissionCancelled - ) - : Promise.resolve<'skip'>('skip'), - isSubmissionCancelled - ) - - if (setupTrustSettlement.status === 'cancelled') { - return null - } - - const setupTrustDecision = setupTrustSettlement.value - - const effectiveSetupDecision: SetupDecision = - setupTrustDecision === 'skip' - ? 'skip' - : ((resolvedSetupDecision ?? 'inherit') as SetupDecision) - - let issueCommandTrustDecision: 'run' | 'skip' = 'run' - - let confirmedIssueCommandTemplate = issueCommandTemplate - - if ( - selectedRepoIsGit && - submitShouldRunIssueAutomation && - currentIssueCommand && - selectedRepoExecutionHostId - ) { - if (setupTrustDecision === 'skip') { - issueCommandTrustDecision = 'skip' - } else { - const issueCommandSettlement = await settleComposerSubmit( - confirmRuntimeIssueCommandRead( - useAppStore.getState(), - repoId, - selectedRepoExecutionHostId, - currentIssueCommand, - isSubmissionCancelled - ), - isSubmissionCancelled - ) - if (issueCommandSettlement.status === 'cancelled') { - return null - } - const confirmed = issueCommandSettlement.value - issueCommandTrustDecision = confirmed.trustDecision - confirmedIssueCommandTemplate = confirmed.template - } - } - - const linkedLinearIssue = - submitLinkedWorkItem && submitLinkedWorkItemProvider === 'linear' - ? submitLinkedWorkItem.linearIdentifier - : undefined - - const linkedLinearIssueWorkspaceId = - submitLinkedWorkItem && submitLinkedWorkItemProvider === 'linear' - ? submitLinkedWorkItem.linearWorkspaceId - : undefined - - const linkedLinearIssueOrganizationUrlKey = - submitLinkedWorkItem && submitLinkedWorkItemProvider === 'linear' - ? submitLinkedWorkItem.linearOrganizationUrlKey - : undefined - - const effectiveBranchNameOverride = resolveComposerBranchNameOverrideForCreate({ - branchNameOverride: submitBranchNameOverride, - branchAutoName: branchAutoNameRef.current, - workspaceName, - preserveWorkspaceNameEdits: - smartGitHubResolution.kind === 'pr-start-point' || branchNameOverridePreservesNameEdits, - createBranchFromWorkspaceName: - smartGitHubResolution.kind === 'none' && smartNameMode === 'branches' - }) - - const createDisplayName = !nameIsAutoManaged - ? workspaceName - : smartGitHubResolution.kind === 'none' - ? submitTitleName?.displayName - : smartGitHubCreateNames.displayName - - // Why: the first-work hook only renames blank, auto-generated git workspaces that launch an agent; persist that pending state for the card. - const pendingFirstAgentMessageRename = - selectedRepoIsGit && - settings?.autoRenameBranchFromWork === true && - !name.trim() && - Boolean(tuiAgent) && - !effectiveBranchNameOverride && - !createDisplayName - - const startupPlan = buildAgentStartupPlan({ - agent: tuiAgent, - prompt: submitStartupPrompt, - cmdOverrides: settings?.agentCmdOverrides ?? {}, - agentArgs: resolveTuiAgentLaunchArgs(tuiAgent, settings?.agentDefaultArgs), - agentEnv: resolveTuiAgentLaunchEnv(tuiAgent, settings?.agentDefaultEnv), - sessionOptions: resolveInitialNativeChatSessionOptions( - { - experimentalNativeChat: settings?.experimentalNativeChat, - openAgentTabsInChatByDefault: settings?.openAgentTabsInChatByDefault, - nativeChatSessionOptions: settings?.nativeChatSessionOptions - }, - { - agent: tuiAgent, - nativeChatTranscriptIsLocalReadable: isNativeChatTranscriptLocalReadable( - selectedRepo?.connectionId - ) - } - ), - platform: selectedRepoAgentLaunchPlatform, - shell: selectedRepoStartupShell, - isRemote: selectedRepoIsRemote - }) - - const shouldSeedInitialAgentStatus = - tuiAgent === 'command-code' && submitStartupPrompt.trim().length > 0 - - // Why: backend startup is safe only for self-contained launch commands; agents needing post-ready paste stay on the renderer path. - const composerTelemetry: AgentStartedTelemetry = { - agent_kind: tuiAgentToAgentKind(tuiAgent), - launch_source: telemetrySource === 'onboarding' ? 'onboarding' : 'new_workspace_composer', - request_kind: 'new' - } - - const backendStartup = - startupPlan && !startupPlan.draftPrompt && !startupPlan.followupPrompt - ? { - command: startupPlan.launchCommand, - ...(startupPlan.env ? { env: startupPlan.env } : {}), - launchConfig: startupPlan.launchConfig, - launchAgent: tuiAgent, - ...(startupPlan.startupCommandDelivery - ? { startupCommandDelivery: startupPlan.startupCommandDelivery } - : {}), - telemetry: composerTelemetry - } - : undefined - - return Object.assign(source, { - effectiveSetupDecision, - issueCommandTrustDecision, - confirmedIssueCommandTemplate, - linkedLinearIssue, - linkedLinearIssueWorkspaceId, - linkedLinearIssueOrganizationUrlKey, - effectiveBranchNameOverride, - createDisplayName, - pendingFirstAgentMessageRename, - startupPlan, - shouldSeedInitialAgentStatus, - composerTelemetry, - backendStartup - }) - }, - [ - branchNameOverridePreservesNameEdits, - currentIssueCommand, - isSubmissionCancelled, - issueCommandTemplate, - name, - prepareFullSubmitSource, - repoId, - resolvedSetupDecision, - selectedRepo, - selectedRepoAgentLaunchPlatform, - selectedRepoExecutionHostId, - selectedRepoIsGit, - selectedRepoIsRemote, - selectedRepoStartupShell, - settings, - smartNameMode, - telemetrySource, - tuiAgent, - branchAutoNameRef - ] - ) - - return { - prepareFullSubmit - } -} diff --git a/src/renderer/src/hooks/composer-state/full-submit-source-preparation.ts b/src/renderer/src/hooks/composer-state/full-submit-source-preparation.ts deleted file mode 100644 index cbb79993a0f..00000000000 --- a/src/renderer/src/hooks/composer-state/full-submit-source-preparation.ts +++ /dev/null @@ -1,232 +0,0 @@ -import type { ComposerModel } from './composer-model' - -type FullSubmitSourcePreparationInput = Pick< - ComposerModel, - | 'agentPrompt' - | 'attachmentPaths' - | 'baseBranch' - | 'branchNameOverride' - | 'compareBaseRef' - | 'effectiveLinkedPR' - | 'decisions' - | 'enableIssueAutomation' - | 'fallbackCreatureName' - | 'hasLoadedIssueCommand' - | 'issueCommandTemplate' - | 'lastAutoNameRef' - | 'linkedGitLabMR' - | 'linkedWorkItem' - | 'name' - | 'parsedLinkedIssueNumber' - | 'pushTarget' - | 'workspaceSeedName' -> - -import { useCallback } from 'react' -import { - getLinkedWorkItemWorkspaceName, - getLinkedWorkItemProvider, - canUseIssueCommandForLinkedItemProvider, - renderIssueCommandTemplate, - DEFAULT_ISSUE_COMMAND_TEMPLATE, - buildAgentPromptWithContext -} from '@/lib/new-workspace' -import { getLinkedWorkItemPromptContext } from '@/lib/linked-work-item-context' -import type { PendingSmartGitHubSubmitResolution } from './source-selection-decisions' - -export function useFullSubmitSourcePreparation(input: FullSubmitSourcePreparationInput) { - const { - agentPrompt, - attachmentPaths, - baseBranch, - branchNameOverride, - compareBaseRef, - decisions, - effectiveLinkedPR, - enableIssueAutomation, - fallbackCreatureName, - hasLoadedIssueCommand, - issueCommandTemplate, - lastAutoNameRef, - linkedGitLabMR, - linkedWorkItem, - name, - parsedLinkedIssueNumber, - pushTarget, - workspaceSeedName - } = input - const { isExplicitWorkspaceNameInput, resolveSmartGitHubCreateNames } = decisions - - const prepareFullSubmitSource = useCallback( - (smartGitHubResolution: PendingSmartGitHubSubmitResolution) => { - const submitLinkedWorkItem = - smartGitHubResolution.kind === 'none' - ? linkedWorkItem - : smartGitHubResolution.linkedWorkItem - - const submitLinkedIssueNumber = - smartGitHubResolution.kind === 'none' - ? parsedLinkedIssueNumber - : smartGitHubResolution.linkedIssueNumber - - const submitLinkedPR = - smartGitHubResolution.kind === 'none' ? effectiveLinkedPR : smartGitHubResolution.linkedPR - - const submitTitleName = submitLinkedWorkItem - ? getLinkedWorkItemWorkspaceName(submitLinkedWorkItem) - : null - - const nameIsAutoManaged = !isExplicitWorkspaceNameInput({ - name, - lastAutoName: lastAutoNameRef.current - }) - - const smartGitHubCreateNames = - smartGitHubResolution.kind === 'none' - ? { workspaceName: workspaceSeedName, displayName: undefined } - : resolveSmartGitHubCreateNames({ - resolutionKind: smartGitHubResolution.kind, - smartWorkspaceName: smartGitHubResolution.workspaceName, - smartDisplayName: smartGitHubResolution.displayName, - fallbackWorkspaceName: workspaceSeedName, - nameIsAutoManaged - }) - - const workspaceName = - smartGitHubResolution.kind === 'none' - ? nameIsAutoManaged && submitTitleName - ? submitTitleName.seedName - : workspaceSeedName - : smartGitHubCreateNames.workspaceName - - if (!workspaceName) { - return null - } - - // Why: only a name Orca generated may be retired — the creature pool contains ordinary words - // ("orca", "runner", "molly") a user can type deliberately and expect to reuse. - // The identity check is what a linked PR/issue seed makes necessary here; mobile's blank-create - // path (NewWorktreeModal, `nameWasGenerated: !trimmedName`) has no other seed, so it can't - // share this expression. Same rule, two submit paths — change both together. - const nameWasGenerated = !name.trim() && workspaceName === fallbackCreatureName - - const submitBaseBranch = - smartGitHubResolution.kind === 'pr-start-point' - ? smartGitHubResolution.baseBranch - : smartGitHubResolution.kind === 'metadata-only' && - (effectiveLinkedPR !== null || linkedGitLabMR !== null) - ? undefined - : baseBranch - - const submitCompareBaseRef = - smartGitHubResolution.kind === 'pr-start-point' - ? smartGitHubResolution.compareBaseRef - : smartGitHubResolution.kind === 'none' - ? compareBaseRef - : undefined - - const submitPushTarget = - smartGitHubResolution.kind === 'pr-start-point' - ? smartGitHubResolution.pushTarget - : smartGitHubResolution.kind === 'none' - ? pushTarget - : undefined - - const submitBranchNameOverride = - smartGitHubResolution.kind === 'pr-start-point' - ? smartGitHubResolution.branchNameOverride - : smartGitHubResolution.kind === 'none' - ? branchNameOverride - : undefined - - const submitLinkedWorkItemProvider = submitLinkedWorkItem - ? getLinkedWorkItemProvider(submitLinkedWorkItem) - : null - - const submitShouldApplyLinkedOnlyTemplate = - enableIssueAutomation && - !agentPrompt.trim() && - Boolean(submitLinkedWorkItem) && - hasLoadedIssueCommand && - canUseIssueCommandForLinkedItemProvider(submitLinkedWorkItemProvider) - - const submitLinkedOnlyTemplatePrompt = - submitShouldApplyLinkedOnlyTemplate && submitLinkedWorkItem - ? renderIssueCommandTemplate( - issueCommandTemplate.trim() || DEFAULT_ISSUE_COMMAND_TEMPLATE, - { - issueNumber: - submitLinkedWorkItem.type === 'issue' ? submitLinkedWorkItem.number : null, - artifactUrl: submitLinkedWorkItem.url - } - ) - : '' - - const linkedPromptContext = getLinkedWorkItemPromptContext(submitLinkedWorkItem) - - const submitStartupPrompt = submitShouldApplyLinkedOnlyTemplate - ? buildAgentPromptWithContext( - submitLinkedOnlyTemplatePrompt, - attachmentPaths, - [], - linkedPromptContext.linkedContextBlocks - ) - : buildAgentPromptWithContext( - agentPrompt, - attachmentPaths, - linkedPromptContext.linkedUrls, - linkedPromptContext.linkedContextBlocks - ) - - const submitShouldRunIssueAutomation = - enableIssueAutomation && - canUseIssueCommandForLinkedItemProvider(submitLinkedWorkItemProvider) && - submitLinkedIssueNumber !== null && - issueCommandTemplate.length > 0 && - !submitShouldApplyLinkedOnlyTemplate - - return { - submitLinkedWorkItem, - submitLinkedIssueNumber, - submitLinkedPR, - submitTitleName, - nameIsAutoManaged, - smartGitHubCreateNames, - workspaceName, - nameWasGenerated, - submitBaseBranch, - submitCompareBaseRef, - submitPushTarget, - submitBranchNameOverride, - submitLinkedWorkItemProvider, - submitStartupPrompt, - submitShouldRunIssueAutomation - } - }, - [ - agentPrompt, - attachmentPaths, - baseBranch, - branchNameOverride, - effectiveLinkedPR, - enableIssueAutomation, - fallbackCreatureName, - hasLoadedIssueCommand, - issueCommandTemplate, - linkedGitLabMR, - linkedWorkItem, - isExplicitWorkspaceNameInput, - name, - parsedLinkedIssueNumber, - pushTarget, - resolveSmartGitHubCreateNames, - workspaceSeedName, - compareBaseRef, - lastAutoNameRef - ] - ) - - return { - prepareFullSubmitSource - } -} diff --git a/src/renderer/src/hooks/composer-state/host-runtime-effects.ts b/src/renderer/src/hooks/composer-state/host-runtime-effects.ts index e6fba662ea8..24a5402433b 100644 --- a/src/renderer/src/hooks/composer-state/host-runtime-effects.ts +++ b/src/renderer/src/hooks/composer-state/host-runtime-effects.ts @@ -4,9 +4,7 @@ type HostRuntimeEffectsInput = Pick< ComposerModel, | 'commitHookCheckIfCurrent' | 'connectionId' - | 'createGateMode' | 'disabledTuiAgents' - | 'enableIssueAutomation' | 'ensureDetectedAgents' | 'ensureRemoteDetectedAgents' | 'ensureRuntimeDetectedAgents' @@ -19,12 +17,9 @@ type HostRuntimeEffectsInput = Pick< | 'repoIdRef' | 'runtimeEnvironmentId' | 'selectedRepoConnectionIdRef' - | 'selectedRepoExecutionHostId' | 'selectedRepoHookContextKey' | 'selectedRepoIsGit' - | 'selectedRepoSettingsRef' | 'selectedRepoSshStatus' - | 'setLoadedIssueCommand' | 'setTuiAgent' | 'settings' | 'tuiAgent' @@ -33,7 +28,6 @@ type HostRuntimeEffectsInput = Pick< import { useEffect, useCallback } from 'react' import { filterEnabledTuiAgents, isTuiAgentEnabled } from '../../../../shared/tui-agent-selection' import { getAgentCatalog } from '@/lib/agent-catalog' -import { readRuntimeIssueCommand } from '@/runtime/runtime-hooks-client' import { useAppStore } from '@/store' import { isSshConnectInProgress } from '@/lib/new-workspace-ssh-gate' import { toast } from 'sonner' @@ -43,9 +37,7 @@ export function useHostRuntimeEffects(input: HostRuntimeEffectsInput) { const { commitHookCheckIfCurrent, connectionId, - createGateMode, disabledTuiAgents, - enableIssueAutomation, ensureDetectedAgents, ensureRemoteDetectedAgents, ensureRuntimeDetectedAgents, @@ -58,12 +50,9 @@ export function useHostRuntimeEffects(input: HostRuntimeEffectsInput) { repoIdRef, runtimeEnvironmentId, selectedRepoConnectionIdRef, - selectedRepoExecutionHostId, selectedRepoHookContextKey, selectedRepoIsGit, - selectedRepoSettingsRef, selectedRepoSshStatus, - setLoadedIssueCommand, setTuiAgent, settings, tuiAgent @@ -109,7 +98,7 @@ export function useHostRuntimeEffects(input: HostRuntimeEffectsInput) { setTuiAgent ]) - // Per-repo: load yaml hooks + issue command template. + // Per-repo: load yaml hooks. useEffect(() => { if (!repoId || !selectedRepoIsGit || !selectedRepoHookContextKey) { return @@ -129,59 +118,16 @@ export function useHostRuntimeEffects(input: HostRuntimeEffectsInput) { } }) - if (!enableIssueAutomation) { - return () => { - cancelled = true - } - } - - if (createGateMode === 'quick') { - return () => { - cancelled = true - } - } - - void readRuntimeIssueCommand( - selectedRepoSettingsRef.current, - repoId, - selectedRepoExecutionHostId ?? undefined - ) - .then((result) => { - if (!cancelled) { - setLoadedIssueCommand({ contextKey: selectedRepoHookContextKey, result }) - } - }) - .catch(() => { - if (!cancelled) { - setLoadedIssueCommand({ - contextKey: selectedRepoHookContextKey, - result: { - status: 'error', - localContent: null, - sharedContent: null, - effectiveContent: null, - localFilePath: '', - source: 'none' - } - }) - } - }) - return () => { cancelled = true } }, [ commitHookCheckIfCurrent, - createGateMode, - enableIssueAutomation, loadHookCheckForRepo, repoId, - selectedRepoExecutionHostId, selectedRepoHookContextKey, selectedRepoIsGit, - runtimeEnvironmentId, - selectedRepoSettingsRef, - setLoadedIssueCommand + runtimeEnvironmentId ]) const onConnectSelectedRepo = useCallback(async (): Promise => { diff --git a/src/renderer/src/hooks/composer-state/quick-submit-source-preparation.ts b/src/renderer/src/hooks/composer-state/quick-submit-source-preparation.ts index e4102933a68..4cf534953c3 100644 --- a/src/renderer/src/hooks/composer-state/quick-submit-source-preparation.ts +++ b/src/renderer/src/hooks/composer-state/quick-submit-source-preparation.ts @@ -96,7 +96,11 @@ export function useQuickSubmitSourcePreparation(input: QuickSubmitSourcePreparat return null } - // Why: only a name Orca generated may be retired — see the full-composer submit path. + // Why: only a name Orca generated may be retired — the creature pool contains ordinary words + // ("orca", "runner", "molly") a user can type deliberately and expect to reuse. + // The identity check is what a linked PR/issue seed makes necessary here; mobile's blank-create + // path (use-new-workspace-create-submit.ts, `nameWasGenerated: !trimmedName`) has no other seed, + // so it can't share this expression. Same rule, two submit paths — change both together. const nameWasGenerated = !name.trim() && workspaceName === fallbackCreatureName const smartSubmitBaseBranch = diff --git a/src/renderer/src/hooks/composer-state/target-store-model.ts b/src/renderer/src/hooks/composer-state/target-store-model.ts index 5bbf643c39f..431e33c1ca1 100644 --- a/src/renderer/src/hooks/composer-state/target-store-model.ts +++ b/src/renderer/src/hooks/composer-state/target-store-model.ts @@ -39,17 +39,13 @@ export type ComposerTargetStoreModel = { onRepoIdOverrideChange: ((value: string) => void) | undefined telemetrySource: WorkspaceCreateTelemetrySource | undefined enableIssueAutomation: boolean - createGateMode: 'full' | 'quick' initialProjectGroupId: string | undefined decisions: ComposerDecisions actions: ComposerStoreActions setNewWorkspaceDraft: ComposerStoreActions['setNewWorkspaceDraft'] clearNewWorkspaceDraft: ComposerStoreActions['clearNewWorkspaceDraft'] - createWorktree: ComposerStoreActions['createWorktree'] updateRepo: ComposerStoreActions['updateRepo'] - updateWorktreeMeta: ComposerStoreActions['updateWorktreeMeta'] createFolderWorkspace: ComposerStoreActions['createFolderWorkspace'] - setSidebarOpen: ComposerStoreActions['setSidebarOpen'] closeModal: ComposerStoreActions['closeModal'] openSettingsPage: ComposerStoreActions['openSettingsPage'] openSettingsTarget: ComposerStoreActions['openSettingsTarget'] diff --git a/src/renderer/src/hooks/useComposerState.integration.test.ts b/src/renderer/src/hooks/useComposerState.integration.test.ts index c82e1a118c6..b80d6d3bf18 100644 --- a/src/renderer/src/hooks/useComposerState.integration.test.ts +++ b/src/renderer/src/hooks/useComposerState.integration.test.ts @@ -48,11 +48,9 @@ describe('useComposerState integrated lifecycle', () => { return unsubscribe }) - const first = renderHook(() => - useComposerState({ initialName: 'first', persistDraft: false, createGateMode: 'quick' }) - ) + const first = renderHook(() => useComposerState({ initialName: 'first', persistDraft: false })) const second = renderHook(() => - useComposerState({ initialName: 'second', persistDraft: false, createGateMode: 'quick' }) + useComposerState({ initialName: 'second', persistDraft: false }) ) expect(first.result.current.cardProps.name).toBe('first') diff --git a/src/renderer/src/hooks/useComposerState.ts b/src/renderer/src/hooks/useComposerState.ts index 4e54c94efce..31925c86b98 100644 --- a/src/renderer/src/hooks/useComposerState.ts +++ b/src/renderer/src/hooks/useComposerState.ts @@ -47,7 +47,6 @@ export type UseComposerStateOptions = { onRepoIdOverrideChange?: (value: string) => void telemetrySource?: WorkspaceCreateTelemetrySource enableIssueAutomation?: boolean - createGateMode?: 'full' | 'quick' } export type ComposerCardProps = ComposerCardSourceProps & ComposerCardActionProps @@ -58,7 +57,6 @@ export type UseComposerStateResult = { onComposerNodeChange: (node: HTMLDivElement | null) => void promptTextareaRef: RefObject nameInputRef: RefObject - submit: () => Promise submitQuick: (agent: TuiAgent | null) => Promise createDisabled: boolean selectAddedProjectRepo: (repoId: string) => void @@ -221,7 +219,6 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS onComposerNodeChange: model.handleComposerNodeChange, promptTextareaRef: model.promptTextareaRef, nameInputRef: model.nameInputRef, - submit: model.submit, submitQuick: model.submitQuick, createDisabled, selectAddedProjectRepo: model.selectAddedProjectRepo diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index d8eb86b05d2..8aef66bea7a 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -1166,7 +1166,6 @@ "workspaceHostUnresolved": "The target workspace spans more than one host, so this run has no single host to use." }, "useComposerState": { - "7eb3f44ff7": "Selected agent is disabled. Choose an enabled agent before creating.", "b2ead86962": "Failed to resolve PR base.", "3db83fc58a": "No project path is available on this host for attachments.", "ba6cb77082": "Failed to connect to project.", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 44af8babf19..44057f37dfc 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -993,7 +993,6 @@ "workspaceHostUnresolved": "El espacio de trabajo de destino abarca más de un host, así que esta ejecución no tiene un único host que usar." }, "useComposerState": { - "7eb3f44ff7": "El agente seleccionado está deshabilitado. Elige un agente habilitado antes de crear.", "b2ead86962": "No se pudo resolver la base del PR.", "3db83fc58a": "No hay ninguna ruta de proyecto remoto disponible para los archivos adjuntos.", "ba6cb77082": "No se pudo conectar al proyecto.", diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index f8d9923227a..b724205a711 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -1094,7 +1094,6 @@ "workspaceHostUnresolved": "L'espace de travail cible s'étend sur plusieurs hôtes, cette exécution n'a donc aucun hôte unique à utiliser." }, "useComposerState": { - "7eb3f44ff7": "L'agent sélectionné est désactivé. Choisissez un agent activé avant de créer.", "b2ead86962": "Échec de la résolution de la base de la PR.", "3db83fc58a": "Aucun chemin de projet n'est disponible sur cet hôte pour les pièces jointes.", "ba6cb77082": "Échec de la connexion au projet.", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index a57404afde0..1ccd5d9b3e4 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -993,7 +993,6 @@ "workspaceHostUnresolved": "ターゲットワークスペースは複数のホストにまたがっているため、この実行では使用する単一のホストがありません。" }, "useComposerState": { - "7eb3f44ff7": "選択した Agent は無効です。作成する前に、有効な Agent を選択してください。", "b2ead86962": "PR ベースを解決できませんでした。", "3db83fc58a": "このホスト上に、添付に使用できるプロジェクトパスがありません。", "ba6cb77082": "プロジェクトへの接続に失敗しました。", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 0ba0c0adef0..50745b68614 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -993,7 +993,6 @@ "workspaceHostUnresolved": "대상 워크스페이스는 두 개 이상의 호스트에 걸쳐 있으므로 이 실행에는 사용할 단일 호스트가 없습니다." }, "useComposerState": { - "7eb3f44ff7": "선택한 agent가 비활성화되었습니다. 생성하기 전에 활성화된 agent를 선택하세요.", "b2ead86962": "PR 기반을 해결하지 못했습니다.", "3db83fc58a": "이 호스트에 첨부 파일에 사용할 수 있는 프로젝트 경로가 없습니다.", "ba6cb77082": "프로젝트에 연결하지 못했습니다.", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index cd6971d81a1..46ce24cd38b 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -993,7 +993,6 @@ "workspaceHostUnresolved": "目标工作区跨越多个主机,因此此运行没有可供使用的单个主机。" }, "useComposerState": { - "7eb3f44ff7": "所选智能体已禁用。创建之前选择启用的智能体。", "b2ead86962": "无法解析 PR 基础引用。", "3db83fc58a": "没有可用于附件的远程项目路径。", "ba6cb77082": "无法连接到项目。", diff --git a/src/renderer/src/lib/ensure-hooks-confirmed.ts b/src/renderer/src/lib/ensure-hooks-confirmed.ts index d392033335c..cb5ce3d1c63 100644 --- a/src/renderer/src/lib/ensure-hooks-confirmed.ts +++ b/src/renderer/src/lib/ensure-hooks-confirmed.ts @@ -193,7 +193,7 @@ export type ConfirmedRuntimeIssueCommand = { trustDecision: 'run' | 'skip' } -export function confirmRuntimeIssueCommandRead( +function confirmRuntimeIssueCommandRead( state: AppState, repoId: string, hostId: ExecutionHostId, diff --git a/src/renderer/src/lib/linked-work-item-context.test.ts b/src/renderer/src/lib/linked-work-item-context.test.ts index c05ef6723b1..03c9707e292 100644 --- a/src/renderer/src/lib/linked-work-item-context.test.ts +++ b/src/renderer/src/lib/linked-work-item-context.test.ts @@ -1,10 +1,8 @@ import { describe, expect, it } from 'vitest' -import { buildAgentPromptWithContext } from './new-workspace' import { buildContainedLinkedContextBlock, buildLinearLaunchContextBlock, getLaunchableWorkItemDraftContent, - getLinkedWorkItemPromptContext, LINKED_CONTEXT_BLOCK_MAX_CHARS, resolveQuickCreateLinkedWorkItemPrompt } from './linked-work-item-context' @@ -152,34 +150,6 @@ describe('buildLinearLaunchContextBlock', () => { }) }) -describe('getLinkedWorkItemPromptContext', () => { - it('returns a link-only Linear reference for Linear items', () => { - const result = getLinkedWorkItemPromptContext(LINEAR_ITEM) - - expect(result.linkedUrls).toEqual([]) - expect(result.linkedContextBlocks).toEqual([ - 'Linked Linear issue: ENG-123\nhttps://linear.app/acme/issue/ENG-123/test' - ]) - expectNoLinearTicketContent(result.linkedContextBlocks[0]) - expectNoProductWorkflowDirection(result.linkedContextBlocks[0]) - }) - - it('falls back to the URL for non-Linear items', () => { - expect( - getLinkedWorkItemPromptContext({ - url: 'https://gitlab.example.com/group/project/-/issues/1' - }) - ).toEqual({ - linkedUrls: ['https://gitlab.example.com/group/project/-/issues/1'], - linkedContextBlocks: [] - }) - expect(getLinkedWorkItemPromptContext(null)).toEqual({ - linkedUrls: [], - linkedContextBlocks: [] - }) - }) -}) - describe('resolveQuickCreateLinkedWorkItemPrompt', () => { it('drafts the note above the link-only Linear reference', () => { const result = resolveQuickCreateLinkedWorkItemPrompt( @@ -277,34 +247,3 @@ describe('getLaunchableWorkItemDraftContent', () => { ).toBe('Linked Linear issue\nhttps://linear.app/acme/issue/ENG-123/test\n') }) }) - -describe('buildAgentPromptWithContext', () => { - it('appends link-only Linear references alongside prompt attachments', () => { - const linearBlock = buildLinearLaunchContextBlock({ - provider: 'linear', - identifier: 'ENG-123', - url: LINEAR_ITEM.url - }) - - const prompt = buildAgentPromptWithContext( - 'Fix this', - ['/tmp/report.txt'], - [], - linearBlock ? [linearBlock] : [] - ) - - expect(prompt).toContain( - [ - 'Fix this', - '', - 'Attachments:', - '- /tmp/report.txt', - '', - 'Linked Linear issue: ENG-123', - 'https://linear.app/acme/issue/ENG-123/test' - ].join('\n') - ) - expectNoLinearTicketContent(prompt) - expectNoProductWorkflowDirection(prompt) - }) -}) diff --git a/src/renderer/src/lib/linked-work-item-context.ts b/src/renderer/src/lib/linked-work-item-context.ts index 214b892b35b..593fb3e3411 100644 --- a/src/renderer/src/lib/linked-work-item-context.ts +++ b/src/renderer/src/lib/linked-work-item-context.ts @@ -150,32 +150,6 @@ function capLinkedContextSourceLines(args: { sourceLines: string; fixedChars: nu return [capped, truncationLine].filter(Boolean).join('\n') } -export function getLinkedWorkItemPromptContext( - linkedWorkItem: - | (Pick< - { provider?: TaskProvider; url: string; title?: string; linearIdentifier?: string }, - 'provider' | 'url' | 'title' | 'linearIdentifier' - > & { linkedContext?: LinkedWorkItemContext }) - | null - | undefined -): { linkedUrls: string[]; linkedContextBlocks: string[] } { - if (isLinearWorkItemReference(linkedWorkItem)) { - const linearBlock = buildLinearLaunchContextBlock({ - provider: linkedWorkItem?.provider, - identifier: linkedWorkItem?.linearIdentifier, - title: linkedWorkItem?.title, - url: linkedWorkItem?.url - }) - return linearBlock - ? { linkedUrls: [], linkedContextBlocks: [linearBlock] } - : { linkedUrls: [], linkedContextBlocks: [] } - } - const linkedUrl = linkedWorkItem?.url?.trim() - return linkedUrl - ? { linkedUrls: [linkedUrl], linkedContextBlocks: [] } - : { linkedUrls: [], linkedContextBlocks: [] } -} - export function getLaunchableWorkItemDraftContent(args: { provider?: TaskProvider pasteContent?: string diff --git a/src/renderer/src/lib/new-workspace-create-gates.test.ts b/src/renderer/src/lib/new-workspace-create-gates.test.ts index a38de1bd043..59770f886c0 100644 --- a/src/renderer/src/lib/new-workspace-create-gates.test.ts +++ b/src/renderer/src/lib/new-workspace-create-gates.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'vitest' import { - getFullComposerCreateDisabled, getQuickComposerCreateDisabled, type ComposerCreateGateInput } from './new-workspace-create-gates' @@ -9,8 +8,6 @@ const readyInput: ComposerCreateGateInput = { repoId: 'repo-1', workspaceSeedName: 'feature', creating: false, - shouldWaitForSetupCheck: false, - shouldWaitForIssueAutomationCheck: false, requiresExplicitSetupChoice: false, hasSetupDecision: false, selectedRepoRequiresConnection: false, @@ -18,32 +15,6 @@ const readyInput: ComposerCreateGateInput = { } describe('new workspace create gates', () => { - it('keeps the full composer disabled while setup and issue automation probes are pending', () => { - expect( - getFullComposerCreateDisabled({ - ...readyInput, - shouldWaitForSetupCheck: true - }) - ).toBe(true) - - expect( - getFullComposerCreateDisabled({ - ...readyInput, - shouldWaitForIssueAutomationCheck: true - }) - ).toBe(true) - }) - - it('lets quick create submit while background setup and issue probes are pending', () => { - expect( - getQuickComposerCreateDisabled({ - ...readyInput, - shouldWaitForSetupCheck: true, - shouldWaitForIssueAutomationCheck: true - }) - ).toBe(false) - }) - it('keeps quick create clickable when no repo is selected so submit can validate inline', () => { expect(getQuickComposerCreateDisabled({ ...readyInput, repoId: '' })).toBe(false) }) @@ -66,10 +37,9 @@ describe('new workspace create gates', () => { ) }) - it('blocks full and quick create synchronously for unresolved source intent', () => { + it('blocks quick create synchronously for unresolved source intent', () => { const blocked = { ...readyInput, sourceIntentBlocksCreate: true } - expect(getFullComposerCreateDisabled(blocked)).toBe(true) expect(getQuickComposerCreateDisabled(blocked)).toBe(true) expect(getQuickComposerCreateDisabled({ ...blocked, sourceIntentBlocksCreate: false })).toBe( false diff --git a/src/renderer/src/lib/new-workspace-create-gates.ts b/src/renderer/src/lib/new-workspace-create-gates.ts index 684f849095a..86dce208a7d 100644 --- a/src/renderer/src/lib/new-workspace-create-gates.ts +++ b/src/renderer/src/lib/new-workspace-create-gates.ts @@ -2,8 +2,6 @@ export type ComposerCreateGateInput = { repoId: string workspaceSeedName: string creating: boolean - shouldWaitForSetupCheck: boolean - shouldWaitForIssueAutomationCheck: boolean sourceIntentBlocksCreate?: boolean requiresExplicitSetupChoice: boolean hasSetupDecision: boolean @@ -22,14 +20,6 @@ function hasBlockingCreateState(input: ComposerCreateGateInput): boolean { ) } -export function getFullComposerCreateDisabled(input: ComposerCreateGateInput): boolean { - return ( - hasBlockingCreateState(input) || - input.shouldWaitForSetupCheck || - input.shouldWaitForIssueAutomationCheck - ) -} - export function getQuickComposerCreateDisabled(input: ComposerCreateGateInput): boolean { // Why: quick create resolves setup hooks and optional issue automation inside submit. Keeping those // background probes out of the disabled gate makes the primary action usable diff --git a/src/renderer/src/lib/new-workspace.ts b/src/renderer/src/lib/new-workspace.ts index d19da5e5de4..6d5cf5ed39e 100644 --- a/src/renderer/src/lib/new-workspace.ts +++ b/src/renderer/src/lib/new-workspace.ts @@ -105,38 +105,6 @@ export function renderIssueCommandTemplate( return rendered } -export function buildAgentPromptWithContext( - prompt: string, - attachments: string[], - linkedUrls: string[], - linkedContextBlocks: string[] = [] -): string { - const trimmedPrompt = prompt.trim() - if (attachments.length === 0 && linkedUrls.length === 0 && linkedContextBlocks.length === 0) { - return trimmedPrompt - } - - const sections: string[] = [] - if (attachments.length > 0) { - const attachmentBlock = attachments.map((pathValue) => `- ${pathValue}`).join('\n') - sections.push(`Attachments:\n${attachmentBlock}`) - } - if (linkedUrls.length > 0) { - const linkBlock = linkedUrls.map((url) => `- ${url}`).join('\n') - sections.push(`Linked work items:\n${linkBlock}`) - } - if (linkedContextBlocks.length > 0) { - sections.push(linkedContextBlocks.join('\n\n')) - } - // Why: the new-workspace flow launches each agent with a single plain-text - // startup prompt. Appending attachments and bounded linked context keeps - // extra data visible to Claude/Codex/OpenCode without cluttering the textarea. - if (!trimmedPrompt) { - return sections.join('\n\n') - } - return `${trimmedPrompt}\n\n${sections.join('\n\n')}` -} - export function getAttachmentLabel(pathValue: string): string { const segments = pathValue.split(/[/\\]/) return segments.at(-1) || pathValue diff --git a/tests/e2e/worktree.spec.ts b/tests/e2e/worktree.spec.ts index 7a394426554..d879400be0a 100644 --- a/tests/e2e/worktree.spec.ts +++ b/tests/e2e/worktree.spec.ts @@ -94,9 +94,9 @@ test.describe('Create Workspace', () => { await expect(nameInput).toBeVisible() await nameInput.fill(workspaceName) - // 4. Click Create. This fires the full submitQuick path: - // createWorktree IPC, applyWorktreeMeta, activateAndRevealWorktree, - // and closeModal via onCreated. + // 4. Click Create. submitQuick starts the background creation + // (createWorktree IPC, then activateAndRevealWorktree) and closes the + // modal via onCreated. const createButton = dialog.getByRole('button', { name: /Create (Workspace|Worktree)/i }) await expect(createButton).toBeEnabled() await createButton.click()