Split managed skill prompt settings

This commit is contained in:
brennanb2025
2026-06-25 15:33:46 -07:00
parent f6568ccf45
commit bce9cd8f61
17 changed files with 527 additions and 62 deletions
+2
View File
@@ -493,6 +493,8 @@ describe('Store', () => {
expect(settings.experimentalActivityDefaultedOffForAllUsers).toBe(true)
expect(settings.experimentalTerminalAttention).toBe(false)
expect(settings.experimentalNewWorktreeCardStyle).toBe(true)
expect(settings.managedAgentSkillSetupPromptsEnabled).toBe(true)
expect(settings.managedAgentSkillBackgroundUpdatesEnabled).toBe(false)
expect(settings.floatingTerminalEnabled).toBe(true)
expect(settings.floatingTerminalDefaultedForAllUsers).toBe(true)
expect(settings.notifications.customSoundPath).toBeNull()
@@ -10,7 +10,7 @@ import { useAppStore } from '../../store'
import { getAgentGeneratedTabTitlesTitle } from './agent-generated-tab-title-copy'
import { getAgentStatusHooksTitle } from './agent-status-hooks-copy'
import { getAgentAwakeDescription, getAgentAwakeTitle } from './agent-awake-copy'
import { getManagedAgentSkillBackgroundUpdatesTitle } from './managed-agent-skill-background-updates-copy'
import { getManagedAgentSkillSetupPromptsTitle } from './managed-agent-skill-setup-prompts-copy'
import { AgentAwakeSetting } from './AgentAwakeSetting'
import {
AgentAvailabilityControl,
@@ -18,7 +18,7 @@ import {
AgentGeneratedTabTitlesSetting,
AgentStatusHooksSetting,
AgentsPane,
ManagedAgentSkillBackgroundUpdatesSetting,
ManagedAgentSkillSetupPromptsSetting,
getAgentsPaneSearchEntries,
buildAgentAvailabilitySettingsUpdate,
createAgentAvailabilityUpdateQueue
@@ -146,11 +146,12 @@ describe('AgentsPane', () => {
expect(markup).toContain(
'Keeps this computer and display awake while agents are working. Orca also asks this device to stay awake when the lid is closed, subject to its power policy.'
)
expect(markup).toContain('Automatically update verified Orca skills')
expect(markup).toContain('Show agent skill setup prompts')
expect(markup).toContain(
'Experimental. When enabled, Orca can update verified Orca-managed global skills in the background when a workflow needs them. When off, Orca asks you to review updates manually.'
'When an Orca workflow needs a missing or outdated managed skill, show a prompt to install or update it.'
)
expect(markup).toContain('aria-checked="false"')
expect(markup).toContain('aria-checked="true"')
expect(markup).not.toContain('Automatically update verified Orca skills')
})
it('does not render the legacy agent location control on Windows', () => {
@@ -229,21 +230,21 @@ describe('AgentsPane', () => {
})
})
it('enables experimental managed skill background updates from the default off state', () => {
it('disables managed skill setup prompts from the default on state', () => {
const updateSettings = vi.fn()
const element = ManagedAgentSkillBackgroundUpdatesSetting({
const element = ManagedAgentSkillSetupPromptsSetting({
settings: getDefaultSettings('/tmp'),
updateSettings
})
const updatesSwitch = findSwitchRow(element, getManagedAgentSkillBackgroundUpdatesTitle())
expect(updatesSwitch.props.checked).toBe(false)
const updatesSwitch = findSwitchRow(element, getManagedAgentSkillSetupPromptsTitle())
expect(updatesSwitch.props.checked).toBe(true)
const onChange = updatesSwitch.props.onChange as () => void
onChange()
expect(updateSettings).toHaveBeenCalledWith({
managedAgentSkillBackgroundUpdatesEnabled: true
managedAgentSkillSetupPromptsEnabled: false
})
})
@@ -280,10 +281,10 @@ describe('AgentsPane', () => {
expect(matchesSettingsSearch('codex', getAgentsPaneSearchEntries())).toBe(true)
})
it('includes managed skill update search metadata', () => {
expect(matchesSettingsSearch('experimental', getAgentsPaneSearchEntries())).toBe(true)
expect(matchesSettingsSearch('automatic', getAgentsPaneSearchEntries())).toBe(true)
expect(matchesSettingsSearch('manual', getAgentsPaneSearchEntries())).toBe(true)
it('includes managed skill prompt search metadata', () => {
expect(matchesSettingsSearch('prompt', getAgentsPaneSearchEntries())).toBe(true)
expect(matchesSettingsSearch('setup', getAgentsPaneSearchEntries())).toBe(true)
expect(matchesSettingsSearch('install', getAgentsPaneSearchEntries())).toBe(true)
expect(matchesSettingsSearch('skills', getAgentsPaneSearchEntries())).toBe(true)
})
@@ -18,9 +18,9 @@ import {
} from './agent-generated-tab-title-copy'
import { getAgentStatusHooksDescription, getAgentStatusHooksTitle } from './agent-status-hooks-copy'
import {
getManagedAgentSkillBackgroundUpdatesDescription,
getManagedAgentSkillBackgroundUpdatesTitle
} from './managed-agent-skill-background-updates-copy'
getManagedAgentSkillSetupPromptsDescription,
getManagedAgentSkillSetupPromptsTitle
} from './managed-agent-skill-setup-prompts-copy'
import {
SettingsBadge,
SettingsSegmentedControl,
@@ -827,10 +827,7 @@ export function AgentsPane({ settings, updateSettings }: AgentsPaneProps): React
<AgentStatusHooksSetting settings={settings} updateSettings={updateSettings} />
<ManagedAgentSkillBackgroundUpdatesSetting
settings={settings}
updateSettings={updateSettings}
/>
<ManagedAgentSkillSetupPromptsSetting settings={settings} updateSettings={updateSettings} />
<AgentGeneratedTabTitlesSetting settings={settings} updateSettings={updateSettings} />
@@ -978,23 +975,23 @@ export function AgentStatusHooksSetting({
)
}
export function ManagedAgentSkillBackgroundUpdatesSetting({
export function ManagedAgentSkillSetupPromptsSetting({
settings,
updateSettings
}: AgentsPaneProps): React.JSX.Element {
const enabled = settings.managedAgentSkillBackgroundUpdatesEnabled === true
const enabled = settings.managedAgentSkillSetupPromptsEnabled !== false
return (
<section className="space-y-3">
<SettingsSwitchRow
label={getManagedAgentSkillBackgroundUpdatesTitle()}
description={getManagedAgentSkillBackgroundUpdatesDescription()}
label={getManagedAgentSkillSetupPromptsTitle()}
description={getManagedAgentSkillSetupPromptsDescription()}
checked={enabled}
onChange={() =>
updateSettings({
managedAgentSkillBackgroundUpdatesEnabled: !enabled
managedAgentSkillSetupPromptsEnabled: !enabled
})
}
ariaLabel={getManagedAgentSkillBackgroundUpdatesTitle()}
ariaLabel={getManagedAgentSkillSetupPromptsTitle()}
/>
</section>
)
@@ -8,6 +8,7 @@ import type { GlobalSettings } from '../../../../shared/types'
import { getDefaultSettings } from '../../../../shared/constants'
import { ExperimentalPane } from './ExperimentalPane'
import { getExperimentalPaneSearchEntries } from './experimental-search'
import { getManagedAgentSkillBackgroundUpdatesTitle } from './managed-agent-skill-background-updates-copy'
vi.mock('../../store', () => ({
useAppStore: (selector: (state: { settingsSearchQuery: string }) => unknown) =>
@@ -77,6 +78,21 @@ describe('ExperimentalPane', () => {
)
})
it('renders managed skill background updates as an off-by-default experimental switch', () => {
const settings = getDefaultSettings('/tmp')
const markup = renderToStaticMarkup(
<ExperimentalPane settings={settings} updateSettings={vi.fn()} />
)
expect(settings.managedAgentSkillBackgroundUpdatesEnabled).toBe(false)
expect(markup).toContain('Automatically update verified Orca skills')
expect(markup).toContain('Experimental. When enabled')
expect(markup).toContain('aria-checked="false"')
expect(getExperimentalPaneSearchEntries().map((entry) => entry.title)).toContain(
getManagedAgentSkillBackgroundUpdatesTitle()
)
})
it('renders the agent sleep idle duration as configurable minutes', async () => {
const updateSettings = vi.fn()
const settings = {
@@ -138,4 +154,25 @@ describe('ExperimentalPane', () => {
expect(updateSettings).toHaveBeenCalledWith({ experimentalNewWorktreeCardStyle: true })
root.unmount()
})
it('enables managed skill background updates through the experimental switch', async () => {
const updateSettings = vi.fn()
const { root, container } = await renderExperimentalPane({ updateSettings })
const switchButton = container.querySelector<HTMLButtonElement>(
'#experimental-managed-skill-background-updates button[role="switch"]'
)
if (!switchButton) {
throw new Error('Managed skill background updates switch was not rendered')
}
await act(async () => {
switchButton.dispatchEvent(new MouseEvent('click', { bubbles: true }))
})
expect(updateSettings).toHaveBeenCalledWith({
managedAgentSkillBackgroundUpdatesEnabled: true
})
root.unmount()
})
})
@@ -6,6 +6,10 @@ import { matchesSettingsSearch } from './settings-search'
import { getExperimentalPaneSearchEntries, getExperimentalSearchEntry } from './experimental-search'
import { HiddenExperimentalGroup } from './HiddenExperimentalGroup'
import { NumberField, SettingsSwitch } from './SettingsFormControls'
import {
getManagedAgentSkillBackgroundUpdatesDescription,
getManagedAgentSkillBackgroundUpdatesTitle
} from './managed-agent-skill-background-updates-copy'
import { translate } from '@/i18n/i18n'
import {
MAX_AGENT_HIBERNATION_IDLE_MS,
@@ -47,8 +51,13 @@ export function ExperimentalPane({
const showNewWorktreeCardStyle = matchesSettingsSearch(searchQuery, [
getExperimentalSearchEntry().newWorktreeCardStyle
])
const showManagedSkillBackgroundUpdates = matchesSettingsSearch(searchQuery, [
getExperimentalSearchEntry().managedSkillBackgroundUpdates
])
const agentHibernationEnabled = settings.experimentalAgentHibernation === true
const newWorktreeCardStyleEnabled = settings.experimentalNewWorktreeCardStyle === true
const managedSkillBackgroundUpdatesEnabled =
settings.managedAgentSkillBackgroundUpdatesEnabled === true
// Why: the planner owns ms-based bounds/defaults; the UI edits minutes
// while displaying the same effective clamped value the planner will use.
const agentHibernationIdleMinutes = Math.round(
@@ -313,6 +322,34 @@ export function ExperimentalPane({
</SearchableSetting>
) : null}
{showManagedSkillBackgroundUpdates ? (
<SearchableSetting
title={getManagedAgentSkillBackgroundUpdatesTitle()}
description={getManagedAgentSkillBackgroundUpdatesDescription()}
keywords={getExperimentalSearchEntry().managedSkillBackgroundUpdates.keywords}
className="space-y-3 py-2"
id="experimental-managed-skill-background-updates"
>
<div className="flex items-start justify-between gap-4">
<div className="min-w-0 shrink space-y-0.5">
<Label>{getManagedAgentSkillBackgroundUpdatesTitle()}</Label>
<p className="text-xs text-muted-foreground">
{getManagedAgentSkillBackgroundUpdatesDescription()}
</p>
</div>
<SettingsSwitch
checked={managedSkillBackgroundUpdatesEnabled}
ariaLabel={getManagedAgentSkillBackgroundUpdatesTitle()}
onChange={() =>
updateSettings({
managedAgentSkillBackgroundUpdatesEnabled: !managedSkillBackgroundUpdatesEnabled
})
}
/>
</div>
</SearchableSetting>
) : null}
{showWorktreeSymlinks ? (
<SearchableSetting
title={translate(
@@ -15,10 +15,10 @@ import {
getAgentStatusHooksTitle
} from './agent-status-hooks-copy'
import {
getManagedAgentSkillBackgroundUpdatesDescription,
getManagedAgentSkillBackgroundUpdatesSearchKeywords,
getManagedAgentSkillBackgroundUpdatesTitle
} from './managed-agent-skill-background-updates-copy'
getManagedAgentSkillSetupPromptsDescription,
getManagedAgentSkillSetupPromptsSearchKeywords,
getManagedAgentSkillSetupPromptsTitle
} from './managed-agent-skill-setup-prompts-copy'
import { getAgentCacheTimerSearchEntries } from './agent-cache-timer-search'
import { translate } from '@/i18n/i18n'
import { searchKeywords, translateSearchKeyword, uniqueKeywords } from './settings-search-keywords'
@@ -79,9 +79,9 @@ export const getAgentsPaneSearchEntries = createLocalizedCatalog(() => [
keywords: getAgentStatusHooksSearchKeywords()
},
{
title: getManagedAgentSkillBackgroundUpdatesTitle(),
description: getManagedAgentSkillBackgroundUpdatesDescription(),
keywords: getManagedAgentSkillBackgroundUpdatesSearchKeywords()
title: getManagedAgentSkillSetupPromptsTitle(),
description: getManagedAgentSkillSetupPromptsDescription(),
keywords: getManagedAgentSkillSetupPromptsSearchKeywords()
},
{
title: getAgentGeneratedTabTitlesTitle(),
@@ -3,6 +3,11 @@ import { createLocalizedCatalog } from '@/i18n/localized-catalog'
import { translate } from '@/i18n/i18n'
import { translateSearchKeyword } from './settings-search-keywords'
import { getNewWorktreeCardStyleSearchEntry } from './new-worktree-card-style-search-entry'
import {
getManagedAgentSkillBackgroundUpdatesDescription,
getManagedAgentSkillBackgroundUpdatesSearchKeywords,
getManagedAgentSkillBackgroundUpdatesTitle
} from './managed-agent-skill-background-updates-copy'
export const getExperimentalPaneSearchEntries = createLocalizedCatalog(
(): SettingsSearchEntry[] => [
@@ -182,6 +187,11 @@ export const getExperimentalPaneSearchEntries = createLocalizedCatalog(
]
},
getNewWorktreeCardStyleSearchEntry(),
{
title: getManagedAgentSkillBackgroundUpdatesTitle(),
description: getManagedAgentSkillBackgroundUpdatesDescription(),
keywords: getManagedAgentSkillBackgroundUpdatesSearchKeywords()
},
{
title: translate(
'auto.components.settings.experimental.search.78c2a8dc74',
@@ -266,6 +276,7 @@ export function getExperimentalSearchEntry() {
'New card style'
)
),
managedSkillBackgroundUpdates: findEntry(getManagedAgentSkillBackgroundUpdatesTitle()),
symlinksOnWorktrees: findEntry(
translate(
'auto.components.settings.experimental.search.78c2a8dc74',
@@ -0,0 +1,45 @@
import { translate } from '@/i18n/i18n'
import { searchKeywords } from './settings-search-keywords'
export function getManagedAgentSkillSetupPromptsTitle(): string {
return translate(
'auto.components.settings.managed.agent.skill.setup.prompts.title',
'Show agent skill setup prompts'
)
}
export function getManagedAgentSkillSetupPromptsDescription(): string {
return translate(
'auto.components.settings.managed.agent.skill.setup.prompts.description',
'When an Orca workflow needs a missing or outdated managed skill, show a prompt to install or update it.'
)
}
export function getManagedAgentSkillSetupPromptsSearchKeywords(): string[] {
return searchKeywords([
{
key: 'auto.components.settings.managed.agent.skill.setup.prompts.search.agent',
fallback: 'agent'
},
{
key: 'auto.components.settings.managed.agent.skill.setup.prompts.search.skills',
fallback: 'skills'
},
{
key: 'auto.components.settings.managed.agent.skill.setup.prompts.search.setup',
fallback: 'setup'
},
{
key: 'auto.components.settings.managed.agent.skill.setup.prompts.search.prompt',
fallback: 'prompt'
},
{
key: 'auto.components.settings.managed.agent.skill.setup.prompts.search.install',
fallback: 'install'
},
{
key: 'auto.components.settings.managed.agent.skill.setup.prompts.search.update',
fallback: 'update'
}
])
}
@@ -1,5 +1,12 @@
// @vitest-environment happy-dom
import React from 'react'
import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { getDefaultSettings } from '../../../../shared/constants'
import type { ManagedAgentSkillFallback } from '../../../../shared/skills'
import type { GlobalSettings } from '../../../../shared/types'
import {
getManagedSkillFallbackDisplayMessage,
getManagedSkillContextCopy,
@@ -12,11 +19,37 @@ import {
prepareManagedAgentSkillSetupTerminal,
replaceActiveAfterManagedAgentSkillRecheck
} from './managed-agent-skill-dialog-state'
import { ManagedAgentSkillSetupDialogHost } from './ManagedAgentSkillSetupDialogHost'
const cliPrerequisiteMocks = vi.hoisted(() => ({
ensureOrcaCliAvailableForAgentSkillTerminal: vi.fn()
}))
const hookMocks = vi.hoisted(() => ({
notifyInstalledAgentSkillsChanged: vi.fn(),
useInstalledAgentSkill: vi.fn(() => ({
installed: false,
loading: false,
error: null
}))
}))
const storeMocks = vi.hoisted(() => ({
state: {
settings: null as ReturnType<typeof getDefaultSettings> | null,
updateSettings: vi.fn<(updates: Partial<GlobalSettings>) => Promise<void>>(async () => {
return undefined
}),
openSettingsPage: vi.fn(),
openSettingsTarget: vi.fn()
}
}))
const toastMocks = vi.hoisted(() => ({
error: vi.fn(),
message: vi.fn()
}))
vi.mock('@/lib/agent-skill-cli-prerequisite', () => ({
AGENT_SKILL_CLI_PREREQUISITE_NOTICE: 'CLI registration notice',
ensureOrcaCliAvailableForAgentSkillTerminal:
@@ -24,6 +57,26 @@ vi.mock('@/lib/agent-skill-cli-prerequisite', () => ({
isOrcaCliAvailableOnPath: vi.fn()
}))
vi.mock('@/hooks/useInstalledAgentSkills', () => ({
notifyInstalledAgentSkillsChanged: hookMocks.notifyInstalledAgentSkillsChanged,
useInstalledAgentSkill: hookMocks.useInstalledAgentSkill
}))
vi.mock('@/store', () => {
const useAppStore = Object.assign(
(selector: (state: typeof storeMocks.state) => unknown) => selector(storeMocks.state),
{ getState: () => storeMocks.state }
)
return { useAppStore }
})
vi.mock('sonner', () => ({
toast: {
error: toastMocks.error,
message: toastMocks.message
}
}))
function fallback(
patch: Partial<ManagedAgentSkillFallback> & Pick<ManagedAgentSkillFallback, 'context'>
): ManagedAgentSkillFallback {
@@ -50,6 +103,20 @@ function fallback(
beforeEach(() => {
cliPrerequisiteMocks.ensureOrcaCliAvailableForAgentSkillTerminal.mockReset()
hookMocks.notifyInstalledAgentSkillsChanged.mockReset()
hookMocks.useInstalledAgentSkill.mockClear()
storeMocks.state.settings = getDefaultSettings('/tmp')
storeMocks.state.updateSettings.mockReset()
storeMocks.state.updateSettings.mockImplementation(async (updates) => {
storeMocks.state.settings = storeMocks.state.settings
? { ...storeMocks.state.settings, ...updates }
: null
})
storeMocks.state.openSettingsPage.mockReset()
storeMocks.state.openSettingsTarget.mockReset()
toastMocks.error.mockReset()
toastMocks.message.mockReset()
document.body.innerHTML = ''
})
describe('ManagedAgentSkillSetupDialogHost copy', () => {
@@ -184,3 +251,118 @@ describe('ManagedAgentSkillSetupDialogHost queue state', () => {
})
})
})
describe('ManagedAgentSkillSetupDialogHost rendering', () => {
async function renderHost(): Promise<{
root: Root
emitFallback: (event: ManagedAgentSkillFallback) => void
}> {
let fallbackListener: ((event: ManagedAgentSkillFallback) => void) | null = null
window.api = {
skills: {
onManagedFallback: vi.fn((listener) => {
fallbackListener = listener
return vi.fn()
}),
onManagedUpdated: vi.fn(() => vi.fn())
}
} as unknown as typeof window.api
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
await act(async () => {
root.render(React.createElement(ManagedAgentSkillSetupDialogHost))
})
return {
root,
emitFallback: (event) => {
if (!fallbackListener) {
throw new Error('fallback listener was not registered')
}
fallbackListener(event)
}
}
}
it('turns off managed skill setup prompts when Don’t show again is clicked', async () => {
const { root, emitFallback } = await renderHost()
await act(async () => {
emitFallback(fallback({ context: 'agent-orchestration' }))
})
const button = Array.from(document.querySelectorAll('button')).find(
(entry) => entry.textContent === "Don't show again"
)
if (!button) {
throw new Error("Don't show again button was not rendered")
}
await act(async () => {
button.dispatchEvent(new MouseEvent('click', { bubbles: true }))
})
expect(storeMocks.state.updateSettings).toHaveBeenCalledWith({
managedAgentSkillSetupPromptsEnabled: false
})
expect(toastMocks.message).toHaveBeenCalledWith(
'Agent skill setup prompts are off',
expect.objectContaining({
description: 'Turn them back on in Settings → Agents.',
action: expect.objectContaining({ label: 'Open Settings' })
})
)
const toastOptions = toastMocks.message.mock.calls[0]?.[1]
toastOptions?.action?.onClick()
expect(storeMocks.state.openSettingsTarget).toHaveBeenCalledWith({
pane: 'agents',
repoId: null
})
expect(storeMocks.state.openSettingsPage).toHaveBeenCalledOnce()
root.unmount()
})
it('keeps the modal open when Don’t show again cannot persist', async () => {
storeMocks.state.updateSettings.mockResolvedValue(undefined)
const { root, emitFallback } = await renderHost()
await act(async () => {
emitFallback(fallback({ context: 'agent-orchestration' }))
})
const button = Array.from(document.querySelectorAll('button')).find(
(entry) => entry.textContent === "Don't show again"
)
if (!button) {
throw new Error("Don't show again button was not rendered")
}
await act(async () => {
button.dispatchEvent(new MouseEvent('click', { bubbles: true }))
})
expect(toastMocks.error).toHaveBeenCalledWith(
"Couldn't turn off agent skill setup prompts",
expect.objectContaining({
description: 'Try again from Settings → Agents.',
action: expect.objectContaining({ label: 'Open Settings' })
})
)
expect(toastMocks.message).not.toHaveBeenCalled()
expect(document.body.textContent).toContain('Agent skill setup needed')
root.unmount()
})
it('does not show managed skill setup prompts when the setting is off', async () => {
storeMocks.state.settings = {
...getDefaultSettings('/tmp'),
managedAgentSkillSetupPromptsEnabled: false
}
const { root, emitFallback } = await renderHost()
await act(async () => {
emitFallback(fallback({ context: 'agent-orchestration' }))
})
expect(document.body.textContent).not.toContain('Agent skill setup needed')
root.unmount()
})
})
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Info, RefreshCw } from 'lucide-react'
import { toast } from 'sonner'
import type { ManagedAgentSkillFallback } from '../../../../shared/skills'
import { buildAgentFeatureSkillUpdateCommand } from '@/lib/agent-feature-install-commands'
import {
@@ -34,10 +35,9 @@ import {
isOrcaCliAvailableOnPath
} from '@/lib/agent-skill-cli-prerequisite'
import { basename } from '@/lib/path'
import { useAppStore } from '@/store'
import { translate } from '@/i18n/i18n'
const DISMISS_STORAGE_PREFIX = 'orca:managed-agent-skill-setup-dismissed:'
export function ManagedAgentSkillSetupDialogHost(): React.JSX.Element | null {
const [dialogState, setDialogState] = useState<ManagedAgentSkillDialogState>({
active: null,
@@ -46,6 +46,12 @@ export function ManagedAgentSkillSetupDialogHost(): React.JSX.Element | null {
const queuedKeysRef = useRef(new Set<string>())
const snoozedKeysRef = useRef(new Set<string>())
const [rechecking, setRechecking] = useState(false)
const setupPromptsEnabled = useAppStore(
(state) => state.settings?.managedAgentSkillSetupPromptsEnabled !== false
)
const updateSettings = useAppStore((state) => state.updateSettings)
const openSettingsPage = useAppStore((state) => state.openSettingsPage)
const openSettingsTarget = useAppStore((state) => state.openSettingsTarget)
const active = dialogState.active
const installedState = useInstalledAgentSkill(active?.skillName ?? 'linear-tickets', {
enabled: active !== null,
@@ -54,16 +60,29 @@ export function ManagedAgentSkillSetupDialogHost(): React.JSX.Element | null {
sourceKinds: active ? getInstalledStateSourceKinds(active.scope) : undefined
})
const enqueueFallback = useCallback((event: ManagedAgentSkillFallback): void => {
if (isDismissed(event.uiKey) || snoozedKeysRef.current.has(event.uiKey)) {
const enqueueFallback = useCallback(
(event: ManagedAgentSkillFallback): void => {
if (!setupPromptsEnabled || snoozedKeysRef.current.has(event.uiKey)) {
return
}
if (queuedKeysRef.current.has(event.uiKey)) {
return
}
queuedKeysRef.current.add(event.uiKey)
setDialogState((current) => enqueueManagedAgentSkillFallback(current, event))
},
[setupPromptsEnabled]
)
useEffect(() => {
if (setupPromptsEnabled) {
return
}
if (queuedKeysRef.current.has(event.uiKey)) {
return
}
queuedKeysRef.current.add(event.uiKey)
setDialogState((current) => enqueueManagedAgentSkillFallback(current, event))
}, [])
queuedKeysRef.current.clear()
snoozedKeysRef.current.clear()
setDialogState({ active: null, queue: [] })
setRechecking(false)
}, [setupPromptsEnabled])
const advanceQueue = useCallback((): void => {
setDialogState((current) => {
@@ -244,8 +263,59 @@ export function ManagedAgentSkillSetupDialogHost(): React.JSX.Element | null {
variant="ghost"
size="sm"
onClick={() => {
setDismissed(active.uiKey)
advanceQueue()
void (async () => {
await updateSettings({ managedAgentSkillSetupPromptsEnabled: false })
if (
useAppStore.getState().settings?.managedAgentSkillSetupPromptsEnabled !== false
) {
toast.error(
translate(
'auto.components.skills.ManagedAgentSkillSetupDialogHost.promptsDisableFailedToast',
"Couldn't turn off agent skill setup prompts"
),
{
description: translate(
'auto.components.skills.ManagedAgentSkillSetupDialogHost.promptsDisableFailedToastDescription',
'Try again from Settings → Agents.'
),
action: {
label: translate(
'auto.components.skills.ManagedAgentSkillSetupDialogHost.openSettings',
'Open Settings'
),
onClick: () => {
openSettingsTarget({ pane: 'agents', repoId: null })
openSettingsPage()
}
}
}
)
return
}
toast.message(
translate(
'auto.components.skills.ManagedAgentSkillSetupDialogHost.promptsDisabledToast',
'Agent skill setup prompts are off'
),
{
description: translate(
'auto.components.skills.ManagedAgentSkillSetupDialogHost.promptsDisabledToastDescription',
'Turn them back on in Settings → Agents.'
),
action: {
label: translate(
'auto.components.skills.ManagedAgentSkillSetupDialogHost.openSettings',
'Open Settings'
),
onClick: () => {
openSettingsTarget({ pane: 'agents', repoId: null })
openSettingsPage()
}
}
}
)
advanceQueue()
})()
}}
>
{translate(
@@ -269,19 +339,3 @@ export function ManagedAgentSkillSetupDialogHost(): React.JSX.Element | null {
</Dialog>
)
}
function isDismissed(uiKey: string): boolean {
try {
return window.localStorage.getItem(`${DISMISS_STORAGE_PREFIX}${uiKey}`) === 'true'
} catch {
return false
}
}
function setDismissed(uiKey: string): void {
try {
window.localStorage.setItem(`${DISMISS_STORAGE_PREFIX}${uiKey}`, 'true')
} catch {
// Local storage can be unavailable in constrained test/browser contexts.
}
}
+19
View File
@@ -3316,6 +3316,11 @@
"recheck": "Re-check",
"dontShowAgain": "Don't show again",
"notNow": "Not now",
"promptsDisabledToast": "Agent skill setup prompts are off",
"promptsDisabledToastDescription": "Turn them back on in Settings → Agents.",
"promptsDisableFailedToast": "Couldn't turn off agent skill setup prompts",
"promptsDisableFailedToastDescription": "Try again from Settings → Agents.",
"openSettings": "Open Settings",
"linearWorktreeContext": "A worktree was started from a Linear task. {{actionLabel}} the Linear agent skill to enable agents to read and update Linear issues.",
"agentOrchestrationContext": "Orca Orchestration was used. {{actionLabel}} the orchestration skill to enable agents to coordinate reliably.",
"agentComputerUseContext": "Computer Use was used. {{actionLabel}} the Computer Use skill to enable agents to control apps reliably.",
@@ -4390,6 +4395,20 @@
"experimental": "experimental"
}
}
},
"setup": {
"prompts": {
"title": "Show agent skill setup prompts",
"description": "When an Orca workflow needs a missing or outdated managed skill, show a prompt to install or update it.",
"search": {
"agent": "agent",
"skills": "skills",
"setup": "setup",
"prompt": "prompt",
"install": "install",
"update": "update"
}
}
}
}
}
+19
View File
@@ -3316,6 +3316,11 @@
"recheck": "Volver a comprobar",
"dontShowAgain": "No volver a mostrar",
"notNow": "Ahora no",
"promptsDisabledToast": "Los avisos de configuración de habilidades de agente están desactivados",
"promptsDisabledToastDescription": "Puedes volver a activarlos en Configuración → Agentes.",
"promptsDisableFailedToast": "No se pudieron desactivar los avisos de configuración de habilidades de agente",
"promptsDisableFailedToastDescription": "Inténtalo de nuevo desde Configuración → Agentes.",
"openSettings": "Abrir Configuración",
"linearWorktreeContext": "A worktree was started from a Linear task. {{actionLabel}} the Linear agent skill to enable agents to read and update Linear issues.",
"agentOrchestrationContext": "Orca Orchestration was used. {{actionLabel}} the orchestration skill to enable agents to coordinate reliably.",
"agentComputerUseContext": "Computer Use was used. {{actionLabel}} the Computer Use skill to enable agents to control apps reliably.",
@@ -4390,6 +4395,20 @@
"experimental": "experimental"
}
}
},
"setup": {
"prompts": {
"title": "Mostrar avisos de configuración de habilidades de agente",
"description": "Cuando un flujo de trabajo de Orca necesite una habilidad administrada faltante o desactualizada, muestra un aviso para instalarla o actualizarla.",
"search": {
"agent": "agente",
"skills": "habilidades",
"setup": "configuración",
"prompt": "aviso",
"install": "instalar",
"update": "actualizar"
}
}
}
}
}
+19
View File
@@ -3316,6 +3316,11 @@
"recheck": "再チェック",
"dontShowAgain": "今後表示しない",
"notNow": "今はしない",
"promptsDisabledToast": "エージェントスキル設定プロンプトはオフです",
"promptsDisabledToastDescription": "Settings → Agents で再度オンにできます。",
"promptsDisableFailedToast": "エージェントスキル設定プロンプトをオフにできませんでした",
"promptsDisableFailedToastDescription": "Settings → Agents からもう一度お試しください。",
"openSettings": "設定を開く",
"linearWorktreeContext": "A worktree was started from a Linear task. {{actionLabel}} the Linear agent skill to enable agents to read and update Linear issues.",
"agentOrchestrationContext": "Orca Orchestration was used. {{actionLabel}} the orchestration skill to enable agents to coordinate reliably.",
"agentComputerUseContext": "Computer Use was used. {{actionLabel}} the Computer Use skill to enable agents to control apps reliably.",
@@ -4375,6 +4380,20 @@
"experimental": "experimental"
}
}
},
"setup": {
"prompts": {
"title": "エージェントスキル設定プロンプトを表示",
"description": "Orca のワークフローで未インストールまたは古い管理対象スキルが必要になったとき、インストールまたは更新のプロンプトを表示します。",
"search": {
"agent": "エージェント",
"skills": "スキル",
"setup": "設定",
"prompt": "プロンプト",
"install": "インストール",
"update": "更新"
}
}
}
}
}
+19
View File
@@ -3316,6 +3316,11 @@
"recheck": "다시 확인",
"dontShowAgain": "다시 표시하지 않기",
"notNow": "나중에",
"promptsDisabledToast": "에이전트 스킬 설정 프롬프트가 꺼져 있습니다",
"promptsDisabledToastDescription": "Settings → Agents에서 다시 켤 수 있습니다.",
"promptsDisableFailedToast": "에이전트 스킬 설정 프롬프트를 끌 수 없습니다",
"promptsDisableFailedToastDescription": "Settings → Agents에서 다시 시도하세요.",
"openSettings": "설정 열기",
"linearWorktreeContext": "A worktree was started from a Linear task. {{actionLabel}} the Linear agent skill to enable agents to read and update Linear issues.",
"agentOrchestrationContext": "Orca Orchestration was used. {{actionLabel}} the orchestration skill to enable agents to coordinate reliably.",
"agentComputerUseContext": "Computer Use was used. {{actionLabel}} the Computer Use skill to enable agents to control apps reliably.",
@@ -4375,6 +4380,20 @@
"experimental": "experimental"
}
}
},
"setup": {
"prompts": {
"title": "에이전트 스킬 설정 프롬프트 표시",
"description": "Orca 워크플로에 누락되었거나 오래된 관리형 스킬이 필요하면 설치 또는 업데이트 프롬프트를 표시합니다.",
"search": {
"agent": "에이전트",
"skills": "스킬",
"setup": "설정",
"prompt": "프롬프트",
"install": "설치",
"update": "업데이트"
}
}
}
}
}
+19
View File
@@ -3316,6 +3316,11 @@
"recheck": "重新检查",
"dontShowAgain": "不再显示",
"notNow": "稍后",
"promptsDisabledToast": "代理技能设置提示已关闭",
"promptsDisabledToastDescription": "可在 Settings → Agents 中重新开启。",
"promptsDisableFailedToast": "无法关闭代理技能设置提示",
"promptsDisableFailedToastDescription": "请从 Settings → Agents 重试。",
"openSettings": "打开设置",
"linearWorktreeContext": "A worktree was started from a Linear task. {{actionLabel}} the Linear agent skill to enable agents to read and update Linear issues.",
"agentOrchestrationContext": "Orca Orchestration was used. {{actionLabel}} the orchestration skill to enable agents to coordinate reliably.",
"agentComputerUseContext": "Computer Use was used. {{actionLabel}} the Computer Use skill to enable agents to control apps reliably.",
@@ -4375,6 +4380,20 @@
"experimental": "experimental"
}
}
},
"setup": {
"prompts": {
"title": "显示代理技能设置提示",
"description": "当 Orca 工作流需要缺失或过期的托管技能时,显示安装或更新提示。",
"search": {
"agent": "代理",
"skills": "技能",
"setup": "设置",
"prompt": "提示",
"install": "安装",
"update": "更新"
}
}
}
}
}
+1
View File
@@ -316,6 +316,7 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
agentDefaultEnv: { ...DEFAULT_TUI_AGENT_ENV },
agentYoloDefaultsMigrated: true,
agentStatusHooksEnabled: true,
managedAgentSkillSetupPromptsEnabled: true,
// Why: keep silent skill mutation opt-in while the verified updater is experimental.
managedAgentSkillBackgroundUpdatesEnabled: false,
tabAutoGenerateTitle: false,
+3
View File
@@ -2648,6 +2648,9 @@ export type GlobalSettings = {
/** Why: disabling must persist so startup does not reinstall global agent
* hook entries right after the user removes them from Settings or CLI. */
agentStatusHooksEnabled: boolean
/** Why: users can silence managed-skill setup prompts from the modal, but
* the prompt should be easy to restore from normal Agents settings. */
managedAgentSkillSetupPromptsEnabled?: boolean
/** Why: verified background updates mutate user skill files, so this remains
* opt-in while experimental; off makes updates manual. */
managedAgentSkillBackgroundUpdatesEnabled?: boolean