mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 00:03:08 +00:00
Restore Linear agent-skill setup prompt and add provider-card install CTA
PR #5071 (36277801e) accidentally dropped the LinearAgentSkillSetupPrompt
modal from WorktreeCard, orphaning the component. Restore the exact
wiring: render on the active worktree when it has a linked Linear issue.
Also surface the decoupled orca-linear agent skill on the Linear task
provider settings card: install state via useInstalledAgentSkillNames,
copyable install/update command resolved for the agent runtime, and a
remote-setup note when a runtime environment is active. The legacy-aware
update-command selection moves into a shared lib module so the sidebar
prompt and the new CTA stay in sync.
This commit is contained in:
@@ -44,8 +44,10 @@ const updateCapableCallers = new Map<string, readonly string[]>([
|
||||
['ORCA_CLI_SKILL_UPDATE_COMMAND', 'installedCommand={updateCommand}']
|
||||
],
|
||||
[
|
||||
// Why: the single-skill update command selection moved into
|
||||
// getLinearAgentSkillUpdateCommand so the settings install CTA shares it.
|
||||
'src/renderer/src/components/sidebar/LinearAgentSkillSetupPrompt.tsx',
|
||||
['ORCA_LINEAR_SKILL_UPDATE_COMMAND', 'installedCommand={installedCommand}']
|
||||
['getLinearAgentSkillUpdateCommand', 'installedCommand={installedCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/sidebar/LinearAgentSkillSetupDialog.tsx',
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { act, type ComponentProps } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import type { DiscoveredSkill } from '../../../../shared/skills'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { TooltipProvider } from '../ui/tooltip'
|
||||
import { LinearAgentSkillInstallCta } from './linear-agent-skill-install-cta'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
skillState: {
|
||||
installed: false,
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
skills: [] as DiscoveredSkill[],
|
||||
refresh: vi.fn(async () => true)
|
||||
},
|
||||
useInstalledAgentSkillNames: vi.fn(),
|
||||
clipboardWrite: vi.fn(async () => {}),
|
||||
toastSuccess: vi.fn(),
|
||||
toastError: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/useInstalledAgentSkills', async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
useInstalledAgentSkillNames: mocks.useInstalledAgentSkillNames
|
||||
}))
|
||||
|
||||
vi.mock('./CliSkillRuntimeSetup', () => ({
|
||||
buildSkillCommandForRuntime: (command: string) => command
|
||||
}))
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: { success: mocks.toastSuccess, error: mocks.toastError }
|
||||
}))
|
||||
|
||||
let root: Root | null = null
|
||||
let container: HTMLDivElement | null = null
|
||||
|
||||
function discoveredSkill(overrides: Partial<DiscoveredSkill>): DiscoveredSkill {
|
||||
return {
|
||||
id: 'skill-1',
|
||||
name: 'orca-linear',
|
||||
description: null,
|
||||
providers: ['agent-skills'],
|
||||
sourceKind: 'home',
|
||||
sourceLabel: 'Agent skills home',
|
||||
rootPath: '/Users/test/.agents/skills',
|
||||
directoryPath: '/Users/test/.agents/skills/orca-linear',
|
||||
skillFilePath: '/Users/test/.agents/skills/orca-linear/SKILL.md',
|
||||
installed: true,
|
||||
fileCount: 1,
|
||||
updatedAt: null,
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
async function renderCta(
|
||||
props: Partial<ComponentProps<typeof LinearAgentSkillInstallCta>> = {}
|
||||
): Promise<HTMLDivElement> {
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
<TooltipProvider>
|
||||
<LinearAgentSkillInstallCta
|
||||
settings={{ activeRuntimeEnvironmentId: null, terminalWindowsShell: '' }}
|
||||
{...props}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
)
|
||||
})
|
||||
if (!container) {
|
||||
throw new Error('Container was not created')
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
describe('LinearAgentSkillInstallCta', () => {
|
||||
beforeEach(() => {
|
||||
mocks.skillState.installed = false
|
||||
mocks.skillState.loading = false
|
||||
mocks.skillState.error = null
|
||||
mocks.skillState.skills = []
|
||||
mocks.skillState.refresh.mockClear()
|
||||
mocks.useInstalledAgentSkillNames.mockReset()
|
||||
mocks.useInstalledAgentSkillNames.mockReturnValue(mocks.skillState)
|
||||
mocks.clipboardWrite.mockClear()
|
||||
mocks.toastSuccess.mockClear()
|
||||
mocks.toastError.mockClear()
|
||||
Object.defineProperty(window, 'api', {
|
||||
configurable: true,
|
||||
value: { ui: { writeClipboardText: mocks.clipboardWrite } }
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await act(async () => {
|
||||
root?.unmount()
|
||||
})
|
||||
}
|
||||
root = null
|
||||
container?.remove()
|
||||
container = null
|
||||
Reflect.deleteProperty(window, 'api')
|
||||
})
|
||||
|
||||
it('shows the install command and explanation when the skill is missing', async () => {
|
||||
const rendered = await renderCta()
|
||||
|
||||
expect(rendered.textContent).toContain('Agent skill:')
|
||||
expect(rendered.textContent).toContain('orca-linear')
|
||||
expect(rendered.textContent).toContain('Not installed')
|
||||
expect(rendered.textContent).toContain('Let your agents read and edit Linear tasks.')
|
||||
expect(rendered.textContent).toContain(
|
||||
'npx skills add https://github.com/stablyai/orca --skill orca-linear --global'
|
||||
)
|
||||
})
|
||||
|
||||
it('copies the install command to the clipboard', async () => {
|
||||
const rendered = await renderCta()
|
||||
|
||||
await act(async () => {
|
||||
rendered
|
||||
.querySelector<HTMLButtonElement>('button[aria-label="Copy command"]')
|
||||
?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
|
||||
expect(mocks.clipboardWrite).toHaveBeenCalledWith(
|
||||
'npx skills add https://github.com/stablyai/orca --skill orca-linear --global'
|
||||
)
|
||||
expect(mocks.toastSuccess).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows a subtle confirmation and the update command when installed', async () => {
|
||||
mocks.skillState.installed = true
|
||||
mocks.skillState.skills = [discoveredSkill({ name: 'orca-linear' })]
|
||||
|
||||
const rendered = await renderCta()
|
||||
|
||||
expect(rendered.textContent).toContain('Installed')
|
||||
expect(rendered.textContent).toContain('Agent skill installed. To update it, run:')
|
||||
expect(rendered.textContent).toContain('npx skills update orca-linear --global')
|
||||
expect(rendered.textContent).not.toContain('Not installed')
|
||||
})
|
||||
|
||||
it('updates through the legacy skill name when only linear-tickets is installed', async () => {
|
||||
mocks.skillState.installed = true
|
||||
mocks.skillState.skills = [
|
||||
discoveredSkill({
|
||||
name: 'linear-tickets',
|
||||
directoryPath: '/Users/test/.agents/skills/linear-tickets',
|
||||
skillFilePath: '/Users/test/.agents/skills/linear-tickets/SKILL.md'
|
||||
})
|
||||
]
|
||||
|
||||
const rendered = await renderCta()
|
||||
|
||||
expect(rendered.textContent).toContain('npx skills update linear-tickets --global')
|
||||
})
|
||||
|
||||
it('notes that remote agent environments need their own setup', async () => {
|
||||
const rendered = await renderCta({
|
||||
settings: { activeRuntimeEnvironmentId: 'runtime-1', terminalWindowsShell: '' }
|
||||
})
|
||||
|
||||
expect(rendered.textContent).toContain(
|
||||
'This installs host setup; remote agent environments may need separate setup.'
|
||||
)
|
||||
})
|
||||
|
||||
it('re-checks the skill scan on demand', async () => {
|
||||
const rendered = await renderCta()
|
||||
|
||||
await act(async () => {
|
||||
Array.from(rendered.querySelectorAll('button'))
|
||||
.find((button) => button.textContent?.includes('Re-check'))
|
||||
?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
})
|
||||
|
||||
expect(mocks.skillState.refresh).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,186 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Copy, RefreshCw } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { IntegrationStatusPill } from '@/components/integration-status-pill'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import {
|
||||
GLOBAL_AGENT_SKILL_SOURCE_KINDS,
|
||||
useInstalledAgentSkillNames
|
||||
} from '@/hooks/useInstalledAgentSkills'
|
||||
import {
|
||||
LINEAR_AGENT_SKILL_NAMES,
|
||||
ORCA_LINEAR_SKILL_INSTALL_COMMAND,
|
||||
ORCA_LINEAR_SKILL_NAME
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import { getLinearAgentSkillUpdateCommand } from '@/lib/linear-agent-skill-update-command'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { getLinearAgentSkillSetupInlineRuntimeCopy } from '../sidebar/linear-agent-skill-setup-copy'
|
||||
import {
|
||||
getCurrentPlatform,
|
||||
getLinearPromptAgentRuntime,
|
||||
getLinearPromptSkillDiscoveryTarget,
|
||||
type LinearAgentSkillPromptSettings
|
||||
} from '../sidebar/linear-agent-skill-runtime'
|
||||
import { buildSkillCommandForRuntime } from './CliSkillRuntimeSetup'
|
||||
import {
|
||||
useIntegrationCommandRowClass,
|
||||
useIntegrationSubordinateRowClass
|
||||
} from './integration-card-presentation'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
type LinearAgentSkillInstallCtaProps = {
|
||||
settings: LinearAgentSkillPromptSettings | null | undefined
|
||||
}
|
||||
|
||||
// Why: the Linear task provider and the orca-linear agent skill are decoupled
|
||||
// setups; this section bridges them so connecting the provider also surfaces
|
||||
// the skill agents need to read and edit Linear tasks.
|
||||
export function LinearAgentSkillInstallCta({
|
||||
settings
|
||||
}: LinearAgentSkillInstallCtaProps): React.JSX.Element {
|
||||
// Why: mirror the sidebar setup prompt's runtime resolution so both surfaces
|
||||
// agree on which machine (host vs. WSL distro) is scanned and installed to.
|
||||
const remote = Boolean(settings?.activeRuntimeEnvironmentId?.trim())
|
||||
const agentRuntime = useMemo(
|
||||
() => getLinearPromptAgentRuntime(settings, getCurrentPlatform(), remote),
|
||||
[remote, settings]
|
||||
)
|
||||
const skillDiscoveryTarget = useMemo(
|
||||
() => getLinearPromptSkillDiscoveryTarget(agentRuntime),
|
||||
[agentRuntime]
|
||||
)
|
||||
const skill = useInstalledAgentSkillNames(LINEAR_AGENT_SKILL_NAMES, {
|
||||
discoveryTarget: skillDiscoveryTarget,
|
||||
sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS
|
||||
})
|
||||
const command = useMemo(
|
||||
() =>
|
||||
buildSkillCommandForRuntime(
|
||||
skill.installed
|
||||
? getLinearAgentSkillUpdateCommand(skill.skills, skill.installed)
|
||||
: ORCA_LINEAR_SKILL_INSTALL_COMMAND,
|
||||
agentRuntime
|
||||
),
|
||||
[agentRuntime, skill.installed, skill.skills]
|
||||
)
|
||||
const subordinateRowClass = useIntegrationSubordinateRowClass('space-y-1.5')
|
||||
const commandRowClass = useIntegrationCommandRowClass()
|
||||
|
||||
const copyCommand = async (): Promise<void> => {
|
||||
try {
|
||||
await window.api.ui.writeClipboardText(command)
|
||||
toast.success(
|
||||
translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.copiedCommand',
|
||||
'Copied command.'
|
||||
)
|
||||
)
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.copyFailed',
|
||||
'Failed to copy command.'
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={subordinateRowClass}>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<p className="text-xs font-medium text-foreground">
|
||||
{translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.skillLabel',
|
||||
'Agent skill:'
|
||||
)}{' '}
|
||||
<span className="font-mono text-[11px]">{ORCA_LINEAR_SKILL_NAME}</span>
|
||||
</p>
|
||||
{skill.loading ? (
|
||||
<IntegrationStatusPill tone="neutral">
|
||||
{translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.checking',
|
||||
'Checking...'
|
||||
)}
|
||||
</IntegrationStatusPill>
|
||||
) : skill.installed ? (
|
||||
<IntegrationStatusPill tone="connected">
|
||||
{translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.installed',
|
||||
'Installed'
|
||||
)}
|
||||
</IntegrationStatusPill>
|
||||
) : (
|
||||
<IntegrationStatusPill tone="attention">
|
||||
{translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.notInstalled',
|
||||
'Not installed'
|
||||
)}
|
||||
</IntegrationStatusPill>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
className="ml-auto gap-1.5"
|
||||
onClick={() => void skill.refresh()}
|
||||
disabled={skill.loading}
|
||||
>
|
||||
<RefreshCw className={cn('size-3', skill.loading && 'animate-spin')} />
|
||||
{translate('auto.components.settings.linear.agent.skill.install.cta.recheck', 'Re-check')}
|
||||
</Button>
|
||||
</div>
|
||||
{skill.error ? <p className="text-xs text-destructive">{skill.error}</p> : null}
|
||||
{!skill.loading && (
|
||||
<>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{skill.installed
|
||||
? translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.installedDescription',
|
||||
'Agent skill installed. To update it, run:'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.description',
|
||||
'Let your agents read and edit Linear tasks.'
|
||||
)}
|
||||
</p>
|
||||
<div className={commandRowClass}>
|
||||
<code className="scrollbar-sleek min-w-0 flex-1 overflow-x-auto whitespace-nowrap">
|
||||
{command}
|
||||
</code>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="shrink-0"
|
||||
aria-label={translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.copyCommand',
|
||||
'Copy command'
|
||||
)}
|
||||
onClick={() => void copyCommand()}
|
||||
>
|
||||
<Copy className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}>
|
||||
{translate(
|
||||
'auto.components.settings.linear.agent.skill.install.cta.copyCommand',
|
||||
'Copy command'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{remote || agentRuntime.runtime === 'wsl' ? (
|
||||
<p className="text-[11px] text-muted-foreground/70">
|
||||
{getLinearAgentSkillSetupInlineRuntimeCopy(remote, agentRuntime)}
|
||||
</p>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
import { act } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { getExecutionHostLabel } from '../../../../shared/execution-host'
|
||||
import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context'
|
||||
import { TooltipProvider } from '../ui/tooltip'
|
||||
import { LinearIntegrationCard } from './task-tracker-integration-cards'
|
||||
|
||||
const LOCAL_HOST_LABEL = getExecutionHostLabel('local')
|
||||
@@ -86,12 +87,27 @@ async function renderCard(): Promise<HTMLDivElement> {
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
await act(async () => {
|
||||
root?.render(<LinearIntegrationCard />)
|
||||
root?.render(
|
||||
<TooltipProvider>
|
||||
<LinearIntegrationCard />
|
||||
</TooltipProvider>
|
||||
)
|
||||
})
|
||||
return container
|
||||
}
|
||||
|
||||
describe('LinearIntegrationCard account scope', () => {
|
||||
beforeEach(() => {
|
||||
// Why: the embedded agent-skill CTA scans installed skills through
|
||||
// window.api; without a stub it renders a scan error instead of a state.
|
||||
Object.defineProperty(window, 'api', {
|
||||
configurable: true,
|
||||
value: {
|
||||
skills: { discover: async () => ({ skills: [], sources: [], scannedAt: 0 }) }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await act(async () => {
|
||||
@@ -102,6 +118,7 @@ describe('LinearIntegrationCard account scope', () => {
|
||||
container?.remove()
|
||||
container = null
|
||||
mocks.store.current = null
|
||||
Reflect.deleteProperty(window, 'api')
|
||||
})
|
||||
|
||||
it('shows local-client account ownership when Linear is disconnected', async () => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context'
|
||||
import { useAppStore } from '@/store'
|
||||
import { IntegrationCardDetails, IntegrationCardShell } from './integration-card-shell'
|
||||
import { useIntegrationSubordinateRowClass } from './integration-card-presentation'
|
||||
import { LinearAgentSkillInstallCta } from './linear-agent-skill-install-cta'
|
||||
import { getProviderAccountScope } from './provider-account-scope'
|
||||
import { ProviderHostScopeControl } from './ProviderHostScopeControl'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
@@ -200,6 +201,7 @@ export function LinearIntegrationCard(): React.JSX.Element {
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
<LinearAgentSkillInstallCta settings={settings} />
|
||||
</IntegrationCardDetails>
|
||||
|
||||
<LinearApiKeyDialog
|
||||
|
||||
@@ -5,17 +5,13 @@ import type { ProjectExecutionRuntimeResolution } from '../../../../shared/proje
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
GLOBAL_AGENT_SKILL_SOURCE_KINDS,
|
||||
hasInstalledAgentSkill,
|
||||
useInstalledAgentSkillNames
|
||||
} from '@/hooks/useInstalledAgentSkills'
|
||||
import {
|
||||
LINEAR_AGENT_SKILL_NAMES,
|
||||
LINEAR_TICKETS_SKILL_NAME,
|
||||
LINEAR_TICKETS_SKILL_UPDATE_COMMAND,
|
||||
ORCA_LINEAR_SKILL_NAME,
|
||||
ORCA_LINEAR_SKILL_INSTALL_COMMAND,
|
||||
ORCA_LINEAR_SKILL_UPDATE_COMMAND
|
||||
ORCA_LINEAR_SKILL_INSTALL_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import { getLinearAgentSkillUpdateCommand } from '@/lib/linear-agent-skill-update-command'
|
||||
import {
|
||||
ensureOrcaCliAvailableForAgentSkillTerminal,
|
||||
isOrcaCliAvailableOnPath
|
||||
@@ -118,21 +114,13 @@ export function LinearAgentSkillSetupPrompt({
|
||||
() => buildSkillCommandForRuntime(ORCA_LINEAR_SKILL_INSTALL_COMMAND, agentRuntime),
|
||||
[agentRuntime]
|
||||
)
|
||||
const canonicalSkillInstalled = hasInstalledAgentSkill(skill.skills, ORCA_LINEAR_SKILL_NAME, {
|
||||
sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS
|
||||
})
|
||||
const legacySkillInstalled = hasInstalledAgentSkill(skill.skills, LINEAR_TICKETS_SKILL_NAME, {
|
||||
sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS
|
||||
})
|
||||
// Why: legacy-only installs must update the installed legacy skill, while
|
||||
// fresh/canonical/both-name states should move through the canonical name.
|
||||
const updateCommand =
|
||||
!skill.installed || canonicalSkillInstalled || !legacySkillInstalled
|
||||
? ORCA_LINEAR_SKILL_UPDATE_COMMAND
|
||||
: LINEAR_TICKETS_SKILL_UPDATE_COMMAND
|
||||
const installedCommand = useMemo(
|
||||
() => buildSkillCommandForRuntime(updateCommand, agentRuntime),
|
||||
[agentRuntime, updateCommand]
|
||||
() =>
|
||||
buildSkillCommandForRuntime(
|
||||
getLinearAgentSkillUpdateCommand(skill.skills, skill.installed),
|
||||
agentRuntime
|
||||
),
|
||||
[agentRuntime, skill.installed, skill.skills]
|
||||
)
|
||||
const terminalShellOverride = getLinearPromptTerminalShellOverride(
|
||||
currentPlatform,
|
||||
|
||||
@@ -23,6 +23,7 @@ import CacheTimer, { usePromptCacheCountdownStartedAt } from './CacheTimer'
|
||||
import WorktreeContextMenu from './WorktreeContextMenu'
|
||||
import { SshDisconnectedDialog } from './SshDisconnectedDialog'
|
||||
import { AutoRenameFailedDialog } from './AutoRenameFailedDialog'
|
||||
import { LinearAgentSkillSetupPrompt } from './LinearAgentSkillSetupPrompt'
|
||||
import WorktreeCardAgents from './WorktreeCardAgents'
|
||||
import { useWorktreeAgentRows } from './useWorktreeAgentRows'
|
||||
import { WorktreeCardStatusSlot } from './WorktreeCardStatusSlot'
|
||||
@@ -1727,6 +1728,15 @@ const WorktreeCard = React.memo(function WorktreeCard({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isActive && worktree.linkedLinearIssue ? (
|
||||
<LinearAgentSkillSetupPrompt
|
||||
linked
|
||||
remote={Boolean(repo?.connectionId || settings?.activeRuntimeEnvironmentId?.trim())}
|
||||
surface="modal"
|
||||
settings={settings}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* Why: inline agent list. Gated on the 'inline-agents' card
|
||||
property so users can hide it. Layout coupling: this block
|
||||
grows the card height dynamically — WorktreeList uses
|
||||
|
||||
@@ -8896,6 +8896,26 @@
|
||||
"reviewFirst": "Review first",
|
||||
"singular": "Global saves won't change 1 repository with its own recipe.",
|
||||
"tooltipTitle": "Repository overrides"
|
||||
},
|
||||
"linear": {
|
||||
"agent": {
|
||||
"skill": {
|
||||
"install": {
|
||||
"cta": {
|
||||
"copiedCommand": "Copied command.",
|
||||
"copyFailed": "Failed to copy command.",
|
||||
"skillLabel": "Agent skill:",
|
||||
"checking": "Checking...",
|
||||
"installed": "Installed",
|
||||
"notInstalled": "Not installed",
|
||||
"recheck": "Re-check",
|
||||
"installedDescription": "Agent skill installed. To update it, run:",
|
||||
"description": "Let your agents read and edit Linear tasks.",
|
||||
"copyCommand": "Copy command"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -8896,6 +8896,26 @@
|
||||
"reviewFirst": "Revisar primero",
|
||||
"singular": "Guardar a nivel global no cambiará 1 repositorio con su propia receta.",
|
||||
"tooltipTitle": "Anulaciones de repositorio"
|
||||
},
|
||||
"linear": {
|
||||
"agent": {
|
||||
"skill": {
|
||||
"install": {
|
||||
"cta": {
|
||||
"copiedCommand": "Copied command.",
|
||||
"copyFailed": "Failed to copy command.",
|
||||
"skillLabel": "Agent skill:",
|
||||
"checking": "Checking...",
|
||||
"installed": "Installed",
|
||||
"notInstalled": "Not installed",
|
||||
"recheck": "Re-check",
|
||||
"installedDescription": "Agent skill installed. To update it, run:",
|
||||
"description": "Let your agents read and edit Linear tasks.",
|
||||
"copyCommand": "Copy command"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -8896,6 +8896,26 @@
|
||||
"reviewFirst": "Review first",
|
||||
"singular": "Global saves won't change 1 repository with its own recipe.",
|
||||
"tooltipTitle": "Repository overrides"
|
||||
},
|
||||
"linear": {
|
||||
"agent": {
|
||||
"skill": {
|
||||
"install": {
|
||||
"cta": {
|
||||
"copiedCommand": "Copied command.",
|
||||
"copyFailed": "Failed to copy command.",
|
||||
"skillLabel": "Agent skill:",
|
||||
"checking": "Checking...",
|
||||
"installed": "Installed",
|
||||
"notInstalled": "Not installed",
|
||||
"recheck": "Re-check",
|
||||
"installedDescription": "Agent skill installed. To update it, run:",
|
||||
"description": "Let your agents read and edit Linear tasks.",
|
||||
"copyCommand": "Copy command"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -8896,6 +8896,26 @@
|
||||
"reviewFirst": "Review first",
|
||||
"singular": "Global saves won't change 1 repository with its own recipe.",
|
||||
"tooltipTitle": "Repository overrides"
|
||||
},
|
||||
"linear": {
|
||||
"agent": {
|
||||
"skill": {
|
||||
"install": {
|
||||
"cta": {
|
||||
"copiedCommand": "Copied command.",
|
||||
"copyFailed": "Failed to copy command.",
|
||||
"skillLabel": "Agent skill:",
|
||||
"checking": "Checking...",
|
||||
"installed": "Installed",
|
||||
"notInstalled": "Not installed",
|
||||
"recheck": "Re-check",
|
||||
"installedDescription": "Agent skill installed. To update it, run:",
|
||||
"description": "Let your agents read and edit Linear tasks.",
|
||||
"copyCommand": "Copy command"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -8896,6 +8896,26 @@
|
||||
"reviewFirst": "Review first",
|
||||
"singular": "Global saves won't change 1 repository with its own recipe.",
|
||||
"tooltipTitle": "Repository overrides"
|
||||
},
|
||||
"linear": {
|
||||
"agent": {
|
||||
"skill": {
|
||||
"install": {
|
||||
"cta": {
|
||||
"copiedCommand": "Copied command.",
|
||||
"copyFailed": "Failed to copy command.",
|
||||
"skillLabel": "Agent skill:",
|
||||
"checking": "Checking...",
|
||||
"installed": "Installed",
|
||||
"notInstalled": "Not installed",
|
||||
"recheck": "Re-check",
|
||||
"installedDescription": "Agent skill installed. To update it, run:",
|
||||
"description": "Let your agents read and edit Linear tasks.",
|
||||
"copyCommand": "Copy command"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { DiscoveredSkill } from '../../../shared/skills'
|
||||
import {
|
||||
GLOBAL_AGENT_SKILL_SOURCE_KINDS,
|
||||
hasInstalledAgentSkill
|
||||
} from '@/hooks/useInstalledAgentSkills'
|
||||
import {
|
||||
LINEAR_TICKETS_SKILL_NAME,
|
||||
LINEAR_TICKETS_SKILL_UPDATE_COMMAND,
|
||||
ORCA_LINEAR_SKILL_NAME,
|
||||
ORCA_LINEAR_SKILL_UPDATE_COMMAND
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
|
||||
// Why: legacy-only installs must update the installed legacy skill, while
|
||||
// fresh/canonical/both-name states should move through the canonical name.
|
||||
export function getLinearAgentSkillUpdateCommand(
|
||||
skills: readonly DiscoveredSkill[],
|
||||
installed: boolean
|
||||
): string {
|
||||
const canonicalSkillInstalled = hasInstalledAgentSkill(skills, ORCA_LINEAR_SKILL_NAME, {
|
||||
sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS
|
||||
})
|
||||
const legacySkillInstalled = hasInstalledAgentSkill(skills, LINEAR_TICKETS_SKILL_NAME, {
|
||||
sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS
|
||||
})
|
||||
return !installed || canonicalSkillInstalled || !legacySkillInstalled
|
||||
? ORCA_LINEAR_SKILL_UPDATE_COMMAND
|
||||
: LINEAR_TICKETS_SKILL_UPDATE_COMMAND
|
||||
}
|
||||
Reference in New Issue
Block a user