Fix canonical worktree identity CI failures

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-23 23:49:29 -07:00
co-authored by Orca
parent 4a484e856b
commit 8b37d052bd
8 changed files with 214 additions and 100 deletions
+6 -1
View File
@@ -3,6 +3,7 @@ single setup-free pure-logic module, and splitting them would make the related
edge cases harder to audit together. */
import { posix, resolve } from 'path'
import { describe, expect, it } from 'vitest'
import { makeWorktreeKey } from '../../shared/worktree-id'
import {
sanitizeWorktreeName,
sanitizeWorktreeDisplayName,
@@ -372,7 +373,11 @@ describe('mergeWorktree', () => {
}
const result = mergeWorktree('repo1', baseGit, meta)
expect(result).toEqual({
id: 'repo1::/workspaces/feature',
id: makeWorktreeKey({
hostId: 'ssh:openclaw-2',
repoId: 'repo1',
path: '/workspaces/feature'
}),
repoId: 'repo1',
path: '/workspaces/feature',
head: 'abc123',
+19 -6
View File
@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { makeWorktreeKey } from '../../shared/worktree-id'
const {
handleMock,
@@ -147,7 +148,8 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
getSettings: vi.fn(),
getWorktreeMeta: vi.fn(),
setWorktreeMeta: vi.fn(),
removeWorktreeMeta: vi.fn()
removeWorktreeMeta: vi.fn(),
migrateWorktreeIdentity: vi.fn()
}
beforeEach(() => {
@@ -187,6 +189,7 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
store.getWorktreeMeta.mockReset()
store.setWorktreeMeta.mockReset()
store.removeWorktreeMeta.mockReset()
store.migrateWorktreeIdentity.mockReset()
for (const key of Object.keys(handlers)) {
delete handlers[key]
@@ -260,6 +263,11 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
})
it('accepts a newly created Windows worktree when git lists the same path with different separators', async () => {
const worktreeId = makeWorktreeKey({
hostId: 'local',
repoId: 'repo-1',
path: 'C:/workspaces/improve-dashboard'
})
listWorktreesMock.mockResolvedValue([
{
path: 'C:/workspaces/improve-dashboard',
@@ -283,14 +291,14 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
false
)
expect(store.setWorktreeMeta).toHaveBeenCalledWith(
'repo-1::C:/workspaces/improve-dashboard',
worktreeId,
expect.objectContaining({
lastActivityAt: expect.any(Number)
})
)
expect(result).toMatchObject({
worktree: expect.objectContaining({
id: 'repo-1::C:/workspaces/improve-dashboard',
id: worktreeId,
path: 'C:/workspaces/improve-dashboard',
branch: 'refs/heads/improve-dashboard'
})
@@ -298,6 +306,11 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
})
it('preserves create-time metadata on the next list when Windows path formatting differs', async () => {
const worktreeId = makeWorktreeKey({
hostId: 'local',
repoId: 'repo-1',
path: 'C:/workspaces/improve-dashboard'
})
const worktreeEntry = {
path: 'C:/workspaces/improve-dashboard',
head: 'abc123',
@@ -314,8 +327,8 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
linkedIssue: 123,
linkedPR: 456
})
store.getWorktreeMeta.mockImplementation((worktreeId: string) =>
worktreeId === 'repo-1::C:/workspaces/improve-dashboard'
store.getWorktreeMeta.mockImplementation((id: string) =>
id === worktreeId
? {
lastActivityAt: 123,
displayName: 'Improve Dashboard',
@@ -337,7 +350,7 @@ describe('registerWorktreeHandlers – Windows path handling', () => {
expect(listed).toMatchObject([
{
id: 'repo-1::C:/workspaces/improve-dashboard',
id: worktreeId,
displayName: 'Improve Dashboard',
linkedIssue: 123,
linkedPR: 456,
+105 -58
View File
@@ -712,12 +712,16 @@ const TEST_WINDOW_ID = 1
const TEST_REPO_ID = 'repo-1'
const TEST_REPO_PATH = '/tmp/repo'
const TEST_WORKTREE_PATH = '/tmp/worktree-a'
const TEST_WORKTREE_ID = `${TEST_REPO_ID}::${TEST_WORKTREE_PATH}`
const TEST_CANONICAL_WORKTREE_ID = makeWorktreeKey({
hostId: 'local',
repoId: TEST_REPO_ID,
path: TEST_WORKTREE_PATH
})
const TEST_WORKTREE_ID = TEST_CANONICAL_WORKTREE_ID
const makeLocalTestWorktreeId = (path: string): string =>
makeWorktreeKey({ hostId: 'local', repoId: TEST_REPO_ID, path })
const makeSshTestWorktreeId = (repoId: string, connectionId: string, path: string): string =>
makeWorktreeKey({ hostId: `ssh:${connectionId}`, repoId, path })
const TEST_FOLDER_PROJECT_GROUP_ID = 'folder-project-group-1'
const TEST_FOLDER_WORKSPACE_ID = 'folder-workspace-1'
const TEST_FOLDER_WORKSPACE_KEY = `folder:${TEST_FOLDER_WORKSPACE_ID}`
@@ -1350,7 +1354,7 @@ describe('OrcaRuntimeService', () => {
tabs: [
{
tabId: 'tab-1',
worktreeId: 'repo-1::/tmp/worktree-a',
worktreeId: TEST_WORKTREE_ID,
title: 'Claude',
activeLeafId: 'pane:1',
layout: null
@@ -1359,7 +1363,7 @@ describe('OrcaRuntimeService', () => {
leaves: [
{
tabId: 'tab-1',
worktreeId: 'repo-1::/tmp/worktree-a',
worktreeId: TEST_WORKTREE_ID,
leafId: 'pane:1',
paneRuntimeId: 1,
ptyId: 'pty-1'
@@ -1371,7 +1375,7 @@ describe('OrcaRuntimeService', () => {
const terminals = await runtime.listTerminals('branch:feature/foo')
expect(terminals.terminals).toHaveLength(1)
expect(terminals.terminals[0]).toMatchObject({
worktreeId: 'repo-1::/tmp/worktree-a',
worktreeId: TEST_WORKTREE_ID,
branch: 'feature/foo',
ptyId: 'pty-1',
title: 'Claude',
@@ -1603,7 +1607,8 @@ describe('OrcaRuntimeService', () => {
})
it('matches explicit-id cwd PTYs for folder workspace instance IDs', async () => {
const folderWorktreeId = `${TEST_REPO_ID}::${TEST_FOLDER_WORKSPACE_PATH}${FOLDER_WORKSPACE_INSTANCE_SEPARATOR}11111111-1111-4111-8111-111111111111`
const folderWorktreeRootId = makeLocalTestWorktreeId(TEST_FOLDER_WORKSPACE_PATH)
const folderWorktreeId = `${folderWorktreeRootId}${FOLDER_WORKSPACE_INSTANCE_SEPARATOR}11111111-1111-4111-8111-111111111111`
vi.mocked(listWorktrees).mockClear()
vi.mocked(listWorktrees).mockRejectedValue(
new Error('folder explicit-id fallback should not rescan worktrees')
@@ -1621,7 +1626,9 @@ describe('OrcaRuntimeService', () => {
const terminals = await runtime.listTerminals(`id:${folderWorktreeId}`)
expect(listWorktrees).not.toHaveBeenCalled()
expect(terminals.terminals.map((terminal) => terminal.worktreeId)).toEqual([folderWorktreeId])
expect(terminals.terminals.map((terminal) => terminal.worktreeId)).toEqual([
folderWorktreeRootId
])
expect(terminals.terminals[0]?.worktreePath).toBe(TEST_FOLDER_WORKSPACE_PATH)
})
@@ -1916,8 +1923,18 @@ describe('OrcaRuntimeService', () => {
addedAt: 1,
kind: 'folder' as const
}
const rootWorktreeId = 'folder-repo::/workspace/folder'
const rootPriorWorktreeIds = ['folder-repo::/workspace/old-folder']
const rootWorktreeId = makeWorktreeKey({
hostId: 'local',
repoId: 'folder-repo',
path: '/workspace/folder'
})
const rootPriorWorktreeIds = [
makeWorktreeKey({
hostId: 'local',
repoId: 'folder-repo',
path: '/workspace/old-folder'
})
]
const metaById: Record<string, WorktreeMeta> = {
[rootWorktreeId]: makeWorktreeMeta({
instanceId: 'root-instance',
@@ -1958,7 +1975,9 @@ describe('OrcaRuntimeService', () => {
expect(addWorktreeMock).not.toHaveBeenCalled()
expect(result.worktree).toEqual(
expect.objectContaining({
id: expect.stringMatching(/^folder-repo::\/workspace\/folder::workspace:[0-9a-f-]{36}$/),
id: expect.stringMatching(
/^orca-worktree:\/\/v1\?hostId=local&repoId=folder-repo&path=%2Fworkspace%2Ffolder::workspace:[0-9a-f-]{36}$/
),
repoId: 'folder-repo',
path: '/workspace/folder',
displayName: 'folder-session',
@@ -1998,9 +2017,9 @@ describe('OrcaRuntimeService', () => {
id: result.worktree.id,
comment: 'note'
})
await expect(
runtime.removeManagedWorktree('id:folder-repo::/workspace/folder')
).rejects.toThrow('Cannot delete the project root workspace')
await expect(runtime.removeManagedWorktree(`id:${rootWorktreeId}`)).rejects.toThrow(
'Cannot delete the project root workspace'
)
deletedWorktreeId = result.worktree.id
await expect(runtime.removeManagedWorktree(`id:${result.worktree.id}`)).resolves.toEqual({})
expect(localProvider.shutdown).toHaveBeenCalledWith(`${result.worktree.id}@@pty-1`, {
@@ -2949,7 +2968,7 @@ describe('OrcaRuntimeService', () => {
isBare: false,
isMainWorktree: false
}
const childId = `${TEST_REPO_ID}::${created.path}`
const childId = makeLocalTestWorktreeId(created.path)
const metaById: Record<string, WorktreeMeta> = {}
const workspaceLineageByChildKey: Record<string, WorkspaceLineage> = {}
const runtimeStore = {
@@ -5037,11 +5056,11 @@ describe('OrcaRuntimeService', () => {
expect(terminals.terminals).toEqual(
expect.arrayContaining([
expect.objectContaining({
worktreeId: `${TEST_REPO_ID}::C:\\Repo`,
worktreeId: makeLocalTestWorktreeId('C:\\Repo'),
worktreePath: 'C:\\Repo'
}),
expect.objectContaining({
worktreeId: `${TEST_REPO_ID}:://Server/Share/Repo`,
worktreeId: makeLocalTestWorktreeId('//Server/Share/Repo'),
worktreePath: '//Server/Share/Repo'
})
])
@@ -11774,6 +11793,7 @@ describe('OrcaRuntimeService', () => {
})
it('spawns fresh headless SSH mobile session terminals instead of reattaching synthetic local ids', async () => {
const sshWorktreeId = makeSshTestWorktreeId(TEST_REPO_ID, 'ssh-1', TEST_WORKTREE_PATH)
const remoteRepo = { ...store.getRepo(TEST_REPO_ID)!, connectionId: 'ssh-1' }
const remoteStore = {
...store,
@@ -11790,12 +11810,12 @@ describe('OrcaRuntimeService', () => {
})
runtime.syncWindowGraph(0, { tabs: [], leaves: [] })
await runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`)
await runtime.createMobileSessionTerminal(`id:${sshWorktreeId}`)
expect(spawn).toHaveBeenCalledWith(
expect.objectContaining({
connectionId: 'ssh-1',
worktreeId: TEST_WORKTREE_ID,
worktreeId: sshWorktreeId,
persistHostSessionBinding: true
})
)
@@ -13192,14 +13212,15 @@ describe('OrcaRuntimeService', () => {
})
it('reattaches hydrated SSH headless terminals with the persisted relay identity', async () => {
const sshWorktreeId = makeSshTestWorktreeId(TEST_REPO_ID, 'ssh-1', TEST_WORKTREE_PATH)
const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
tabsByWorktree: {
[TEST_WORKTREE_ID]: [
[sshWorktreeId]: [
{
id: 'host-tab',
ptyId: 'ssh:ssh-1@@relay-pty',
worktreeId: TEST_WORKTREE_ID,
worktreeId: sshWorktreeId,
title: 'Remote Terminal',
customTitle: null,
color: null,
@@ -13232,7 +13253,7 @@ describe('OrcaRuntimeService', () => {
})
runtime.syncWindowGraph(0, { tabs: [], leaves: [] })
await runtime.activateMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'host-tab')
await runtime.activateMobileSessionTab(`id:${sshWorktreeId}`, 'host-tab')
expect(spawn).toHaveBeenCalledWith(
expect.objectContaining({
@@ -13246,15 +13267,16 @@ describe('OrcaRuntimeService', () => {
})
it('spawns fresh after an expired hydrated SSH headless reattach clears persistence', async () => {
const sshWorktreeId = makeSshTestWorktreeId(TEST_REPO_ID, 'ssh-1', TEST_WORKTREE_PATH)
const stalePtyId = 'ssh:ssh-1@@relay-pty'
const { runtimeStore, getSession } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
tabsByWorktree: {
[TEST_WORKTREE_ID]: [
[sshWorktreeId]: [
{
id: 'host-tab',
ptyId: stalePtyId,
worktreeId: TEST_WORKTREE_ID,
worktreeId: sshWorktreeId,
title: 'Remote Terminal',
customTitle: null,
color: null,
@@ -13282,7 +13304,7 @@ describe('OrcaRuntimeService', () => {
...session,
tabsByWorktree: {
...session.tabsByWorktree,
[TEST_WORKTREE_ID]: session.tabsByWorktree[TEST_WORKTREE_ID].map((tab) =>
[sshWorktreeId]: session.tabsByWorktree[sshWorktreeId].map((tab) =>
tab.id === 'host-tab' ? { ...tab, ptyId: null } : tab
)
},
@@ -13308,9 +13330,9 @@ describe('OrcaRuntimeService', () => {
runtime.syncWindowGraph(0, { tabs: [], leaves: [] })
await expect(
runtime.activateMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'host-tab')
runtime.activateMobileSessionTab(`id:${sshWorktreeId}`, 'host-tab')
).rejects.toThrow('SSH session expired')
await runtime.activateMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'host-tab')
await runtime.activateMobileSessionTab(`id:${sshWorktreeId}`, 'host-tab')
expect(spawn).toHaveBeenNthCalledWith(
1,
@@ -13476,14 +13498,15 @@ describe('OrcaRuntimeService', () => {
})
it('spawns fresh SSH terminals when hydrated persistence has no relay identity', async () => {
const sshWorktreeId = makeSshTestWorktreeId(TEST_REPO_ID, 'ssh-1', TEST_WORKTREE_PATH)
const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(
makeWorkspaceSessionWithHeadlessTerminal({
tabsByWorktree: {
[TEST_WORKTREE_ID]: [
[sshWorktreeId]: [
{
id: 'host-tab',
ptyId: null,
worktreeId: TEST_WORKTREE_ID,
worktreeId: sshWorktreeId,
title: 'Remote Terminal',
customTitle: null,
color: null,
@@ -13514,7 +13537,7 @@ describe('OrcaRuntimeService', () => {
})
runtime.syncWindowGraph(0, { tabs: [], leaves: [] })
await runtime.activateMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'host-tab')
await runtime.activateMobileSessionTab(`id:${sshWorktreeId}`, 'host-tab')
expect(spawn).toHaveBeenCalledWith(
expect.objectContaining({
@@ -15871,8 +15894,16 @@ describe('OrcaRuntimeService', () => {
addedAt: 1,
connectionId: 'ssh-1'
}
const childId = `${remoteRepo.id}::/home/user/repo-child`
const parentId = `${remoteRepo.id}::/home/user/repo-parent`
const childId = makeSshTestWorktreeId(
remoteRepo.id,
remoteRepo.connectionId,
'/home/user/repo-child'
)
const parentId = makeSshTestWorktreeId(
remoteRepo.id,
remoteRepo.connectionId,
'/home/user/repo-parent'
)
const canonicalChildId = makeWorktreeKey({
hostId: 'ssh:ssh-1',
repoId: remoteRepo.id,
@@ -15946,8 +15977,16 @@ describe('OrcaRuntimeService', () => {
addedAt: 1,
connectionId: 'ssh-1'
}
const childId = `${remoteRepo.id}::/home/user/repo-child`
const parentId = `${remoteRepo.id}::/home/user/repo-parent`
const childId = makeSshTestWorktreeId(
remoteRepo.id,
remoteRepo.connectionId,
'/home/user/repo-child'
)
const parentId = makeSshTestWorktreeId(
remoteRepo.id,
remoteRepo.connectionId,
'/home/user/repo-parent'
)
const metaById: Record<string, WorktreeMeta> = {
[childId]: makeWorktreeMeta({ instanceId: 'child-instance' }),
[parentId]: makeWorktreeMeta({ instanceId: 'parent-instance' })
@@ -16185,8 +16224,8 @@ describe('OrcaRuntimeService', () => {
it('keeps workspace lineage in sync when manually reparenting a worktree', async () => {
const parentPath = '/tmp/worktree-parent'
const childPath = '/tmp/worktree-child'
const parentId = `${TEST_REPO_ID}::${parentPath}`
const childId = `${TEST_REPO_ID}::${childPath}`
const parentId = makeLocalTestWorktreeId(parentPath)
const childId = makeLocalTestWorktreeId(childPath)
const canonicalParentId = makeWorktreeKey({
hostId: 'local',
repoId: TEST_REPO_ID,
@@ -16258,7 +16297,7 @@ describe('OrcaRuntimeService', () => {
it('clears workspace lineage when manually removing a parent', async () => {
const childPath = '/tmp/worktree-child'
const childId = `${TEST_REPO_ID}::${childPath}`
const childId = makeLocalTestWorktreeId(childPath)
const metaById: Record<string, WorktreeMeta> = {
[childId]: makeWorktreeMeta({ instanceId: 'child-instance' })
}
@@ -16323,8 +16362,8 @@ describe('OrcaRuntimeService', () => {
it('ignores stale instance-mismatched lineage when validating manual cycle repairs', async () => {
const parentPath = '/tmp/worktree-a'
const childPath = '/tmp/worktree-b'
const parentId = `${TEST_REPO_ID}::${parentPath}`
const childId = `${TEST_REPO_ID}::${childPath}`
const parentId = makeLocalTestWorktreeId(parentPath)
const childId = makeLocalTestWorktreeId(childPath)
const metaById: Record<string, WorktreeMeta> = {
[parentId]: makeWorktreeMeta({ instanceId: 'new-parent-instance' }),
[childId]: makeWorktreeMeta({ instanceId: 'child-instance' })
@@ -16388,8 +16427,8 @@ describe('OrcaRuntimeService', () => {
it('rejects lineage updates when upgraded metadata is missing a parent instance id', async () => {
const parentPath = '/tmp/worktree-parent'
const childPath = '/tmp/worktree-child'
const parentId = `${TEST_REPO_ID}::${parentPath}`
const childId = `${TEST_REPO_ID}::${childPath}`
const parentId = makeLocalTestWorktreeId(parentPath)
const childId = makeLocalTestWorktreeId(childPath)
const metaById: Record<string, WorktreeMeta> = {
[parentId]: makeWorktreeMeta(),
[childId]: makeWorktreeMeta({ instanceId: 'child-instance' })
@@ -16435,8 +16474,8 @@ describe('OrcaRuntimeService', () => {
it('rotates a missing parent instance during runtime selector scans before same-path reuse', async () => {
const parentPath = '/tmp/worktree-parent'
const childPath = '/tmp/worktree-child'
const parentId = `${TEST_REPO_ID}::${parentPath}`
const childId = `${TEST_REPO_ID}::${childPath}`
const parentId = makeLocalTestWorktreeId(parentPath)
const childId = makeLocalTestWorktreeId(childPath)
const metaById: Record<string, WorktreeMeta> = {
[parentId]: makeWorktreeMeta({ instanceId: 'old-parent-instance' }),
[childId]: makeWorktreeMeta({ instanceId: 'child-instance' })
@@ -16520,8 +16559,8 @@ describe('OrcaRuntimeService', () => {
it('does not prune lineage when a runtime local worktree scan fails', async () => {
const parentPath = '/tmp/worktree-parent'
const childPath = '/tmp/worktree-child'
const parentId = `${TEST_REPO_ID}::${parentPath}`
const childId = `${TEST_REPO_ID}::${childPath}`
const parentId = makeLocalTestWorktreeId(parentPath)
const childId = makeLocalTestWorktreeId(childPath)
const metaById: Record<string, WorktreeMeta> = {
[parentId]: makeWorktreeMeta({ instanceId: 'parent-instance' }),
[childId]: makeWorktreeMeta({ instanceId: 'child-instance' })
@@ -16591,8 +16630,16 @@ describe('OrcaRuntimeService', () => {
addedAt: 1,
connectionId: 'ssh-1'
}
const parentId = `${remoteRepo.id}::/home/user/repo-parent`
const childId = `${remoteRepo.id}::/home/user/repo-child`
const parentId = makeSshTestWorktreeId(
remoteRepo.id,
remoteRepo.connectionId,
'/home/user/repo-parent'
)
const childId = makeSshTestWorktreeId(
remoteRepo.id,
remoteRepo.connectionId,
'/home/user/repo-child'
)
const metaById: Record<string, WorktreeMeta> = {
[parentId]: makeWorktreeMeta({ instanceId: 'parent-instance' }),
[childId]: makeWorktreeMeta({ instanceId: 'child-instance' })
@@ -16735,8 +16782,8 @@ describe('OrcaRuntimeService', () => {
it('keeps valid orchestration lineage when caller terminal context is stale', async () => {
const parentPath = '/tmp/worktree-parent'
const childPath = '/tmp/workspaces/worker-child'
const parentId = `${TEST_REPO_ID}::${parentPath}`
const childId = `${TEST_REPO_ID}::${childPath}`
const parentId = makeLocalTestWorktreeId(parentPath)
const childId = makeLocalTestWorktreeId(childPath)
const metaById: Record<string, WorktreeMeta> = {
[parentId]: makeWorktreeMeta({
instanceId: 'parent-instance',
@@ -16813,8 +16860,8 @@ describe('OrcaRuntimeService', () => {
it('enriches caller-terminal lineage with active orchestration dispatch context', async () => {
const workerPath = '/tmp/worktree-worker'
const childPath = '/tmp/workspaces/worker-child'
const childId = `${TEST_REPO_ID}::${childPath}`
const workerId = `${TEST_REPO_ID}::${workerPath}`
const childId = makeLocalTestWorktreeId(childPath)
const workerId = makeLocalTestWorktreeId(workerPath)
const metaById: Record<string, WorktreeMeta> = {
[TEST_WORKTREE_ID]: makeWorktreeMeta({
instanceId: 'parent-instance',
@@ -17205,8 +17252,8 @@ describe('OrcaRuntimeService', () => {
it('falls back to cwd lineage when the caller terminal handle is stale', async () => {
const parentPath = '/tmp/worktree-parent'
const childPath = '/tmp/workspaces/cwd-child'
const parentId = `${TEST_REPO_ID}::${parentPath}`
const childId = `${TEST_REPO_ID}::${childPath}`
const parentId = makeLocalTestWorktreeId(parentPath)
const childId = makeLocalTestWorktreeId(childPath)
const metaById: Record<string, WorktreeMeta> = {
[parentId]: makeWorktreeMeta({ instanceId: 'parent-instance' })
}
@@ -17312,8 +17359,8 @@ describe('OrcaRuntimeService', () => {
it('infers orchestration lineage from task-id comments when dispatch is completed', async () => {
const workerPath = '/tmp/worktree-worker'
const childPath = '/tmp/workspaces/worker-child'
const childId = `${TEST_REPO_ID}::${childPath}`
const workerId = `${TEST_REPO_ID}::${workerPath}`
const childId = makeLocalTestWorktreeId(childPath)
const workerId = makeLocalTestWorktreeId(workerPath)
const metaById: Record<string, WorktreeMeta> = {
[workerId]: makeWorktreeMeta({
instanceId: 'worker-instance',
@@ -17410,8 +17457,8 @@ describe('OrcaRuntimeService', () => {
it('infers orchestration lineage from task creator when no dispatch context exists', async () => {
const parentPath = '/tmp/worktree-parent'
const childPath = '/tmp/workspaces/parent-child'
const childId = `${TEST_REPO_ID}::${childPath}`
const parentId = `${TEST_REPO_ID}::${parentPath}`
const childId = makeLocalTestWorktreeId(childPath)
const parentId = makeLocalTestWorktreeId(parentPath)
const metaById: Record<string, WorktreeMeta> = {
[parentId]: makeWorktreeMeta({
instanceId: 'parent-instance',
@@ -19991,7 +20038,7 @@ describe('OrcaRuntimeService', () => {
try {
const worktree = await runtime.showManagedWorktree(`path:${duplicatePath}`)
expect(worktree.id).toBe(`${TEST_REPO_ID}::${duplicatePath}`)
expect(worktree.id).toBe(makeLocalTestWorktreeId(duplicatePath))
expect(worktree.path).toBe(duplicatePath)
} finally {
getRepos.mockRestore()
@@ -20690,7 +20737,7 @@ describe('OrcaRuntimeService', () => {
addedAt: 1,
connectionId: 'ssh-missing-fs'
}
const worktreeId = `${repo.id}::${localPath}`
const worktreeId = makeSshTestWorktreeId(repo.id, repo.connectionId, localPath)
const metaById: Record<string, WorktreeMeta> = {
[worktreeId]: makeWorktreeMeta({
orcaCreatedAt: Date.now(),
@@ -21270,7 +21317,7 @@ describe('OrcaRuntimeService', () => {
expect(listed.worktrees).toMatchObject([
{
id: 'repo-1::C:/workspaces/improve-dashboard',
id: makeLocalTestWorktreeId('C:/workspaces/improve-dashboard'),
displayName: 'Improve Dashboard'
}
])
+5 -2
View File
@@ -16685,10 +16685,13 @@ export class OrcaRuntimeService {
store.removeWorktreeLineage(childId)
store.removeWorkspaceLineage?.(worktreeWorkspaceKey(childId))
}
if (
const parentMissingFromLegacyScan =
lineage.parentWorktreeId.startsWith(repoPrefix) &&
!legacyLiveIds.has(lineage.parentWorktreeId)
) {
const parentMissingFromCanonicalScan =
isRuntimeCanonicalWorktreeIdForRepoHost(repo, lineage.parentWorktreeId) &&
!liveIds.has(lineage.parentWorktreeId)
if (parentMissingFromLegacyScan || parentMissingFromCanonicalScan) {
const parentMeta = store.getWorktreeMeta(lineage.parentWorktreeId)
if (!parentMeta || parentMeta.instanceId === lineage.parentWorktreeInstanceId) {
// Why: preserving child lineage powers the repair UI, but a missing
+34 -21
View File
@@ -15,6 +15,7 @@ import { createRuntimeTransportMetadata, OrcaRuntimeRpcServer } from './runtime-
import { parsePairingCode } from '../../shared/pairing'
import { decrypt, deriveSharedKey, encrypt, generateKeyPair } from './rpc/e2ee-crypto'
import { DeviceRegistry } from './device-registry'
import { makeWorktreeKey } from '../../shared/worktree-id'
vi.mock('../git/worktree', () => ({
listWorktrees: vi.fn().mockResolvedValue([
@@ -259,6 +260,25 @@ class FakeWebSocket extends EventEmitter {
}
describe('OrcaRuntimeRpcServer', () => {
const LEGACY_WORKTREE_A_ID = 'repo-1::/tmp/worktree-a'
const WORKTREE_A_ID = makeWorktreeKey({
hostId: 'local',
repoId: 'repo-1',
path: '/tmp/worktree-a'
})
const makeWorktreeMeta = (overrides?: { isUnread?: boolean }) => ({
displayName: 'foo',
comment: '',
linkedIssue: 123,
linkedPR: null,
linkedLinearIssue: null,
isArchived: false,
isUnread: overrides?.isUnread ?? false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0
})
const makeStore = (overrides?: { isUnread?: boolean }) => ({
getRepo: (id: string) =>
makeStore(overrides)
@@ -279,27 +299,20 @@ describe('OrcaRuntimeRpcServer', () => {
...makeStore(overrides).getRepo(id),
...updates
}) as never,
getAllWorktreeMeta: () => ({
'repo-1::/tmp/worktree-a': {
displayName: 'foo',
comment: '',
linkedIssue: 123,
linkedPR: null,
linkedLinearIssue: null,
isArchived: false,
isUnread: overrides?.isUnread ?? false,
isPinned: false,
sortOrder: 0,
lastActivityAt: 0
getAllWorktreeMeta: () => {
const meta = makeWorktreeMeta(overrides)
return {
[LEGACY_WORKTREE_A_ID]: meta,
[WORKTREE_A_ID]: meta
}
}),
},
getWorktreeMeta: (worktreeId: string) =>
worktreeId === 'repo-1::/tmp/worktree-a'
worktreeId === LEGACY_WORKTREE_A_ID || worktreeId === WORKTREE_A_ID
? (makeStore(overrides).getAllWorktreeMeta()[worktreeId] as never)
: undefined,
setWorktreeMeta: (_worktreeId: string, meta: Record<string, unknown>) =>
({
...makeStore(overrides).getAllWorktreeMeta()['repo-1::/tmp/worktree-a'],
...makeStore(overrides).getAllWorktreeMeta()[WORKTREE_A_ID],
...meta
}) as never,
removeWorktreeMeta: () => {},
@@ -2240,7 +2253,7 @@ describe('OrcaRuntimeRpcServer', () => {
const userDataPath = mkdtempSync(join(tmpdir(), 'orca-runtime-rpc-'))
const runtime = new OrcaRuntimeService(makeStore() as never)
const server = new OrcaRuntimeRpcServer({ runtime, userDataPath })
const worktreeId = 'repo-1::/tmp/worktree-a'
const worktreeId = WORKTREE_A_ID
const leftLeaf = '11111111-1111-4111-8111-111111111111'
const topLeaf = '22222222-2222-4222-8222-222222222222'
const bottomLeaf = '33333333-3333-4333-8333-333333333333'
@@ -2507,7 +2520,7 @@ describe('OrcaRuntimeRpcServer', () => {
ok: true,
result: {
terminal: {
worktreeId: 'repo-1::/tmp/worktree-a',
worktreeId: WORKTREE_A_ID,
surface: 'background'
}
}
@@ -2520,7 +2533,7 @@ describe('OrcaRuntimeRpcServer', () => {
authToken,
method: 'session.tabs.list',
params: {
worktree: 'id:repo-1::/tmp/worktree-a'
worktree: `id:${WORKTREE_A_ID}`
}
})
@@ -2609,7 +2622,7 @@ describe('OrcaRuntimeRpcServer', () => {
const metadata = readRuntimeMetadata(userDataPath)
const laptopEndpoint = metadata!.transports[0]!.endpoint
const laptopAuthToken = metadata!.authToken
const worktree = 'id:repo-1::/tmp/worktree-a'
const worktree = `id:${WORKTREE_A_ID}`
const leafId = '11111111-1111-4111-8111-111111111111'
try {
@@ -2757,7 +2770,7 @@ describe('OrcaRuntimeRpcServer', () => {
result: {
worktrees: [
{
worktreeId: 'repo-1::/tmp/worktree-a',
worktreeId: WORKTREE_A_ID,
repoId: 'repo-1',
repo: 'repo',
path: '/tmp/worktree-a',
@@ -2903,7 +2916,7 @@ describe('OrcaRuntimeRpcServer', () => {
tabs: [
{
tabId: 'tab-1',
worktreeId: 'repo-1::/tmp/worktree-a',
worktreeId: WORKTREE_A_ID,
title: 'Terminal 1',
activeLeafId: 'pane:1',
layout: null
+6 -1
View File
@@ -1,4 +1,5 @@
import { describe, expect, it, vi } from 'vitest'
import { makeWorktreeKey } from '../shared/worktree-id'
import { loadKnownUsageWorktreesByRepo } from './usage-worktree-metadata'
describe('loadKnownUsageWorktreesByRepo', () => {
@@ -36,7 +37,11 @@ describe('loadKnownUsageWorktreesByRepo', () => {
'repo-1',
[
{
worktreeId: 'repo-1::/workspace/repo-a',
worktreeId: makeWorktreeKey({
hostId: 'local',
repoId: 'repo-1',
path: '/workspace/repo-a'
}),
path: '/workspace/repo-a',
displayName: 'Repo A'
},
@@ -28,13 +28,13 @@ describe('createWorktreeChangeRefreshQueue', () => {
queue.enqueue({ repoId: 'repo-1' })
expect(handler).toHaveBeenCalledTimes(1)
expect(handler).toHaveBeenCalledWith('repo-1', undefined)
expect(handler).toHaveBeenCalledWith('repo-1', undefined, undefined)
firstRefresh.resolve()
await flushPromises()
expect(handler).toHaveBeenCalledTimes(2)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-1', undefined)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-1', undefined, undefined)
})
it('does not overlap refreshes for the same repo', async () => {
@@ -73,8 +73,8 @@ describe('createWorktreeChangeRefreshQueue', () => {
queue.enqueue({ repoId: 'repo-2' })
expect(handler).toHaveBeenCalledTimes(2)
expect(handler).toHaveBeenNthCalledWith(1, 'repo-1', undefined)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-2', undefined)
expect(handler).toHaveBeenNthCalledWith(1, 'repo-1', undefined, undefined)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-2', undefined, undefined)
repoOneRefresh.resolve()
await flushPromises()
@@ -100,8 +100,8 @@ describe('createWorktreeChangeRefreshQueue', () => {
await flushPromises()
expect(handler).toHaveBeenCalledTimes(3)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-1', renamed)
expect(handler).toHaveBeenNthCalledWith(3, 'repo-1', undefined)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-1', undefined, renamed)
expect(handler).toHaveBeenNthCalledWith(3, 'repo-1', undefined, undefined)
} finally {
consoleError.mockRestore()
}
@@ -116,8 +116,8 @@ describe('createWorktreeChangeRefreshQueue', () => {
queue.enqueue({ repoId: 'repo-1', renamed })
await flushPromises()
expect(handler).toHaveBeenNthCalledWith(1, 'repo-1', undefined)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-1', renamed)
expect(handler).toHaveBeenNthCalledWith(1, 'repo-1', undefined, undefined)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-1', undefined, renamed)
})
it('keeps a plain refresh queued after a rename', async () => {
@@ -136,8 +136,8 @@ describe('createWorktreeChangeRefreshQueue', () => {
await flushPromises()
expect(handler).toHaveBeenCalledTimes(3)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-1', renamed)
expect(handler).toHaveBeenNthCalledWith(3, 'repo-1', undefined)
expect(handler).toHaveBeenNthCalledWith(2, 'repo-1', undefined, renamed)
expect(handler).toHaveBeenNthCalledWith(3, 'repo-1', undefined, undefined)
})
it('drops queued trailing refreshes after disposal', async () => {
@@ -5036,7 +5036,35 @@ describe('migrateWorktreeIdentity', () => {
return [{ id: 'group1', worktreeId }]
case 'recentlyClosedEditorTabsByWorktree':
return [{ id: 'file1', worktreeId }]
default:
case 'deleteStateByWorktreeId':
case 'baseStatusByWorktreeId':
case 'remoteBranchConflictByWorktreeId':
case 'fileSearchStateByWorktree':
case 'activeBrowserTabIdByWorktree':
case 'activeFileIdByWorktree':
case 'activeTabTypeByWorktree':
case 'activeTabIdByWorktree':
case 'tabBarOrderByWorktree':
case 'pendingReconnectTabByWorktree':
case 'rightSidebarTabByWorktree':
case 'rightSidebarExplorerViewByWorktree':
case 'layoutByWorktree':
case 'activeGroupIdByWorktree':
case 'gitStatusByWorktree':
case 'gitStatusHeadByWorktree':
case 'gitStatusHugeByWorktree':
case 'gitIgnoredPathsByWorktree':
case 'gitConflictOperationByWorktree':
case 'trackedConflictPathsByWorktree':
case 'gitBranchChangesByWorktree':
case 'gitBranchCompareSummaryByWorktree':
case 'gitBranchCompareRequestKeyByWorktree':
case 'gitBranchCompareRequestStatusHeadByWorktree':
case 'remoteStatusesByWorktree':
case 'showDotfilesByWorktree':
case 'expandedDirs':
case 'lastVisitedAtByWorktreeId':
case 'defaultTerminalTabsAppliedByWorktreeId':
return { marker: key }
}
}