Files
orca/src/main/runtime/runtime-store-contract.ts
T
Brennan Benson b402822fbb refactor(onboarding): scope first-run hook consent to the installation
The gate this replaces decided "is this profile new?" by inspecting the disk at
load: a state file or any backup meant pre-existing. That inference was wrong
three separate times. Telemetry seeding writes a file for a brand-new profile,
so a second profile installed hooks globally before its own checkbox appeared,
undoing the first profile's opt-out. A user who quit onboarding halfway read as
consented on relaunch. Two candidate redesigns died on the same inference.

Freshness is now recorded when it is a fact rather than discovered later. A
marker under the user-data root, established in preflight before any profile or
Store exists, says whether this installation predates the change. Absence means
pre-change, so an upgrading user is never deferred and nothing is written for
them at all.

Every ambiguity installs. A corrupt marker, a malformed partial record, an
unreadable root, or a failed write all resolve to install-as-today; a deferral
requires no marker, none of Orca's own artifacts on disk, and a successful
write. Deferring a user who already has hooks would stop maintaining them, so
that direction is the one that must never happen by accident.

Consent is installation-scoped, not per-profile, so a second profile inherits
the answer instead of being asked again -- hooks are user-global and the person
has already seen the disclosure. Profile seeding now carries
agentStatusHooksEnabled too; previously a parent's opt-out was silently lost and
the child reinstalled.

The policy is threaded through every writer, not just startup. SSH, the WSL
relay, Codex launch and resume, and the offline CLI all bypass
installManagedAgentHooks and each wrote user-global config on its own.

Also required, not incidental: the Codex refresh no longer runs the legacy
system cleanup. That is a profile-local operation reaching into the real
~/.codex, which "existing users untouched" forbids outright.
2026-09-13 19:59:50 -07:00

132 lines
7.1 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']
listAutomationRunsPage?: Store['listAutomationRunsPage']
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']
experimentalNativeChat?: GlobalSettings['experimentalNativeChat']
openAgentTabsInChatByDefault?: GlobalSettings['openAgentTabsInChatByDefault']
experimentalStructuredNativeChat?: GlobalSettings['experimentalStructuredNativeChat']
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']
minimaxEndpoint?: GlobalSettings['minimaxEndpoint']
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']
hostSettingOverrides?: GlobalSettings['hostSettingOverrides']
agentSkillSharingEnabled?: GlobalSettings['agentSkillSharingEnabled']
nativeChatSessionOptions?: GlobalSettings['nativeChatSessionOptions']
}
// 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
onSettingsChanged?: Store['onSettingsChanged']
}