Files
orca/src/main/runtime/runtime-store-contract.ts
T
Neil ff8b4d08ab fix(runtime): sweep missing local worktree metadata on the host that owns it
`pruneMetadataMissingFromAuthoritativeLocalScan` had exactly one caller:
`ipcMain.handle('worktrees:listAll')`. A headless runtime host has no
renderer, so it never ran, and that host's `worktreeMeta` grew without bound
even for its own local repos -- 129 of 139 rows dangling on the profile in
#17776.

Run it from the runtime's own detected listing instead. That is the same
trigger on the same evidence: `listDetected` already prunes lineage on an
authoritative scan, and a paired client refreshing a remote repo calls
`worktree.detectedList`, so the host now sweeps exactly when the desktop
would have.

The expectation is captured before the scan, because listing can mutate
metadata synchronously before its first await. WSL-routed repos are excluded
for the reason the desktop listing excludes them: the listing runs in the
distro and reports Linux paths while metadata can hold UNC ones, and v1
cannot prove those aliases equivalent. A runtime needing repair throws rather
than resolving routing, which is likewise no basis for deleting rows.

The prune's own gates still apply, so an SSH- or otherwise off-host repo is
never swept from a local stat -- the execution host owns that verdict.

Refs #17776
2026-09-01 17:20:17 -07:00

124 lines
6.6 KiB
TypeScript

