mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 09:57:48 +00:00
* Keep dropped-macOS updaters on their last compatible build and explain why Stamp minimumSystemVersion into every mac update manifest from the built app's LSMinimumSystemVersion, and refuse to publish an ungated release. When the updater withholds a newer release for this macOS, show a one-time notice and say so in Settings and the update card. * Trim the unsupported-Mac notice copy
277 lines
11 KiB
TypeScript
277 lines
11 KiB
TypeScript
import { z } from 'zod'
|
||
import { isFeatureTipId } from '../feature-tips'
|
||
import {
|
||
WORKTREE_CARD_PROPERTIES,
|
||
normalizeWorktreeCardProperties
|
||
} from '../worktree/card-properties'
|
||
import { isPluginPanelTabKey } from '../plugins/manifest/plugin-manifest'
|
||
import { isFeatureInteractionId } from '../feature-interactions'
|
||
import type { FeatureInteractionId } from '../feature-interactions'
|
||
import { ACTIVITY_GROUP_BY_VALUES, THREAD_READ_FILTER_VALUES } from '../agents-view-thread-filters'
|
||
import { isReleaseChannel } from '../release-channel'
|
||
import type { ReleaseChannel } from '../release-channel'
|
||
import { ClientUiWorkspaceFilterFields } from './client-ui-workspace-filter-fields-params'
|
||
import { TaskResumeState } from './task-resume-state-params'
|
||
import { WorkspaceCleanup } from './workspace-cleanup-ui-params'
|
||
import { omitUndefinedValues, tolerateUnknownValues } from './ui-update-value-tolerance-params'
|
||
|
||
export const NullableString = z.string().nullable()
|
||
|
||
export const StringArray = z.array(z.string())
|
||
|
||
export const FeatureTipIds = z.array(
|
||
z.custom(isFeatureTipId, { message: 'Unknown feature tip id' })
|
||
)
|
||
|
||
export const UnknownRecord = z.record(z.string(), z.unknown())
|
||
|
||
export const UnknownRecordArray = z.array(UnknownRecord)
|
||
|
||
export type StaticRightSidebarTab = (typeof STATIC_RIGHT_SIDEBAR_TABS)[number]
|
||
|
||
// Derive from the shared union; drift previously omitted cli and rejected whole client payloads.
|
||
export const WorktreeCardPropertyParam = z.enum(WORKTREE_CARD_PROPERTIES)
|
||
|
||
export const WorktreeCardProperties = z
|
||
.array(WorktreeCardPropertyParam)
|
||
.transform((value) => normalizeWorktreeCardProperties(value))
|
||
|
||
export const STATIC_RIGHT_SIDEBAR_TABS = [
|
||
'explorer',
|
||
'search',
|
||
'vault',
|
||
'workspaces',
|
||
'pr-checks',
|
||
'source-control',
|
||
'checks',
|
||
'ports'
|
||
] as const
|
||
|
||
// Plugin panels are open-ended `plugin:<publisher>.<id>/<panel>` keys, so the
|
||
// schema validates their shape rather than enumerating them.
|
||
export const RightSidebarTabParam = z.custom<StaticRightSidebarTab | `plugin:${string}`>(
|
||
(value) =>
|
||
typeof value === 'string' &&
|
||
(STATIC_RIGHT_SIDEBAR_TABS.includes(value as StaticRightSidebarTab) ||
|
||
isPluginPanelTabKey(value)),
|
||
{ message: 'Unknown right sidebar tab' }
|
||
)
|
||
|
||
export const AgentActivityDisplayMode = z.enum(['compact', 'full'])
|
||
|
||
export const StatusBarItem = z.enum([
|
||
'claude',
|
||
'codex',
|
||
'gemini',
|
||
'antigravity',
|
||
'opencode-go',
|
||
'kimi',
|
||
'minimax',
|
||
'grok',
|
||
'cursor',
|
||
'zcode',
|
||
'ssh',
|
||
'resource-usage',
|
||
'ports'
|
||
])
|
||
|
||
export const WorkspaceStatusDefinition = z.object({
|
||
id: z.string(),
|
||
label: z.string(),
|
||
color: z.string().optional(),
|
||
icon: z.string().optional()
|
||
})
|
||
|
||
export const FeatureInteractionRecord = z
|
||
.object({
|
||
firstInteractedAt: z.number().finite().nonnegative(),
|
||
interactionCount: z.number().int().positive().optional()
|
||
})
|
||
.strict()
|
||
|
||
export const FeatureInteractions = z
|
||
.record(z.string(), FeatureInteractionRecord)
|
||
.superRefine((value, ctx) => {
|
||
for (const id of Object.keys(value)) {
|
||
if (!isFeatureInteractionId(id)) {
|
||
ctx.addIssue({
|
||
code: z.ZodIssueCode.custom,
|
||
message: `Unknown feature interaction id: ${id}`,
|
||
path: [id]
|
||
})
|
||
}
|
||
}
|
||
})
|
||
|
||
export const FeatureInteractionIdParam = z.custom<FeatureInteractionId>(isFeatureInteractionId, {
|
||
message: 'Unknown feature interaction id'
|
||
})
|
||
|
||
export const TopLevelViewSchema = z.enum([
|
||
'terminal',
|
||
'settings',
|
||
'tasks',
|
||
'activity',
|
||
'automations',
|
||
'space',
|
||
'skills',
|
||
'artifacts',
|
||
'mobile'
|
||
])
|
||
|
||
export const UiUpdateFields = z
|
||
.object({
|
||
lastActiveRepoId: NullableString.optional(),
|
||
lastActiveWorktreeId: NullableString.optional(),
|
||
// Why: sync hydration ignores this persisted startup view, so paired windows stay put.
|
||
activeView: TopLevelViewSchema.optional(),
|
||
sidebarWidth: z.number().finite().optional(),
|
||
sidebarOpen: z.boolean().optional(),
|
||
rightSidebarOpen: z.boolean().optional(),
|
||
rightSidebarTab: RightSidebarTabParam.optional(),
|
||
rightSidebarExplorerView: z.enum(['files', 'search']).optional(),
|
||
rightSidebarWidth: z.number().finite().optional(),
|
||
markdownTocPanelWidth: z.number().finite().optional(),
|
||
combinedDiffFileTreeWidth: z.number().finite().optional(),
|
||
groupBy: z.enum(['none', 'workspace-status', 'repo', 'pr-status']).optional(),
|
||
showWorkspaceLineage: z.boolean().optional(),
|
||
sortBy: z.enum(['name', 'smart', 'recent', 'repo', 'manual']).optional(),
|
||
projectOrderBy: z.enum(['manual', 'recent']).optional(),
|
||
showActiveOnly: z.boolean().optional(),
|
||
hideSleepingWorkspaces: z.boolean().optional(),
|
||
showSleepingWorkspaces: z.boolean().optional(),
|
||
showInactiveWorkspaces: z.boolean().optional(),
|
||
workspaceHostScope: z.string().optional(),
|
||
visibleWorkspaceHostIds: z.array(z.string()).nullable().optional(),
|
||
agentsVisibleHostIds: z.array(z.string()).nullable().optional(),
|
||
agentsFilterRepoIds: StringArray.optional(),
|
||
agentsHideWorkspacesFromOtherDevices: z.boolean().optional(),
|
||
agentsHideAutomationGeneratedWorkspaces: z.boolean().optional(),
|
||
agentsHideCliCreatedWorkspaces: z.boolean().optional(),
|
||
agentsShowChildAgents: z.boolean().optional(),
|
||
agentsCompactMode: z.boolean().optional(),
|
||
agentsShowSearch: z.boolean().optional(),
|
||
agentsReadFilter: z.enum(THREAD_READ_FILTER_VALUES).optional(),
|
||
agentsGroupBy: z.enum(ACTIVITY_GROUP_BY_VALUES).optional(),
|
||
workspaceHostOrder: z.array(z.string()).optional(),
|
||
automationHostFilter: z
|
||
.union([
|
||
z.object({ kind: z.literal('all') }).strict(),
|
||
z.object({ kind: z.literal('host'), hostKey: z.string().min(1) }).strict()
|
||
])
|
||
.optional(),
|
||
manualRepoOrder: z
|
||
.array(z.object({ hostId: z.string(), repoId: z.string() }).strict())
|
||
.optional(),
|
||
...ClientUiWorkspaceFilterFields,
|
||
// Part of App.tsx’s debounced writer; missing fields reject the whole paired-client payload.
|
||
explorerDisplayRootByWorktree: z.record(z.string(), z.string()).optional(),
|
||
showDotfilesByWorktree: z.record(z.string(), z.boolean()).optional(),
|
||
collapsedGroups: StringArray.optional(),
|
||
uiZoomLevel: z.number().finite().optional(),
|
||
editorFontZoomLevel: z.number().finite().optional(),
|
||
worktreeCardProperties: WorktreeCardProperties.optional(),
|
||
_worktreeCardModeDefaulted: z.boolean().optional(),
|
||
showAgentModels: z.boolean().optional(),
|
||
agentActivityDisplayMode: AgentActivityDisplayMode.optional(),
|
||
workspaceStatuses: z.array(WorkspaceStatusDefinition).optional(),
|
||
workspaceBoardOpacity: z.number().finite().optional(),
|
||
workspaceBoardColumnWidth: z.number().finite().optional(),
|
||
syncTaskStatusFromWorkspaceBoard: z.boolean().optional(),
|
||
_workspaceStatusesDefaultOrderMigrated: z.boolean().optional(),
|
||
_workspaceStatusesReorderedDefaultRepaired: z.boolean().optional(),
|
||
_workspaceStatusesDefaultWorkflowMigrated: z.boolean().optional(),
|
||
_workspaceStatusesDefaultVisualsMigrated: z.boolean().optional(),
|
||
statusBarItems: z.array(StatusBarItem).optional(),
|
||
_portsStatusBarDefaultAdded: z.boolean().optional(),
|
||
_kimiStatusBarDefaultAdded: z.boolean().optional(),
|
||
_minimaxStatusBarDefaultAdded: z.boolean().optional(),
|
||
_antigravityStatusBarDefaultAdded: z.boolean().optional(),
|
||
_grokStatusBarDefaultAdded: z.boolean().optional(),
|
||
_cursorStatusBarDefaultAdded: z.boolean().optional(),
|
||
_zcodeStatusBarDefaultAdded: z.boolean().optional(),
|
||
statusBarVisible: z.boolean().optional(),
|
||
usagePercentageDisplay: z.enum(['used', 'remaining']).optional(),
|
||
statusBarUsageMode: z.enum(['verbose', 'compact']).optional(),
|
||
dismissedUpdateVersion: NullableString.optional(),
|
||
dismissedUnexpectedSignoutVersion: NullableString.optional(),
|
||
lastUpdateCheckAt: z.number().finite().nullable().optional(),
|
||
pendingUpdateNudgeId: NullableString.optional(),
|
||
dismissedUpdateNudgeId: NullableString.optional(),
|
||
// Share the release-channel predicate so new channel overrides cannot silently fail parsing.
|
||
releaseChannelOverride: z.custom<ReleaseChannel>(isReleaseChannel).nullable().optional(),
|
||
notificationPermissionRequested: z.boolean().optional(),
|
||
updateReassuranceSeen: z.boolean().optional(),
|
||
seenMacosUpdateRequirement: NullableString.optional(),
|
||
osc52ClipboardDefaultOnNoticePending: z.boolean().optional(),
|
||
acknowledgedAgentsByPaneKey: z.record(z.string(), z.number().finite()).optional(),
|
||
activityClearedAtByPaneKey: z.record(z.string(), z.number().finite()).optional(),
|
||
manuallyUnreadTurnsByPaneKey: z.record(z.string(), z.number().finite()).optional(),
|
||
browserDefaultUrl: NullableString.optional(),
|
||
browserDefaultSearchEngine: z
|
||
.enum(['google', 'duckduckgo', 'bing', 'kagi'])
|
||
.nullable()
|
||
.optional(),
|
||
browserDefaultZoomLevel: z.number().finite().optional(),
|
||
browserKagiSessionLink: NullableString.optional(),
|
||
browserDevToolsDock: z
|
||
.object({
|
||
side: z.enum(['bottom', 'right']),
|
||
bottomFraction: z.number().finite(),
|
||
rightFraction: z.number().finite()
|
||
})
|
||
.optional(),
|
||
windowBounds: z
|
||
.object({
|
||
x: z.number().finite(),
|
||
y: z.number().finite(),
|
||
width: z.number().finite(),
|
||
height: z.number().finite()
|
||
})
|
||
.nullable()
|
||
.optional(),
|
||
windowMaximized: z.boolean().optional(),
|
||
_sortBySmartMigrated: z.boolean().optional(),
|
||
_inlineAgentsDefaultedForExperiment: z.boolean().optional(),
|
||
_inlineAgentsDefaultedForAllUsers: z.boolean().optional(),
|
||
trustedOrcaHooks: z.record(z.string(), z.unknown()).optional(),
|
||
setupScriptPromptDismissedRepoIds: StringArray.optional(),
|
||
// Declare renderer one-shot dismissals to avoid whole-payload rejections for paired clients.
|
||
setupGuideSidebarDismissed: z.boolean().optional(),
|
||
setupGuideBrowserMilestoneMigrated: z.boolean().optional(),
|
||
setupGuideBrowserMilestoneLegacyComplete: z.boolean().optional(),
|
||
browserImportHintHidden: z.boolean().optional(),
|
||
mobileEmulatorTabIntroDismissed: z.boolean().optional(),
|
||
mobileEmulatorAgentSetupDismissed: z.boolean().optional(),
|
||
projectOrderManualDefaultNoticeDismissed: z.boolean().optional(),
|
||
usagePercentageDisplayChangeNoticeDismissed: z.boolean().optional(),
|
||
statusBarCompactChangeNoticeDismissed: z.boolean().optional(),
|
||
usageEmptyStateDismissed: z.boolean().optional(),
|
||
pluginsAlphaNoticeDismissed: z.boolean().optional(),
|
||
codexTerminalServerIsolationNoticeSeen: z.boolean().optional(),
|
||
codexSharedSettingsNoticeSeen: z.boolean().optional(),
|
||
claudeDiffPanelNoticeSeen: z.boolean().optional(),
|
||
claudeAccountSignInNoticeSeen: z.boolean().optional(),
|
||
petVisible: z.boolean().optional(),
|
||
petId: z.string().optional(),
|
||
customPets: UnknownRecordArray.optional(),
|
||
petSize: z.number().finite().optional(),
|
||
sidekickVisible: z.boolean().optional(),
|
||
sidekickId: z.string().optional(),
|
||
customSidekicks: UnknownRecordArray.optional(),
|
||
sidekickSize: z.number().finite().optional(),
|
||
taskResumeState: TaskResumeState.optional(),
|
||
workspaceCleanup: WorkspaceCleanup.optional(),
|
||
featureTipsSeenIds: FeatureTipIds.optional(),
|
||
featureInteractions: FeatureInteractions.optional(),
|
||
contextualToursSeenIds: StringArray.optional(),
|
||
contextualToursAutoEligible: z.boolean().optional()
|
||
})
|
||
.strict()
|
||
|
||
export const UiUpdate = z
|
||
.object(tolerateUnknownValues(UiUpdateFields.shape))
|
||
.strict()
|
||
.default({})
|
||
.transform(omitUndefinedValues)
|