From af2d47d358b684327f83c52fbfc4cf7609ca7c7a Mon Sep 17 00:00:00 2001
From: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Date: Thu, 27 Aug 2026 19:29:47 -0700
Subject: [PATCH] Allow selecting parent worktree when creating workspace
- Rename "Parent workspace" label to "Parent worktree"
- Filter candidates by execution host and project to prevent nesting across hosts
- Wire parentWorktreeId through composer state and creation request pipeline
- Update translations and add new copy for nesting-related messages
---
.../NewWorkspaceComposerCard.test.tsx | 30 +++---
.../components/NewWorkspaceComposerCard.tsx | 4 +
.../ComposerParentWorktreePicker.test.tsx | 51 ++++++++++
.../ComposerParentWorktreePicker.tsx | 15 ++-
.../NewWorkspaceComposerAdvancedSection.tsx | 6 ++
.../new-workspace-composer-card-props.ts | 3 +
.../composer-state/composer-card-contract.ts | 4 +
.../composer-state/composer-card-props.ts | 8 ++
.../composer-state/composer-store-actions.ts | 1 +
.../composer-submit-orchestration.ts | 2 +
.../full-creation-execution.test.ts | 1 +
.../composer-state/full-creation-execution.ts | 6 +-
.../hooks/composer-state/identity-model.ts | 2 +
.../quick-creation-execution.ts | 4 +
.../quick-creation-request.test.ts | 3 +
.../composer-state/quick-creation-request.ts | 2 +
.../workspace-identity-state.ts | 4 +
.../useComposerState.integration.test.ts | 8 +-
src/renderer/src/i18n/locales/en.json | 12 ++-
src/renderer/src/i18n/locales/es.json | 12 ++-
src/renderer/src/i18n/locales/ja.json | 12 ++-
src/renderer/src/i18n/locales/ko.json | 89 +++++++++++++++--
src/renderer/src/i18n/locales/zh.json | 97 +++++++++++++++++--
23 files changed, 335 insertions(+), 41 deletions(-)
diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx
index 7895ecffbc0..8206354f916 100644
--- a/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx
+++ b/src/renderer/src/components/NewWorkspaceComposerCard.test.tsx
@@ -308,6 +308,11 @@ function findRunTargetItem(label: string): HTMLElement | undefined {
let current: { container: HTMLDivElement; root: Root } | null = null
+function unmountCurrent(): void {
+ act(() => current?.root.unmount())
+ current?.container.remove()
+}
+
describe('NewWorkspaceComposerCard folder task source mode', () => {
beforeEach(() => {
;(window as unknown as { api: unknown }).api = {
@@ -335,8 +340,7 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
})
afterEach(() => {
- act(() => current?.root.unmount())
- current?.container.remove()
+ unmountCurrent()
current = null
vi.clearAllMocks()
})
@@ -397,8 +401,7 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
)
expect(collapsedReuse).toBeTruthy()
- act(() => current?.root.unmount())
- current?.container.remove()
+ unmountCurrent()
current = renderCard({ canReuseSelectedBranch: true, reuseSelectedBranch: true })
const reuseLabel = [...current.container.querySelectorAll('label')].find((label) =>
@@ -432,8 +435,7 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
clickReuseCheckbox()
expect(offChanges).toEqual([false])
- act(() => current?.root.unmount())
- current?.container.remove()
+ unmountCurrent()
// Unchecked -> checked (opting into reuse — the action that pins the branch).
const onChanges: boolean[] = []
@@ -460,8 +462,7 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
'Wait for setup to complete before starting agent'
)
- act(() => current?.root.unmount())
- current?.container.remove()
+ unmountCurrent()
current = renderCard({
advancedOpen: true,
@@ -587,12 +588,10 @@ describe('NewWorkspaceComposerCard folder task source mode', () => {
branchesEnabled: true
})
- const branchInput = findInputByLabel(current.container, 'Branch name')
- expect(branchInput).toBeTruthy()
- const branchBlock = branchInput?.closest('div.space-y-1')
- const nextField = branchBlock?.nextElementSibling
- expect(nextField?.textContent).toContain('Parent workspace')
- expect(nextField?.textContent).not.toContain('Note')
+ const nextField = findInputByLabel(current.container, 'Branch name')?.closest(
+ 'div.space-y-1'
+ )?.nextElementSibling
+ expect(nextField?.textContent).toMatch(/Parent worktree(?!.*Note)/)
expect(nextField?.nextElementSibling?.textContent).toContain('Note')
})
@@ -903,8 +902,7 @@ describe('NewWorkspaceComposerCard note sizing', () => {
// Sizing is layout-driven (field-sizing) rather than a JS measure pass, and happy-dom
// has no layout engine, so these assert the class contract that produces the growth.
afterEach(() => {
- act(() => current?.root.unmount())
- current?.container.remove()
+ unmountCurrent()
current = null
})
diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.tsx
index 4f819cb4fc5..c6cca5192ea 100644
--- a/src/renderer/src/components/NewWorkspaceComposerCard.tsx
+++ b/src/renderer/src/components/NewWorkspaceComposerCard.tsx
@@ -57,6 +57,8 @@ export default function NewWorkspaceComposerCard(
selectedProjectId = null,
parentWorktreeId,
onParentWorktreeIdChange,
+ selectedRepoExecutionHostId,
+ selectedRepoProjectId,
activeFolderWorkspaceId,
onAddProjectOverride,
onNestedDialogOpenChange
@@ -307,6 +309,8 @@ export default function NewWorkspaceComposerCard(
showSetupAgentStartupPolicy={showSetupAgentStartupPolicy}
parentWorktreeId={parentWorktreeId}
onParentWorktreeIdChange={onParentWorktreeIdChange}
+ selectedRepoExecutionHostId={selectedRepoExecutionHostId}
+ selectedRepoProjectId={selectedRepoProjectId}
activeFolderWorkspaceId={activeFolderWorkspaceId}
/>
diff --git a/src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.test.tsx b/src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.test.tsx
index 6846a94ba38..c512abcebdb 100644
--- a/src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.test.tsx
+++ b/src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.test.tsx
@@ -323,6 +323,57 @@ describe('ComposerParentWorktreePicker', () => {
expect(candidateLabels().join(' ')).not.toContain('Other repo')
})
+ it('excludes candidates on another execution host or project', () => {
+ seed([
+ makeWorktree({ id: 'same', displayName: 'Same host', hostId: 'local', projectId: 'proj1' }),
+ makeWorktree({
+ id: 'otherHost',
+ displayName: 'Other host',
+ hostId: 'ssh:box',
+ projectId: 'proj1'
+ }),
+ makeWorktree({
+ id: 'otherProject',
+ displayName: 'Other project',
+ hostId: 'local',
+ projectId: 'proj2'
+ })
+ ])
+
+ render(
+
+ )
+ fireEvent.click(trigger())
+
+ expect(candidateLabels().join(' ')).toContain('Same host')
+ expect(candidateLabels().join(' ')).not.toContain('Other host')
+ expect(candidateLabels().join(' ')).not.toContain('Other project')
+ })
+
+ // A worktree with no recorded hostId inherits its repo's host, which is the child's host too.
+ it('keeps candidates whose host or project is unrecorded', () => {
+ seed([makeWorktree({ id: 'unscoped', displayName: 'Unscoped' })])
+
+ render(
+
+ )
+ fireEvent.click(trigger())
+
+ expect(candidateLabels().join(' ')).toContain('Unscoped')
+ })
+
it('restricts candidates to the active folder workspace subtree', () => {
const attached = makeWorktree({
id: 'attached',
diff --git a/src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.tsx b/src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.tsx
index b888f80e7c7..41c22978f14 100644
--- a/src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.tsx
+++ b/src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.tsx
@@ -117,7 +117,7 @@ function ComposerParentWorktreePickerImpl({
return (
- {translate('auto.components.ComposerParentWorktreePicker.label', 'Parent workspace')}
+ {translate('auto.components.ComposerParentWorktreePicker.label', 'Parent worktree')}
@@ -225,8 +225,14 @@ function ParentWorktreeCandidateList({
const [highlightedIndex, setHighlightedIndex] = useState(0)
const candidates = useMemo(() => {
- const childBoundary: WorktreeLineageBoundary | null =
- executionHostId && projectId ? { repoId, hostId: executionHostId, projectId } : null
+ // Why `?? undefined`: an unresolved host or project is "not known yet", not "no host", and
+ // the boundary check reads undefined on either side as a wildcard. A candidate in this repo
+ // with no recorded hostId inherits that repo's host, so it is on the child's host too.
+ const childBoundary: WorktreeLineageBoundary = {
+ repoId,
+ hostId: executionHostId ?? undefined,
+ projectId: projectId ?? undefined
+ }
const worktreeMap = getIndexedWorktreeMap(worktreesByRepo)
const cyclicLineageIds = getCyclicProjectedWorktreeLineageIds(worktreeLineageById, worktreeMap)
const folderSubtreeIds = activeFolderWorkspaceId
@@ -241,9 +247,8 @@ function ParentWorktreeCandidateList({
.filter(
(candidate) =>
candidate.repoId === repoId &&
- (executionHostId === undefined || candidate.hostId === executionHostId) &&
!candidate.isArchived &&
- (childBoundary === null || sharesWorktreeLineageBoundary(childBoundary, candidate)) &&
+ sharesWorktreeLineageBoundary(childBoundary, candidate) &&
!cyclicLineageIds.has(candidate.id) &&
(folderSubtreeIds === null || folderSubtreeIds.has(candidate.id))
)
diff --git a/src/renderer/src/components/new-workspace/NewWorkspaceComposerAdvancedSection.tsx b/src/renderer/src/components/new-workspace/NewWorkspaceComposerAdvancedSection.tsx
index 9b46a2480e9..f7b18f6ebaa 100644
--- a/src/renderer/src/components/new-workspace/NewWorkspaceComposerAdvancedSection.tsx
+++ b/src/renderer/src/components/new-workspace/NewWorkspaceComposerAdvancedSection.tsx
@@ -40,6 +40,8 @@ type NewWorkspaceComposerAdvancedSectionProps = Pick<
| 'onBranchNameOverrideChange'
| 'parentWorktreeId'
| 'onParentWorktreeIdChange'
+ | 'selectedRepoExecutionHostId'
+ | 'selectedRepoProjectId'
| 'activeFolderWorkspaceId'
| 'note'
| 'onNoteChange'
@@ -80,6 +82,8 @@ export function NewWorkspaceComposerAdvancedSection({
onBranchNameOverrideChange,
parentWorktreeId = null,
onParentWorktreeIdChange,
+ selectedRepoExecutionHostId,
+ selectedRepoProjectId,
activeFolderWorkspaceId = null,
note,
onNoteChange,
@@ -201,6 +205,8 @@ export function NewWorkspaceComposerAdvancedSection({
{selectedRepoIsGit && branchesEnabled && onParentWorktreeIdChange ? (
['repos'][number]
export type EphemeralVmRecipeOption = NonNullable[number]
@@ -63,6 +64,8 @@ export type NewWorkspaceComposerCardProps = {
onBranchNameOverrideChange: (value: string | undefined) => void
parentWorktreeId?: string | null
onParentWorktreeIdChange?: (value: string | null) => void
+ selectedRepoExecutionHostId?: ExecutionHostId | null
+ selectedRepoProjectId?: string | null
activeFolderWorkspaceId?: string | null
onSmartGitHubItemSelect: (item: GitHubWorkItem) => void
onSmartGitLabItemSelect: (item: GitLabWorkItem) => void
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 b41282032e4..1dffa161c32 100644
--- a/src/renderer/src/hooks/composer-state/composer-card-contract.ts
+++ b/src/renderer/src/hooks/composer-state/composer-card-contract.ts
@@ -14,6 +14,9 @@ export type ComposerCardSourceProps = Pick<
| 'ephemeralVmRecipeError'
| 'name'
| 'branchNameOverride'
+ | 'parentWorktreeId'
+ | 'selectedRepoExecutionHostId'
+ | 'selectedRepoProjectId'
| 'smartNameSelection'
| 'reuseSelectedBranch'
| 'createMultiple'
@@ -63,6 +66,7 @@ export type ComposerCardActionProps = {
smartNameRepoSwitchTarget?: 'project' | 'task-source'
onNameValueChange: ComposerModel['handleNameValueChange']
onBranchNameOverrideChange: ComposerModel['handleBranchNameOverrideChange']
+ onParentWorktreeIdChange: ComposerModel['setParentWorktreeId']
onSmartGitHubItemSelect: ComposerModel['handleSmartGitHubItemSelect']
onSmartGitLabItemSelect: ComposerModel['handleSmartGitLabItemSelect']
onSmartBranchSelect: ComposerModel['handleSmartBranchSelect']
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 8363910fe8f..0e126e3af75 100644
--- a/src/renderer/src/hooks/composer-state/composer-card-props.ts
+++ b/src/renderer/src/hooks/composer-state/composer-card-props.ts
@@ -13,6 +13,7 @@ export function buildComposerCardProps(state: ComposerModel) {
attachmentPaths,
baseBranch,
branchNameOverride,
+ parentWorktreeId,
createGateMode,
createError,
createMultiple,
@@ -81,10 +82,12 @@ export function buildComposerCardProps(state: ComposerModel) {
selectedProjectHostSetupId,
selectedProjectId,
selectedRepo,
+ selectedRepoExecutionHostId,
selectedRepoConnectInProgress,
selectedRepoConnectionId,
selectedRepoGitHubSourceContext,
selectedRepoIsGit,
+ selectedRepoProjectId,
selectedRepoRequiresConnection,
shouldWaitForIssueAutomationCheck,
sourceIntentBlocksCreate,
@@ -96,6 +99,7 @@ export function buildComposerCardProps(state: ComposerModel) {
setCreateMultiple,
setLinkQuery,
setNote,
+ setParentWorktreeId,
setSelectedEphemeralVmRecipeId,
setSetupDecision,
setSmartNameMode,
@@ -157,6 +161,10 @@ export function buildComposerCardProps(state: ComposerModel) {
onNameValueChange: handleNameValueChange,
branchNameOverride: isProjectGroupTarget ? undefined : branchNameOverride,
onBranchNameOverrideChange: isProjectGroupTarget ? () => {} : handleBranchNameOverrideChange,
+ parentWorktreeId: isProjectGroupTarget ? null : parentWorktreeId,
+ onParentWorktreeIdChange: isProjectGroupTarget ? () => {} : setParentWorktreeId,
+ selectedRepoExecutionHostId: isProjectGroupTarget ? null : selectedRepoExecutionHostId,
+ selectedRepoProjectId: isProjectGroupTarget ? null : selectedRepoProjectId,
onSmartGitHubItemSelect: handleSmartGitHubItemSelect,
onSmartGitLabItemSelect: handleSmartGitLabItemSelect,
onSmartBranchSelect: isProjectGroupTarget ? () => {} : handleSmartBranchSelect,
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 3c0a172dc46..3aa7d96f5d3 100644
--- a/src/renderer/src/hooks/composer-state/composer-store-actions.ts
+++ b/src/renderer/src/hooks/composer-state/composer-store-actions.ts
@@ -55,6 +55,7 @@ export type ComposerStoreActions = {
linkedTaskSourceContext?: TaskSourceContext | null
startupDraft?: string
nameWasGenerated?: boolean
+ parentWorktreeId?: string
provisionedRoot?: {
runtimeId: string
executionHostId: ExecutionHostId
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 e4289e452cb..b925eb12b2d 100644
--- a/src/renderer/src/hooks/composer-state/composer-submit-orchestration.ts
+++ b/src/renderer/src/hooks/composer-state/composer-submit-orchestration.ts
@@ -98,6 +98,7 @@ export function useComposerSubmitOrchestration(
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,
@@ -210,6 +211,7 @@ export function useComposerSubmitOrchestration(
linkedGitLabMR: target.workspaceIdentityState.linkedGitLabMR,
normalizedSparseDirectories: target.derivedComposerState.normalizedSparseDirectories,
onCreated: target.composerTargetStore.onCreated,
+ parentWorktreeId: target.workspaceIdentityState.parentWorktreeId,
persistDraft: target.composerTargetStore.persistDraft,
persistSetupAgentStartupPolicy: target.providerRuntimeSync.persistSetupAgentStartupPolicy,
prepareQuickSubmit: quickSubmitPreparation.prepareQuickSubmit,
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
index 704dac2832e..c8bca80df09 100644
--- a/src/renderer/src/hooks/composer-state/full-creation-execution.test.ts
+++ b/src/renderer/src/hooks/composer-state/full-creation-execution.test.ts
@@ -72,6 +72,7 @@ describe('useFullCreationExecution cancellation', () => {
normalizedSparseDirectories: [],
note: '',
onCreated: vi.fn>(),
+ parentWorktreeId: null,
persistDraft: false,
persistSetupAgentStartupPolicy,
prepareFullSubmit: vi
diff --git a/src/renderer/src/hooks/composer-state/full-creation-execution.ts b/src/renderer/src/hooks/composer-state/full-creation-execution.ts
index 713d42961d1..04ec3e2cf6d 100644
--- a/src/renderer/src/hooks/composer-state/full-creation-execution.ts
+++ b/src/renderer/src/hooks/composer-state/full-creation-execution.ts
@@ -12,6 +12,7 @@ export type FullCreationExecutionInput = Pick<
| 'normalizedSparseDirectories'
| 'note'
| 'onCreated'
+ | 'parentWorktreeId'
| 'persistDraft'
| 'persistSetupAgentStartupPolicy'
| 'prepareFullSubmit'
@@ -47,6 +48,7 @@ export function useFullCreationExecution(input: FullCreationExecutionInput) {
normalizedSparseDirectories,
note,
onCreated,
+ parentWorktreeId,
persistDraft,
persistSetupAgentStartupPolicy,
prepareFullSubmit,
@@ -155,7 +157,8 @@ export function useFullCreationExecution(input: FullCreationExecutionInput) {
nameWasGenerated,
...(!backendStartup && startupPlan?.draftPrompt
? { startupDraft: startupPlan.draftPrompt }
- : {})
+ : {}),
+ ...(parentWorktreeId ? { parentWorktreeId } : {})
}
)
@@ -251,6 +254,7 @@ export function useFullCreationExecution(input: FullCreationExecutionInput) {
normalizedSparseDirectories,
note,
onCreated,
+ parentWorktreeId,
persistDraft,
persistSetupAgentStartupPolicy,
prepareFullSubmit,
diff --git a/src/renderer/src/hooks/composer-state/identity-model.ts b/src/renderer/src/hooks/composer-state/identity-model.ts
index 8830e3d72bc..e375dd754d7 100644
--- a/src/renderer/src/hooks/composer-state/identity-model.ts
+++ b/src/renderer/src/hooks/composer-state/identity-model.ts
@@ -18,6 +18,8 @@ export type ComposerIdentityModel = {
setCompareBaseRef: React.Dispatch>
branchNameOverride: string | undefined
setBranchNameOverride: React.Dispatch>
+ parentWorktreeId: string | null
+ setParentWorktreeId: React.Dispatch>
branchNameOverridePreservesNameEdits: boolean
setBranchNameOverridePreservesNameEdits: React.Dispatch>
smartNameMode: SmartNameMode
diff --git a/src/renderer/src/hooks/composer-state/quick-creation-execution.ts b/src/renderer/src/hooks/composer-state/quick-creation-execution.ts
index 1e91f686d82..b6b14350290 100644
--- a/src/renderer/src/hooks/composer-state/quick-creation-execution.ts
+++ b/src/renderer/src/hooks/composer-state/quick-creation-execution.ts
@@ -12,6 +12,7 @@ type QuickCreationExecutionInput = Pick<
| 'linkedGitLabMR'
| 'normalizedSparseDirectories'
| 'onCreated'
+ | 'parentWorktreeId'
| 'persistDraft'
| 'persistSetupAgentStartupPolicy'
| 'prepareQuickSubmit'
@@ -58,6 +59,7 @@ export function useQuickCreationExecution(input: QuickCreationExecutionInput) {
linkedGitLabMR,
normalizedSparseDirectories,
onCreated,
+ parentWorktreeId,
persistDraft,
persistSetupAgentStartupPolicy,
prepareQuickSubmit,
@@ -217,6 +219,7 @@ export function useQuickCreationExecution(input: QuickCreationExecutionInput) {
linkedLinearIssueWorkspaceId,
linkedLinearIssueOrganizationUrlKey,
branchNameOverride: effectiveBranchNameOverride,
+ parentWorktreeId,
workspaceStatus: resolvedInitialWorkspaceStatus,
linkedGitLabMR,
linkedGitLabIssue,
@@ -259,6 +262,7 @@ export function useQuickCreationExecution(input: QuickCreationExecutionInput) {
linkedGitLabMR,
normalizedSparseDirectories,
onCreated,
+ parentWorktreeId,
persistDraft,
persistSetupAgentStartupPolicy,
prepareQuickSubmit,
diff --git a/src/renderer/src/hooks/composer-state/quick-creation-request.test.ts b/src/renderer/src/hooks/composer-state/quick-creation-request.test.ts
index 0533bcc2e28..d5cd9a11c5b 100644
--- a/src/renderer/src/hooks/composer-state/quick-creation-request.test.ts
+++ b/src/renderer/src/hooks/composer-state/quick-creation-request.test.ts
@@ -29,6 +29,7 @@ function createInput(
linkedLinearIssueWorkspaceId: undefined,
linkedLinearIssueOrganizationUrlKey: undefined,
branchNameOverride: undefined,
+ parentWorktreeId: null,
workspaceStatus: undefined,
linkedGitLabMR: null,
linkedGitLabIssue: null,
@@ -87,6 +88,7 @@ describe('quick composer creation request', () => {
linkedLinearIssueWorkspaceId: 'workspace-1',
linkedLinearIssueOrganizationUrlKey: 'orca',
branchNameOverride: 'feature',
+ parentWorktreeId: 'repo-1::/worktrees/parent',
workspaceStatus: 'in-progress',
linkedGitLabMR: 9,
linkedGitLabIssue: 8,
@@ -107,6 +109,7 @@ describe('quick composer creation request', () => {
linkedGitLabMR: 9,
linkedGitLabIssue: 8,
branchNameOverride: 'feature',
+ parentWorktreeId: 'repo-1::/worktrees/parent',
launchDraftPrompt: 'draft',
suppressTerminalFocusOnCompletion: true
})
diff --git a/src/renderer/src/hooks/composer-state/quick-creation-request.ts b/src/renderer/src/hooks/composer-state/quick-creation-request.ts
index 71f0735f70b..e12f2dd6256 100644
--- a/src/renderer/src/hooks/composer-state/quick-creation-request.ts
+++ b/src/renderer/src/hooks/composer-state/quick-creation-request.ts
@@ -33,6 +33,7 @@ export type QuickCreationRequestInput = {
linkedLinearIssueWorkspaceId: string | undefined
linkedLinearIssueOrganizationUrlKey: string | undefined
branchNameOverride: string | undefined
+ parentWorktreeId: string | null
workspaceStatus: WorktreeCreationRequest['workspaceStatus']
linkedGitLabMR: number | null
linkedGitLabIssue: number | null
@@ -88,6 +89,7 @@ export function buildQuickCreationRequest(
? { linkedLinearIssueOrganizationUrlKey: input.linkedLinearIssueOrganizationUrlKey }
: {}),
...(input.branchNameOverride ? { branchNameOverride: input.branchNameOverride } : {}),
+ ...(input.parentWorktreeId ? { parentWorktreeId: input.parentWorktreeId } : {}),
...(input.workspaceStatus ? { workspaceStatus: input.workspaceStatus } : {}),
...(input.includeGitLabLinks && input.linkedGitLabMR != null
? { linkedGitLabMR: input.linkedGitLabMR }
diff --git a/src/renderer/src/hooks/composer-state/workspace-identity-state.ts b/src/renderer/src/hooks/composer-state/workspace-identity-state.ts
index 8d46a3551d4..6b7e73e9417 100644
--- a/src/renderer/src/hooks/composer-state/workspace-identity-state.ts
+++ b/src/renderer/src/hooks/composer-state/workspace-identity-state.ts
@@ -100,6 +100,8 @@ export function useWorkspaceIdentityState(input: WorkspaceIdentityStateInput) {
initialLinearBranchName
)
+ const [parentWorktreeId, setParentWorktreeId] = useState(null)
+
const [branchNameOverridePreservesNameEdits, setBranchNameOverridePreservesNameEdits] = useState(
Boolean(initialLinearBranchName)
)
@@ -195,6 +197,8 @@ export function useWorkspaceIdentityState(input: WorkspaceIdentityStateInput) {
setCompareBaseRef,
branchNameOverride,
setBranchNameOverride,
+ parentWorktreeId,
+ setParentWorktreeId,
branchNameOverridePreservesNameEdits,
setBranchNameOverridePreservesNameEdits,
smartNameMode,
diff --git a/src/renderer/src/hooks/useComposerState.integration.test.ts b/src/renderer/src/hooks/useComposerState.integration.test.ts
index 2119e99f860..c82e1a118c6 100644
--- a/src/renderer/src/hooks/useComposerState.integration.test.ts
+++ b/src/renderer/src/hooks/useComposerState.integration.test.ts
@@ -1,6 +1,6 @@
// @vitest-environment happy-dom
-import { renderHook } from '@testing-library/react'
+import { act, renderHook } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useAppStore } from '@/store'
import { useComposerState } from './useComposerState'
@@ -31,7 +31,7 @@ afterEach(() => {
})
describe('useComposerState integrated lifecycle', () => {
- it('composes two live composers and releases each native drop listener on unmount', () => {
+ it('composes two live composers and exposes the parent-worktree control state', () => {
useAppStore.setState({
repos: [],
projects: [],
@@ -57,6 +57,10 @@ describe('useComposerState integrated lifecycle', () => {
expect(first.result.current.cardProps.name).toBe('first')
expect(second.result.current.cardProps.name).toBe('second')
+ expect(first.result.current.cardProps.parentWorktreeId).toBeNull()
+ expect(first.result.current.cardProps.onParentWorktreeIdChange).toBeTypeOf('function')
+ act(() => first.result.current.cardProps.onParentWorktreeIdChange('repo-1::/parent'))
+ expect(first.result.current.cardProps.parentWorktreeId).toBe('repo-1::/parent')
expect(window.api.ui.onFileDrop).toHaveBeenCalledTimes(2)
second.unmount()
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json
index bbffbf8a974..3f0a9feb9f0 100644
--- a/src/renderer/src/i18n/locales/en.json
+++ b/src/renderer/src/i18n/locales/en.json
@@ -448,7 +448,10 @@
}
}
}
- }
+ },
+ "createdWithoutParentNesting": "Created without nesting under \"{{value0}}\"",
+ "createdWithoutParentNestingUnnamed": "Created without nesting under the selected parent",
+ "createdWithoutParentNestingDetail": "The parent workspace was no longer available. You can set it from the workspace menu."
},
"repos": {
"2975400634": "Update Orca server to open non-Git folders on this runtime.",
@@ -16293,6 +16296,13 @@
"remoteLabel": "Browsers on {{value0}}",
"clientNoteLocalStorage": "Imports read this device’s browsers. Cookies are stored locally.",
"remoteNoteRemoteStorage": "Imports read browsers on {{value0}}. Cookies are stored on that machine, and a permission prompt may appear on its screen."
+ },
+ "ComposerParentWorktreePicker": {
+ "label": "Parent worktree",
+ "noParent": "No parent",
+ "description": "Nests this workspace under another in the sidebar. Does not change the base branch.",
+ "searchPlaceholder": "Search workspaces...",
+ "noMatches": "No matches."
}
},
"i18n": {
diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json
index 5142116a66b..5fa24d06071 100644
--- a/src/renderer/src/i18n/locales/es.json
+++ b/src/renderer/src/i18n/locales/es.json
@@ -263,7 +263,10 @@
"3b57982bf6": "Git no pudo eliminar de forma segura la rama \"{{value0}}\" después de eliminar el espacio de trabajo \"{{value1}}\", así que Orca la conservó para evitar perder commits locales.",
"81f13f48d2": "Git no pudo eliminar de forma segura la rama \"{{value0}}\" después de eliminar el worktree \"{{value1}}\", así que Orca la conservó para evitar perder commits locales.",
"runtimeScopeForbiddenTitle": "Esta conexión tiene acceso limitado (móvil)",
- "runtimeScopeForbiddenDescription": "Los espacios de trabajo no están disponibles con un emparejamiento de acceso móvil. Vuelve a conectarte con el enlace del navegador desde Ajustes → Servidores remotos de Orca → Comparte este servidor Orca."
+ "runtimeScopeForbiddenDescription": "Los espacios de trabajo no están disponibles con un emparejamiento de acceso móvil. Vuelve a conectarte con el enlace del navegador desde Ajustes → Servidores remotos de Orca → Comparte este servidor Orca.",
+ "createdWithoutParentNesting": "Creado sin anidarlo bajo \"{{value0}}\"",
+ "createdWithoutParentNestingUnnamed": "Creado sin anidarlo bajo el worktree padre seleccionado",
+ "createdWithoutParentNestingDetail": "El espacio de trabajo padre ya no estaba disponible. Puedes establecerlo desde el menú del espacio de trabajo."
},
"repos": {
"2975400634": "Actualiza el servidor Orca para abrir carpetas que no sean Git en este host.",
@@ -14434,6 +14437,13 @@
"deleteDescription": "“{{name}}” ya no estará disponible en su enlace público.",
"delete": "Eliminar"
}
+ },
+ "ComposerParentWorktreePicker": {
+ "label": "Worktree padre",
+ "noParent": "Sin worktree padre",
+ "description": "Anida este espacio de trabajo bajo otro en la barra lateral. No cambia la rama base.",
+ "searchPlaceholder": "Buscar espacios de trabajo...",
+ "noMatches": "No hay coincidencias."
}
},
"i18n": {
diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json
index 1c72acfa367..0394b1c3d01 100644
--- a/src/renderer/src/i18n/locales/ja.json
+++ b/src/renderer/src/i18n/locales/ja.json
@@ -263,7 +263,10 @@
"3b57982bf6": "ワークスペース\"{{value1}}\"を削除した後、Git はブランチ\"{{value0}}\"を安全に削除できなかったため、Orca はローカルコミットを失わないように保持しました。",
"81f13f48d2": "ワークツリー\"{{value1}}\"を削除した後、Git はブランチ\"{{value0}}\"を安全に削除できなかったため、Orca はローカルコミットを失わないように保持しました。",
"runtimeScopeForbiddenTitle": "この接続は制限付き(モバイル)アクセスです",
- "runtimeScopeForbiddenDescription": "モバイル向けペアリングではワークスペースを使用できません。[設定] → [リモート Orca サーバー] → [この Orca サーバーを共有する] から、ブラウザ用リンクで再接続してください。"
+ "runtimeScopeForbiddenDescription": "モバイル向けペアリングではワークスペースを使用できません。[設定] → [リモート Orca サーバー] → [この Orca サーバーを共有する] から、ブラウザ用リンクで再接続してください。",
+ "createdWithoutParentNesting": "「{{value0}}」の配下にネストせずに作成しました",
+ "createdWithoutParentNestingUnnamed": "選択した親ワークツリーの配下にネストせずに作成しました",
+ "createdWithoutParentNestingDetail": "親ワークスペースは利用できなくなりました。ワークスペースメニューから設定できます。"
},
"repos": {
"2975400634": "Orca サーバーを更新して、このランタイムで Git 以外のフォルダーを開くようにします。",
@@ -14425,6 +14428,13 @@
"deleteDescription": "「{{name}}」は公開リンクで利用できなくなります。",
"delete": "削除"
}
+ },
+ "ComposerParentWorktreePicker": {
+ "label": "親ワークツリー",
+ "noParent": "親なし",
+ "description": "このワークスペースをサイドバー内の別のワークスペースの配下にネストします。ベースブランチは変更されません。",
+ "searchPlaceholder": "ワークスペースを検索...",
+ "noMatches": "一致する項目がありません。"
}
},
"i18n": {
diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json
index e5aefb9291a..3a57a64954a 100644
--- a/src/renderer/src/i18n/locales/ko.json
+++ b/src/renderer/src/i18n/locales/ko.json
@@ -266,7 +266,10 @@
"3b57982bf6": "\"{{value1}}\" 워크스페이스를 삭제한 후 Git이 \"{{value0}}\" 브랜치를 안전하게 삭제할 수 없어 Orca가 로컬 커밋 손실을 방지하기 위해 유지했습니다.",
"81f13f48d2": "\"{{value1}}\" 워크트리를 삭제한 후 Git이 \"{{value0}}\" 브랜치를 안전하게 삭제할 수 없어 Orca가 로컬 커밋 손실을 방지하기 위해 유지했습니다.",
"runtimeScopeForbiddenTitle": "이 연결은 제한된(모바일) 액세스입니다",
- "runtimeScopeForbiddenDescription": "모바일 범위 페어링에서는 워크스페이스를 사용할 수 없습니다. 설정 → 원격 Orca 서버 → 이 Orca 서버 공유에서 브라우저 액세스 링크로 다시 연결하세요."
+ "runtimeScopeForbiddenDescription": "모바일 범위 페어링에서는 워크스페이스를 사용할 수 없습니다. 설정 → 원격 Orca 서버 → 이 Orca 서버 공유에서 브라우저 액세스 링크로 다시 연결하세요.",
+ "createdWithoutParentNesting": "\"{{value0}}\" 아래에 중첩하지 않고 생성했습니다",
+ "createdWithoutParentNestingUnnamed": "선택한 상위 워크트리 아래에 중첩하지 않고 생성했습니다",
+ "createdWithoutParentNestingDetail": "상위 워크스페이스를 더 이상 사용할 수 없습니다. 워크스페이스 메뉴에서 설정할 수 있습니다."
},
"repos": {
"2975400634": "이 런타임에서 Git이 아닌 폴더를 열려면 Orca 서버를 업데이트하세요.",
@@ -3879,17 +3882,82 @@
},
"skills": {
"SkillsPage": {
- "cb142070b4": "새로 고침", "a68dee6a32": "스킬 검색", "f43ad6edf3": "스킬", "ea72d6185b": "스킬을 검색하지 못했습니다", "dc4c3328ee": "파일 표시", "9963dff6d3": "설명을 찾을 수 없습니다.", "995fde8337": "스킬 파일을 표시하지 못했습니다", "4acd6d68ec": "스킬을 찾을 수 없습니다", "6a62a0168c": "일치하는 항목 없음", "08a321a984": "현재 검색 및 필터와 일치하는 스킬이 없습니다.", "cd7893fbc1": "스킬 검색 중", "35b9a724a0": "사용 가능", "c13b82793c": "설치 관리", "aee7b99cc6": "링크에서 설치", "filterProvider": "에이전트별 필터", "filterSource": "소스별 필터", "allSources": "전체", "clearFilters": "필터 지우기", "closeSkills": "스킬 닫기", "closeTooltip": "닫기 · Esc", "moreActions": "추가 작업", "sharedLinks": "공유 링크", "emptyCopy": "검색한 스킬 폴더가 비어 있습니다. 공유 번들을 설치하거나 스킬을 추가한 후 새로 고치세요.", "retry": "다시 시도", "remoteShareNotice": "이 스킬은 {{host}}에 있습니다. 공유하려면 해당 머신에서 스킬을 여세요.", "viewSwitch": "표시", "searchLinks": "링크 검색", "deleteSkills": "스킬 삭제…"
+ "cb142070b4": "새로 고침",
+ "a68dee6a32": "스킬 검색",
+ "f43ad6edf3": "스킬",
+ "ea72d6185b": "스킬을 검색하지 못했습니다",
+ "dc4c3328ee": "파일 표시",
+ "9963dff6d3": "설명을 찾을 수 없습니다.",
+ "995fde8337": "스킬 파일을 표시하지 못했습니다",
+ "4acd6d68ec": "스킬을 찾을 수 없습니다",
+ "6a62a0168c": "일치하는 항목 없음",
+ "08a321a984": "현재 검색 및 필터와 일치하는 스킬이 없습니다.",
+ "cd7893fbc1": "스킬 검색 중",
+ "35b9a724a0": "사용 가능",
+ "c13b82793c": "설치 관리",
+ "aee7b99cc6": "링크에서 설치",
+ "filterProvider": "에이전트별 필터",
+ "filterSource": "소스별 필터",
+ "allSources": "전체",
+ "clearFilters": "필터 지우기",
+ "closeSkills": "스킬 닫기",
+ "closeTooltip": "닫기 · Esc",
+ "moreActions": "추가 작업",
+ "sharedLinks": "공유 링크",
+ "emptyCopy": "검색한 스킬 폴더가 비어 있습니다. 공유 번들을 설치하거나 스킬을 추가한 후 새로 고치세요.",
+ "retry": "다시 시도",
+ "remoteShareNotice": "이 스킬은 {{host}}에 있습니다. 공유하려면 해당 머신에서 스킬을 여세요.",
+ "viewSwitch": "표시",
+ "searchLinks": "링크 검색",
+ "deleteSkills": "스킬 삭제…"
},
"SkillShareSelectionControls": { "01c5a15e02": "스킬 공유" },
- "SkillRow": { "updatedUnknown": "날짜 없음", "pathCopied": "경로 복사됨", "copyPath": "경로 복사", "detailPath": "경로", "skillActions": "{{value0}} 작업", "viewDetails": "세부 정보 보기", "deleteSkill": "삭제…" },
+ "SkillRow": {
+ "updatedUnknown": "날짜 없음",
+ "pathCopied": "경로 복사됨",
+ "copyPath": "경로 복사",
+ "detailPath": "경로",
+ "skillActions": "{{value0}} 작업",
+ "viewDetails": "세부 정보 보기",
+ "deleteSkill": "삭제…"
+ },
"SkillsList": { "listLabel": "스킬" },
- "sourceStatus": { "missing": "폴더를 찾을 수 없음", "remoteRepo": "원격 리포지토리 — 검색 안 됨", "unavailable": "검색 안 됨" },
+ "sourceStatus": {
+ "missing": "폴더를 찾을 수 없음",
+ "remoteRepo": "원격 리포지토리 — 검색 안 됨",
+ "unavailable": "검색 안 됨"
+ },
"sources": { "heading": "스킬 폴더" },
- "sourceKind": { "home": "홈", "workspace": "워크스페이스", "bundled": "번들", "plugin": "플러그인" },
- "count": { "skillOne": "스킬 {{count}}개", "skillOther": "스킬 {{count}}개", "sourceOne": "소스 {{count}}개", "sourceOther": "소스 {{count}}개", "fileOne": "파일 {{count}}개", "fileOther": "파일 {{count}}개", "resultOne": "결과 {{count}}개", "resultOther": "결과 {{count}}개", "selected": "{{count}}개 선택됨", "shareOne": "스킬 {{count}}개 공유", "shareOther": "스킬 {{count}}개 공유", "linkOne": "링크 {{count}}개", "linkOther": "링크 {{count}}개" },
+ "sourceKind": {
+ "home": "홈",
+ "workspace": "워크스페이스",
+ "bundled": "번들",
+ "plugin": "플러그인"
+ },
+ "count": {
+ "skillOne": "스킬 {{count}}개",
+ "skillOther": "스킬 {{count}}개",
+ "sourceOne": "소스 {{count}}개",
+ "sourceOther": "소스 {{count}}개",
+ "fileOne": "파일 {{count}}개",
+ "fileOther": "파일 {{count}}개",
+ "resultOne": "결과 {{count}}개",
+ "resultOther": "결과 {{count}}개",
+ "selected": "{{count}}개 선택됨",
+ "shareOne": "스킬 {{count}}개 공유",
+ "shareOther": "스킬 {{count}}개 공유",
+ "linkOne": "링크 {{count}}개",
+ "linkOther": "링크 {{count}}개"
+ },
"filter": { "allAgents": "모든 에이전트", "sharedAgent": "공유됨 (.agents)" },
- "SkillsSelectionHeader": { "exit": "선택 나가기", "exitTooltip": "선택 나가기 · Esc", "title": "공유할 스킬 선택", "selectAll": "가능한 {{count}}개 모두 선택", "clear": "지우기", "deleteTitle": "삭제할 스킬 선택" },
+ "SkillsSelectionHeader": {
+ "exit": "선택 나가기",
+ "exitTooltip": "선택 나가기 · Esc",
+ "title": "공유할 스킬 선택",
+ "selectAll": "가능한 {{count}}개 모두 선택",
+ "clear": "지우기",
+ "deleteTitle": "삭제할 스킬 선택"
+ },
"SkillDetailDialog": { "agents": "에이전트", "updated": "업데이트됨", "copy": "복사" },
"SkillFreshnessNudge": {
"titleOne": "설치된 Orca 스킬이 오래되었습니다",
@@ -14392,6 +14460,13 @@
"deleteDescription": "“{{name}}”은(는) 공개 링크에서 더 이상 사용할 수 없습니다.",
"delete": "삭제"
}
+ },
+ "ComposerParentWorktreePicker": {
+ "label": "상위 워크트리",
+ "noParent": "상위 워크트리 없음",
+ "description": "이 워크스페이스를 사이드바의 다른 워크스페이스 아래에 중첩합니다. 기준 브랜치는 변경되지 않습니다.",
+ "searchPlaceholder": "워크스페이스 검색...",
+ "noMatches": "일치하는 항목이 없습니다."
}
},
"i18n": {
diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json
index 6b5cae8fc24..0e55995d3d9 100644
--- a/src/renderer/src/i18n/locales/zh.json
+++ b/src/renderer/src/i18n/locales/zh.json
@@ -266,7 +266,10 @@
"3b57982bf6": "删除工作区\"{{value1}}\"后,Git 无法安全删除分支\"{{value0}}\",因此 Orca 将其保留以避免丢失本地提交。",
"81f13f48d2": "删除工作树\"{{value1}}\"后,Git 无法安全删除分支\"{{value0}}\",因此 Orca 将其保留以避免丢失本地提交。",
"runtimeScopeForbiddenTitle": "此连接具有受限(移动端)访问权限",
- "runtimeScopeForbiddenDescription": "移动端范围的配对无法使用工作区。请通过设置 → 远程 Orca 服务器 → 分享此 Orca 服务器中的浏览器访问链接重新连接。"
+ "runtimeScopeForbiddenDescription": "移动端范围的配对无法使用工作区。请通过设置 → 远程 Orca 服务器 → 分享此 Orca 服务器中的浏览器访问链接重新连接。",
+ "createdWithoutParentNesting": "已创建,但未嵌套在“{{value0}}”下",
+ "createdWithoutParentNestingUnnamed": "已创建,但未嵌套在所选父工作树下",
+ "createdWithoutParentNestingDetail": "父工作区已不可用。您可以从工作区菜单中重新设置。"
},
"repos": {
"2975400634": "请更新 Orca 服务器,以便在此运行时打开非 Git 文件夹。",
@@ -3889,17 +3892,90 @@
},
"skills": {
"SkillsPage": {
- "cb142070b4": "刷新", "a68dee6a32": "搜索技能", "f43ad6edf3": "技能", "ea72d6185b": "无法扫描技能", "dc4c3328ee": "显示文件", "9963dff6d3": "未找到说明。", "995fde8337": "无法显示技能文件", "4acd6d68ec": "未找到技能", "6a62a0168c": "无匹配项", "08a321a984": "没有技能匹配当前搜索和筛选条件。", "cd7893fbc1": "正在扫描技能", "35b9a724a0": "可用", "c13b82793c": "管理安装", "aee7b99cc6": "从链接安装", "filterProvider": "按 Agent 筛选", "filterSource": "按来源筛选", "allSources": "全部", "clearFilters": "清除筛选", "closeSkills": "关闭技能", "closeTooltip": "关闭 · Esc", "moreActions": "更多操作", "sharedLinks": "共享链接", "emptyCopy": "扫描的技能文件夹为空。请安装共享包,或添加技能后刷新。", "retry": "重试", "remoteShareNotice": "这些技能位于 {{host}}。请在该机器上打开“技能”以进行共享。", "viewSwitch": "显示", "searchLinks": "搜索链接", "deleteSkills": "删除技能…"
+ "cb142070b4": "刷新",
+ "a68dee6a32": "搜索技能",
+ "f43ad6edf3": "技能",
+ "ea72d6185b": "无法扫描技能",
+ "dc4c3328ee": "显示文件",
+ "9963dff6d3": "未找到说明。",
+ "995fde8337": "无法显示技能文件",
+ "4acd6d68ec": "未找到技能",
+ "6a62a0168c": "无匹配项",
+ "08a321a984": "没有技能匹配当前搜索和筛选条件。",
+ "cd7893fbc1": "正在扫描技能",
+ "35b9a724a0": "可用",
+ "c13b82793c": "管理安装",
+ "aee7b99cc6": "从链接安装",
+ "filterProvider": "按 Agent 筛选",
+ "filterSource": "按来源筛选",
+ "allSources": "全部",
+ "clearFilters": "清除筛选",
+ "closeSkills": "关闭技能",
+ "closeTooltip": "关闭 · Esc",
+ "moreActions": "更多操作",
+ "sharedLinks": "共享链接",
+ "emptyCopy": "扫描的技能文件夹为空。请安装共享包,或添加技能后刷新。",
+ "retry": "重试",
+ "remoteShareNotice": "这些技能位于 {{host}}。请在该机器上打开“技能”以进行共享。",
+ "viewSwitch": "显示",
+ "searchLinks": "搜索链接",
+ "deleteSkills": "删除技能…"
},
"SkillShareSelectionControls": { "01c5a15e02": "共享技能" },
- "SkillRow": { "updatedUnknown": "无日期", "pathCopied": "路径已复制", "copyPath": "复制路径", "detailPath": "路径", "skillActions": "{{value0}} 的操作", "viewDetails": "查看详情", "deleteSkill": "删除…" },
+ "SkillRow": {
+ "updatedUnknown": "无日期",
+ "pathCopied": "路径已复制",
+ "copyPath": "复制路径",
+ "detailPath": "路径",
+ "skillActions": "{{value0}} 的操作",
+ "viewDetails": "查看详情",
+ "deleteSkill": "删除…"
+ },
"SkillsList": { "listLabel": "技能" },
- "sourceStatus": { "missing": "未找到文件夹", "remoteRepo": "远程仓库 — 未扫描", "unavailable": "未扫描" },
+ "sourceStatus": {
+ "missing": "未找到文件夹",
+ "remoteRepo": "远程仓库 — 未扫描",
+ "unavailable": "未扫描"
+ },
"sources": { "heading": "技能文件夹" },
- "sourceKind": { "home": "主目录", "workspace": "工作区", "bundled": "内置", "plugin": "插件" },
- "count": { "skillOne": "{{count}} 个技能", "skillOther": "{{count}} 个技能", "sourceOne": "{{count}} 个来源", "sourceOther": "{{count}} 个来源", "fileOne": "{{count}} 个文件", "fileOther": "{{count}} 个文件", "resultOne": "{{count}} 个结果", "resultOther": "{{count}} 个结果", "selected": "已选择 {{count}} 个", "shareOne": "共享 {{count}} 个技能", "shareOther": "共享 {{count}} 个技能", "linkOne": "{{count}} 个链接", "linkOther": "{{count}} 个链接", "deleteOne": "删除 {{count}} 个技能", "deleteOther": "删除 {{count}} 个技能", "deletedOne": "已删除 {{count}} 个技能", "deletedOther": "已删除 {{count}} 个技能", "deleteFolderOne": "{{count}} 个文件夹", "deleteFolderOther": "{{count}} 个文件夹", "deleteLinkOne": "{{count}} 个链接", "deleteLinkOther": "{{count}} 个链接" },
+ "sourceKind": {
+ "home": "主目录",
+ "workspace": "工作区",
+ "bundled": "内置",
+ "plugin": "插件"
+ },
+ "count": {
+ "skillOne": "{{count}} 个技能",
+ "skillOther": "{{count}} 个技能",
+ "sourceOne": "{{count}} 个来源",
+ "sourceOther": "{{count}} 个来源",
+ "fileOne": "{{count}} 个文件",
+ "fileOther": "{{count}} 个文件",
+ "resultOne": "{{count}} 个结果",
+ "resultOther": "{{count}} 个结果",
+ "selected": "已选择 {{count}} 个",
+ "shareOne": "共享 {{count}} 个技能",
+ "shareOther": "共享 {{count}} 个技能",
+ "linkOne": "{{count}} 个链接",
+ "linkOther": "{{count}} 个链接",
+ "deleteOne": "删除 {{count}} 个技能",
+ "deleteOther": "删除 {{count}} 个技能",
+ "deletedOne": "已删除 {{count}} 个技能",
+ "deletedOther": "已删除 {{count}} 个技能",
+ "deleteFolderOne": "{{count}} 个文件夹",
+ "deleteFolderOther": "{{count}} 个文件夹",
+ "deleteLinkOne": "{{count}} 个链接",
+ "deleteLinkOther": "{{count}} 个链接"
+ },
"filter": { "allAgents": "所有 Agent", "sharedAgent": "共享 (.agents)" },
- "SkillsSelectionHeader": { "exit": "退出选择", "exitTooltip": "退出选择 · Esc", "title": "选择要共享的技能", "selectAll": "选择全部 {{count}} 个符合条件的项目", "clear": "清除", "deleteTitle": "选择要删除的技能" },
+ "SkillsSelectionHeader": {
+ "exit": "退出选择",
+ "exitTooltip": "退出选择 · Esc",
+ "title": "选择要共享的技能",
+ "selectAll": "选择全部 {{count}} 个符合条件的项目",
+ "clear": "清除",
+ "deleteTitle": "选择要删除的技能"
+ },
"SkillDetailDialog": { "agents": "Agent", "updated": "已更新", "copy": "复制" },
"SkillFreshnessNudge": {
"titleOne": "已安装的 Orca 技能已过期",
@@ -14408,6 +14484,13 @@
"deleteDescription": "“{{name}}” 将无法再通过其公共链接访问。",
"delete": "删除"
}
+ },
+ "ComposerParentWorktreePicker": {
+ "label": "父工作树",
+ "noParent": "无父工作树",
+ "description": "在侧边栏中将此工作区嵌套到另一个工作区下。不会更改基础分支。",
+ "searchPlaceholder": "搜索工作区...",
+ "noMatches": "无匹配项。"
}
},
"i18n": {