mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 08:03:14 +00:00
Address managed skill PR feedback
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
+2
-1
@@ -9,6 +9,7 @@ import { app, BrowserWindow, ipcMain, nativeTheme } from 'electron'
|
||||
import { electronApp, is } from '@electron-toolkit/utils'
|
||||
import * as QRCode from 'qrcode'
|
||||
import { Store, initDataPath } from './persistence'
|
||||
import { shouldEmitManagedAgentSkillFallback } from '../shared/skills'
|
||||
import { applyAppIcon } from './app-icon'
|
||||
import { StatsCollector, initStatsPath } from './stats/collector'
|
||||
import { ClaudeUsageStore, initClaudeUsagePath } from './claude-usage/store'
|
||||
@@ -1643,7 +1644,7 @@ app.whenReady().then(async () => {
|
||||
...(remoteRuntime ? { remoteRuntime } : {}),
|
||||
...(!remoteRuntime && discoveryTarget ? { discoveryTarget } : {})
|
||||
})
|
||||
if (result.status === 'fallback') {
|
||||
if (result.status === 'fallback' && shouldEmitManagedAgentSkillFallback(result)) {
|
||||
sendManagedSkillFallback(result)
|
||||
} else if (result.status === 'updated') {
|
||||
sendManagedSkillUpdated(result)
|
||||
|
||||
@@ -5,6 +5,8 @@ export function isDiscoveredManagedSkill(
|
||||
skill: DiscoveredSkill,
|
||||
skillName: ManagedAgentSkillName
|
||||
): boolean {
|
||||
// Why: frontmatter names can be display labels, while the folder basename is
|
||||
// the install identity used by the skills CLI.
|
||||
const expected = normalizeSkillName(skillName)
|
||||
return (
|
||||
normalizeSkillName(skill.name) === expected ||
|
||||
|
||||
@@ -68,7 +68,7 @@ export function selectSingleGlobalManagedSkillCandidate(args: {
|
||||
fallback: { reason: 'project-install', scope: 'project' }
|
||||
}
|
||||
}
|
||||
if (bundledOrPluginCandidates.length > 0 || homeCandidates.length !== 1) {
|
||||
if (bundledOrPluginCandidates.length > 0) {
|
||||
return {
|
||||
status: 'fallback',
|
||||
fallback: {
|
||||
@@ -82,6 +82,12 @@ export function selectSingleGlobalManagedSkillCandidate(args: {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (homeCandidates.length !== 1) {
|
||||
return {
|
||||
status: 'fallback',
|
||||
fallback: { reason: 'ambiguous-install', scope: 'global' }
|
||||
}
|
||||
}
|
||||
|
||||
const candidate = homeCandidates[0]
|
||||
if (candidate.directoryIsSymlink || candidate.skillFileIsSymlink) {
|
||||
|
||||
@@ -10,6 +10,7 @@ type ManagedSkillLockEntry = {
|
||||
skillFolderHash?: unknown
|
||||
}
|
||||
|
||||
// Why: managed-skill readiness validation currently trusts lockfile schema v3 only.
|
||||
const SUPPORTED_LOCKFILE_SCHEMA_VERSION = 3
|
||||
|
||||
export async function readManagedSkillLockEntry(args: {
|
||||
@@ -43,6 +44,7 @@ export async function readManagedSkillLockEntry(args: {
|
||||
if (!entry) {
|
||||
return { ok: false, reason: 'lock-entry-missing' }
|
||||
}
|
||||
// Why: only trust lock entries tied to Orca-managed source and canonical skill path.
|
||||
if (!isManagedLockEntryForSkill(entry, args.skillName)) {
|
||||
return { ok: false, reason: 'lock-entry-unmanaged-source' }
|
||||
}
|
||||
|
||||
+7
-3
@@ -6,6 +6,10 @@ import { createRoot, type Root } from 'react-dom/client'
|
||||
import type { CliInstallStatus } from '../../../../shared/cli-install-types'
|
||||
import type { DiscoveredSkill } from '../../../../shared/skills'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
LINEAR_TICKETS_SKILL_UPDATE_COMMAND,
|
||||
ORCA_LINEAR_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
LinearAgentSkillSetupPrompt,
|
||||
_linearAgentSkillSetupPromptInternalsForTests
|
||||
@@ -156,7 +160,7 @@ describe('LinearAgentSkillSetupPrompt update command', () => {
|
||||
await renderPrompt()
|
||||
|
||||
expect(mocks.panelProps.at(-1)).toEqual(
|
||||
expect.objectContaining({ installedCommand: 'npx skills update orca-linear --global' })
|
||||
expect.objectContaining({ installedCommand: ORCA_LINEAR_SKILL_UPDATE_COMMAND })
|
||||
)
|
||||
})
|
||||
|
||||
@@ -166,7 +170,7 @@ describe('LinearAgentSkillSetupPrompt update command', () => {
|
||||
await renderPrompt()
|
||||
|
||||
expect(mocks.panelProps.at(-1)).toEqual(
|
||||
expect.objectContaining({ installedCommand: 'npx skills update linear-tickets --global' })
|
||||
expect.objectContaining({ installedCommand: LINEAR_TICKETS_SKILL_UPDATE_COMMAND })
|
||||
)
|
||||
})
|
||||
|
||||
@@ -176,7 +180,7 @@ describe('LinearAgentSkillSetupPrompt update command', () => {
|
||||
await renderPrompt()
|
||||
|
||||
expect(mocks.panelProps.at(-1)).toEqual(
|
||||
expect.objectContaining({ installedCommand: 'npx skills update orca-linear --global' })
|
||||
expect.objectContaining({ installedCommand: ORCA_LINEAR_SKILL_UPDATE_COMMAND })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -91,22 +91,42 @@ export function ManagedAgentSkillSetupDialogHost(): React.JSX.Element | null {
|
||||
)
|
||||
|
||||
const recheck = useCallback(async (): Promise<void> => {
|
||||
if (!active) {
|
||||
if (!active || rechecking) {
|
||||
return
|
||||
}
|
||||
const recheckUiKey = active.uiKey
|
||||
setRechecking(true)
|
||||
try {
|
||||
const result = await window.api.skills.ensureManagedReady({ ...active.request, force: true })
|
||||
if (result.status === 'fallback') {
|
||||
setDialogState((current) => replaceActiveAfterManagedAgentSkillRecheck(current, result))
|
||||
setDialogState((current) => {
|
||||
if (current.active?.uiKey !== recheckUiKey) {
|
||||
return current
|
||||
}
|
||||
const previousKey = current.active.uiKey
|
||||
const next = replaceActiveAfterManagedAgentSkillRecheck(current, result)
|
||||
if (next.active?.uiKey !== previousKey) {
|
||||
queuedKeysRef.current.delete(previousKey)
|
||||
if (next.active) {
|
||||
queuedKeysRef.current.add(next.active.uiKey)
|
||||
}
|
||||
}
|
||||
return next
|
||||
})
|
||||
return
|
||||
}
|
||||
setDialogState((current) => {
|
||||
if (current.active?.uiKey !== recheckUiKey) {
|
||||
return current
|
||||
}
|
||||
queuedKeysRef.current.delete(recheckUiKey)
|
||||
return advanceManagedAgentSkillFallbackQueue(current)
|
||||
})
|
||||
notifyInstalledAgentSkillsChanged()
|
||||
advanceQueue()
|
||||
} finally {
|
||||
setRechecking(false)
|
||||
}
|
||||
}, [active, advanceQueue])
|
||||
}, [active, rechecking])
|
||||
|
||||
if (!active) {
|
||||
return null
|
||||
@@ -188,7 +208,13 @@ export function ManagedAgentSkillSetupDialogHost(): React.JSX.Element | null {
|
||||
/>
|
||||
) : (
|
||||
<div className="px-6 pt-4 pb-3">
|
||||
<Button type="button" variant="outline" size="sm" onClick={() => void recheck()}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={loading}
|
||||
onClick={() => void recheck()}
|
||||
>
|
||||
<RefreshCw className={loading ? 'size-3.5 animate-spin' : 'size-3.5'} />
|
||||
{translate(
|
||||
'auto.components.skills.ManagedAgentSkillSetupDialogHost.recheck',
|
||||
|
||||
@@ -95,7 +95,7 @@ export async function discoverInstalledAgentSkills(
|
||||
|
||||
const inFlightDiscovery = pendingDiscoveryByTarget.get(key)
|
||||
if (inFlightDiscovery) {
|
||||
if (!force || pendingDiscoverySatisfiesForcedRefreshByTarget.get(key)) {
|
||||
if (!force) {
|
||||
return inFlightDiscovery
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -2312,6 +2312,7 @@ function createSkillsApi(): NonNullable<Partial<PreloadApi>['skills']> {
|
||||
scannedAt: Date.now()
|
||||
})),
|
||||
ensureManagedReady: (request: ManagedAgentSkillEnsureRequest) => {
|
||||
// Why: web clients cannot inspect or mutate trusted host skill installs.
|
||||
const uiKey = ['remote', '', request.skillName, request.context].join(':')
|
||||
const cooldownUntil = cooldownUntilByKey.get(uiKey)
|
||||
if (!request.force && cooldownUntil && cooldownUntil > Date.now()) {
|
||||
|
||||
@@ -11,6 +11,7 @@ export function buildAgentFeatureSkillInstallCommand(skillNames: readonly string
|
||||
if (skillNames.length === 0) {
|
||||
throw new Error('At least one skill name is required.')
|
||||
}
|
||||
// Why: keep setup commands fully non-interactive in both npx and skills layers.
|
||||
return `npx --yes skills add ${ORCA_SKILLS_REPOSITORY_URL} --skill ${skillNames.join(' ')} --global --yes`
|
||||
}
|
||||
|
||||
@@ -19,6 +20,7 @@ export function buildAgentFeatureSkillUpdateCommand(skillName: string): string {
|
||||
if (!trimmedSkillName) {
|
||||
throw new Error('A skill name is required.')
|
||||
}
|
||||
// Why: keep setup commands fully non-interactive in both npx and skills layers.
|
||||
return `npx --yes skills update ${trimmedSkillName} --global --yes`
|
||||
}
|
||||
|
||||
|
||||
@@ -313,6 +313,7 @@ export function getDefaultSettings(homedir: string): GlobalSettings {
|
||||
agentDefaultEnv: { ...DEFAULT_TUI_AGENT_ENV },
|
||||
agentYoloDefaultsMigrated: true,
|
||||
agentStatusHooksEnabled: true,
|
||||
// Why: keep managed skill readiness proactive; users can opt out in Agents settings.
|
||||
managedAgentSkillBackgroundUpdatesEnabled: true,
|
||||
tabAutoGenerateTitle: false,
|
||||
confirmClosePinnedTab: true,
|
||||
|
||||
Reference in New Issue
Block a user