import type { Store } from '../persistence'
import type { GlobalSettings } from '../../shared/global-settings-types'
import type { VoiceSettings } from '../../shared/speech-types'
export type RuntimeStore = {
getRepos: Store['getRepos']
getRepo: Store['getRepo']
addRepo: Store['addRepo']
updateRepo: Store['updateRepo']
addRetiredWorktreeName?: Store['addRetiredWorktreeName']
mergeRetiredWorktreeNames?: Store['mergeRetiredWorktreeNames']
getRetiredWorktreeNameRegistry?: Store['getRetiredWorktreeNameRegistry']
getRetiredWorktreeNameRegistryForNamespace?: Store['getRetiredWorktreeNameRegistryForNamespace']
getProjects?: Store['getProjects']
updateProject?: Store['updateProject']
getProjectHostSetups?: Store['getProjectHostSetups']
createProjectHostSetup?: Store['createProjectHostSetup']
updateProjectHostSetup?: Store['updateProjectHostSetup']
deleteProjectHostSetup?: Store['deleteProjectHostSetup']
getProjectGroups?: Store['getProjectGroups']
createProjectGroup?: Store['createProjectGroup']
updateProjectGroup?: Store['updateProjectGroup']
deleteProjectGroup?: Store['deleteProjectGroup']
moveProjectToGroup?: Store['moveProjectToGroup']
getFolderWorkspaces?: Store['getFolderWorkspaces']
createFolderWorkspace?: Store['createFolderWorkspace']
updateFolderWorkspace?: Store['updateFolderWorkspace']
removeFolderWorkspace?: Store['removeFolderWorkspace']
removeProject?: Store['removeProject']
removeProjectForHost?: Store['removeProjectForHost']
reorderRepos?: Store['reorderRepos']
getAllWorktreeMeta: Store['getAllWorktreeMeta']
captureNativeLocalWorktreeMetadataScanExpectation?: Store['captureNativeLocalWorktreeMetadataScanExpectation']
pruneSessionlessMissingLocalWorktreeMetadataForRepo?: Store['pruneSessionlessMissingLocalWorktreeMetadataForRepo']
getProfileStorageDirectory?: Store['getProfileStorageDirectory']
getWorktreeMeta: Store['getWorktreeMeta']
setWorktreeMeta: Store['setWorktreeMeta']
setWorktreeMetaForHost?: Store['setWorktreeMetaForHost']
removeWorktreeMeta: Store['removeWorktreeMeta']
getWorktreeLineage?: Store['getWorktreeLineage']
getAllWorktreeLineage?: Store['getAllWorktreeLineage']
setWorktreeLineage?: Store['setWorktreeLineage']
removeWorktreeLineage?: Store['removeWorktreeLineage']
getAllWorkspaceLineage?: Store['getAllWorkspaceLineage']
setWorkspaceLineage?: Store['setWorkspaceLineage']
removeWorkspaceLineage?: Store['removeWorkspaceLineage']
getGitHubCache: Store['getGitHubCache']
getWorkspaceSession?: Store['getWorkspaceSession']
getWorkspaceSessionHostIds?: Store['getWorkspaceSessionHostIds']
setWorkspaceSession?: Store['setWorkspaceSession']
flushOrThrow?: Store['flushOrThrow']
flushPendingOrThrowAsync?: Store['flushPendingOrThrowAsync']
persistPtyBinding?: Store['persistPtyBinding']
getSshRemotePtyLeases?: Store['getSshRemotePtyLeases']
getUI?: Store['getUI']
updateUI?: Store['updateUI']
recordFeatureInteraction?: Store['recordFeatureInteraction']
listAutomations?: Store['listAutomations']
listAutomationsForScope?: Store['listAutomationsForScope']
assertAutomationOwnerFence?: Store['assertAutomationOwnerFence']
automationOwnerPrecondition?: Store['automationOwnerPrecondition']
automationChangeSelector?: Store['automationChangeSelector']
listAutomationRuns?: Store['listAutomationRuns']
createAutomation?: Store['createAutomation']
updateAutomation?: Store['updateAutomation']
deleteAutomation?: Store['deleteAutomation']
getSparsePresets?: Store['getSparsePresets']
saveSparsePreset?: Store['saveSparsePreset']
getMobileClientTabSelections?: Store['getMobileClientTabSelections']
setMobileClientTabSelections?: Store['setMobileClientTabSelections']
getSettings(): {
workspaceDir: string
nestWorkspaces: boolean
// Read by worktree placement: decides whether this project's worktrees
// mirror into a WSL distro instead of the Windows drive.
localWindowsRuntimeDefault?: GlobalSettings['localWindowsRuntimeDefault']
refreshLocalBaseRefOnWorktreeCreate: boolean
localBaseRefSuggestionDismissed?: boolean
branchPrefix: string
branchPrefixCustom: string
defaultTuiAgent?: GlobalSettings['defaultTuiAgent']
disabledTuiAgents?: GlobalSettings['disabledTuiAgents']
agentCmdOverrides?: GlobalSettings['agentCmdOverrides']
agentDefaultArgs?: GlobalSettings['agentDefaultArgs']
agentDefaultEnv?: GlobalSettings['agentDefaultEnv']
terminalWindowsShell?: GlobalSettings['terminalWindowsShell']
floatingTerminalEnabled?: GlobalSettings['floatingTerminalEnabled']
agentStatusHooksEnabled?: GlobalSettings['agentStatusHooksEnabled']
defaultTaskSource?: GlobalSettings['defaultTaskSource']
defaultTaskViewPreset?: GlobalSettings['defaultTaskViewPreset']
visibleTaskProviders?: GlobalSettings['visibleTaskProviders']
defaultRepoSelection?: GlobalSettings['defaultRepoSelection']
defaultLinearTeamSelection?: GlobalSettings['defaultLinearTeamSelection']
githubProjects?: GlobalSettings['githubProjects']
experimentalNewWorktreeCardStyle?: GlobalSettings['experimentalNewWorktreeCardStyle']
compactWorktreeCards?: GlobalSettings['compactWorktreeCards']
minimaxGroupId?: GlobalSettings['minimaxGroupId']
minimaxUsageModels?: GlobalSettings['minimaxUsageModels']
prBotAuthorOverrides?: GlobalSettings['prBotAuthorOverrides']
artifactSharingEnabled?: GlobalSettings['artifactSharingEnabled']
terminalQuickCommands?: GlobalSettings['terminalQuickCommands']
gitlabProjects?: GlobalSettings['gitlabProjects']
mobileAutoRestoreFitMs?: number | null
mobileEmulatorEnabled?: boolean
mobileEmulatorDefaultDeviceUdid?: string | null
voice?: VoiceSettings
claudeAgentTeamsMode?: GlobalSettings['claudeAgentTeamsMode']
// Why: Phase-5 query responder kill switches — read per chunk in
// onPtyData to capture reply ownership at ingestion.
terminalMainSideEffectAuthority?: GlobalSettings['terminalMainSideEffectAuthority']
terminalHiddenDeliveryGate?: GlobalSettings['terminalHiddenDeliveryGate']
terminalModelQueryAuthority?: GlobalSettings['terminalModelQueryAuthority']
worktreeVisibilityDefaults?: GlobalSettings['worktreeVisibilityDefaults']
agentSkillSharingEnabled?: GlobalSettings['agentSkillSharingEnabled']
}
// Why: narrow to `unknown` return so test mocks can return void without
// a cast. The runtime never reads the return value — the persisted value
// is read back via getSettings() on the next access.
updateSettings?: (
updates: Partial<GlobalSettings>,
options?: { notifyListeners?: boolean; originWebContentsId?: number }
) => unknown
}