fix(workspaces): honor legacy display-name provenance

This commit is contained in:
Jinwoo-H
2026-08-31 16:44:59 -04:00
parent 406a8c6972
commit 174ca42726
10 changed files with 152 additions and 13 deletions
+41 -2
View File
@@ -55,7 +55,7 @@ async function flush(): Promise<void> {
// cutover). Records every call so tests can assert on the clientMutationId.
function scriptedClient(
outcomes: Array<
| { id: string }
| { id: string; displayName?: string }
| { errorMessage: string }
// takesMs models how long the ambiguity took to SURFACE — a clean close is
// instant, a half-open socket waits out the liveness watchdog or the timeout.
@@ -103,7 +103,12 @@ function scriptedClient(
return {
id: '1',
ok: true,
result: { worktree: { id: outcome.id } },
result: {
worktree: {
id: outcome.id,
...(outcome.displayName !== undefined ? { displayName: outcome.displayName } : {})
}
},
_meta: { runtimeId: 'r' }
}
}
@@ -227,6 +232,40 @@ describe('createWorktreeWithNameRetry', () => {
expect(attempts[1]!.params.name).toBe('topic-2')
})
it('uses the host-selected display name after a collision retry', async () => {
const attempts: Attempt[] = []
const client = scriptedClient(
[{ errorMessage: 'already exists locally' }, { id: 'wt-host-name', displayName: 'topic-3' }],
attempts
)
await expect(
createWorktreeWithNameRetry({
client,
baseName: 'topic',
buildParams: (name) => ({ repo: 'id:r', name }),
worktreeCreateIdempotency: false
})
).resolves.toEqual({ worktreeId: 'wt-host-name', name: 'topic-3' })
})
it('falls back to the client candidate when an older host omits displayName', async () => {
const attempts: Attempt[] = []
const client = scriptedClient(
[{ errorMessage: 'already exists locally' }, { id: 'wt-legacy' }],
attempts
)
await expect(
createWorktreeWithNameRetry({
client,
baseName: 'topic',
buildParams: (name) => ({ repo: 'id:r', name }),
worktreeCreateIdempotency: false
})
).resolves.toEqual({ worktreeId: 'wt-legacy', name: 'topic-2' })
})
it('advances generated retries without nesting suffixes', async () => {
const attempts: Attempt[] = []
const client = scriptedClient(
+11 -2
View File
@@ -82,8 +82,17 @@ export async function createWorktreeWithNameRetry(
: candidateParams
const response = await sendWorktreeCreateResilient(client, params, worktreeCreateIdempotency)
if (response.ok) {
const result = (response as RpcSuccess).result as { worktree: { id: string } }
return { worktreeId: result.worktree.id, name: candidateName }
const result = (response as RpcSuccess).result as {
worktree: { id: string; displayName?: string }
}
const authoritativeName = result.worktree.displayName
return {
worktreeId: result.worktree.id,
name:
typeof authoritativeName === 'string' && authoritativeName.trim()
? authoritativeName
: candidateName
}
}
lastError = response.error.message
if (!isRetryableWorktreeCreateConflict(lastError ?? '')) {
+8 -3
View File
@@ -44,12 +44,17 @@ export function resolveWorktreeCreateDisplayNameRequest(
input: string | undefined,
kind: DisplayNameKind,
fallbackName: string,
cliCreated: boolean
cliCreated: boolean,
nameWasGenerated = false
): { value: string | undefined; kind: DisplayNameKind } {
// The CLI name is always an explicit command argument; its marker wins over a
// missing or malformed kind so a future client cannot make it auto-managed.
const effectiveKind = cliCreated ? 'user' : kind
const effectiveInput = input ?? (cliCreated ? fallbackName : undefined)
// Legacy clients omitted displayNameKind: an explicit displayName was the artifact-title
// contract, while a name-only request was user-entered unless marked as generated.
const effectiveKind = cliCreated
? 'user'
: (kind ?? (input !== undefined || nameWasGenerated ? 'generated' : 'user'))
const effectiveInput = input ?? (effectiveKind === 'user' ? fallbackName : undefined)
return {
value: resolveWorktreeCreateDisplayName(effectiveInput, effectiveKind),
kind: effectiveKind
+30
View File
@@ -145,6 +145,36 @@ describe('worktree create display-name provenance', () => {
})
})
it('recovers a legacy name-only user create without CLI provenance', () => {
expect(resolveWorktreeCreateDisplayNameRequest(undefined, undefined, 'feature', false)).toEqual(
{
value: 'feature',
kind: 'user'
}
)
expect(
resolveWorktreeCreateDisplayNameMeta('feature', 'feature', 'user', {
requestedName: 'feature',
sanitizedName: 'feature'
})
).toEqual({ displayName: 'feature', displayNameIsPinned: true })
})
it('keeps a legacy generated name automatic when nameWasGenerated is set', () => {
expect(
resolveWorktreeCreateDisplayNameRequest(undefined, undefined, 'nautilus', false, true)
).toEqual({
value: undefined,
kind: 'generated'
})
})
it('keeps a legacy artifact display name generated when its kind is absent', () => {
expect(
resolveWorktreeCreateDisplayNameRequest('Issue title', undefined, 'feature', false)
).toEqual({ value: 'Issue title', kind: 'generated' })
})
it('treats a CLI name as intentional even if a caller supplies generated provenance', () => {
expect(
resolveWorktreeCreateDisplayNameRequest('Agent label', 'generated', 'feature', true)
+4 -2
View File
@@ -1550,7 +1550,8 @@ export async function createRemoteWorktree(
args.displayName,
args.displayNameKind,
args.name,
args.cliProvenance?.kind === 'created-by-cli'
args.cliProvenance?.kind === 'created-by-cli',
args.nameWasGenerated === true
)
const requestedDisplayName = displayNameRequest.value
@@ -2030,7 +2031,8 @@ export async function createLocalWorktree(
args.displayName,
args.displayNameKind,
args.name,
args.cliProvenance?.kind === 'created-by-cli'
args.cliProvenance?.kind === 'created-by-cli',
args.nameWasGenerated === true
)
const requestedDisplayName = displayNameRequest.value
// Why: explicit branches and non-username prefix modes never consume this; skipping the probe preserves the exact generated branch name.
@@ -333,6 +333,29 @@ describe('registerWorktreeHandlers', () => {
})
})
it('pins a legacy name-only user create when the branch matches', async () => {
listWorktreesMock.mockResolvedValue([
{
path: '/workspace/feature',
head: 'abc123',
branch: 'feature',
isBare: false,
isMainWorktree: false
}
])
store.setWorktreeMeta.mockImplementation((_worktreeId, meta) => meta)
await handlers['worktrees:create'](null, {
repoId: 'repo-1',
name: 'feature'
})
expect(store.setWorktreeMeta).toHaveBeenCalledWith(
'repo-1::/workspace/feature',
expect.objectContaining({ displayName: 'feature', displayNameIsPinned: true })
)
})
it('persists linked issue and PR metadata during local create', async () => {
listWorktreesMock.mockResolvedValue([
{
@@ -19,7 +19,8 @@ export function createFolderWorkspace(
args.displayName,
args.displayNameKind,
args.name,
args.cliProvenance?.kind === 'created-by-cli'
args.cliProvenance?.kind === 'created-by-cli',
args.nameWasGenerated === true
)
const meta = store.setWorktreeMeta(worktreeId, {
instanceId,
+2 -1
View File
@@ -173,7 +173,8 @@ function buildProvisionedRootMeta(
args.displayName,
args.displayNameKind,
args.name,
false
false,
args.nameWasGenerated === true
)
const displayNameMeta = resolveWorktreeCreateDisplayNameMeta(
displayNameRequest.value,
+4 -2
View File
@@ -27054,7 +27054,8 @@ export class OrcaRuntimeService {
args.displayName,
args.displayNameKind,
args.name,
args.cliProvenance?.kind === 'created-by-cli'
args.cliProvenance?.kind === 'created-by-cli',
args.nameWasGenerated === true
)
const resolvedFolderDisplayName = displayNameRequest.value
const meta = this.store.setWorktreeMeta(worktreeId, {
@@ -27259,7 +27260,8 @@ export class OrcaRuntimeService {
args.displayName,
args.displayNameKind,
args.name,
args.cliProvenance?.kind === 'created-by-cli'
args.cliProvenance?.kind === 'created-by-cli',
args.nameWasGenerated === true
)
const requestedDisplayName = displayNameRequest.value
const sanitizedName = sanitizeWorktreeName(args.name)
@@ -147,6 +147,33 @@ describe('worktree RPC methods', () => {
})
})
it('keeps the legacy CLI name-only create shape explicit at the host boundary', async () => {
const runtime = {
getRuntimeId: () => 'test-runtime',
dedupeWorktreeCreate: passthroughDedupe,
showRepo: vi.fn().mockResolvedValue(repo),
createManagedWorktree: vi.fn().mockResolvedValue({ worktree: { id: 'wt-cli' } })
} as unknown as OrcaRuntimeService
const dispatcher = new RpcDispatcher({ runtime, methods: WORKTREE_METHODS })
await dispatcher.dispatch(
makeRequest('worktree.create', {
repo: 'repo-1',
name: 'feature',
cliProvenanceRequest: {}
})
)
expect(runtime.createManagedWorktree).toHaveBeenCalledWith(
expect.objectContaining({
name: 'feature',
displayName: undefined,
displayNameKind: undefined,
cliProvenance: expect.objectContaining({ kind: 'created-by-cli' })
})
)
})
it('mints automation provenance from a valid dispatch request on worktree creation', async () => {
const dispatchToken = createAutomationDispatchToken('automation-1', 'run-1')
const runtime = {