mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
feat(settings): unify skill card status and add Linear agent-skill section
Show the freshness-aware status pill ("Up to date" / "Update available")
on the Orchestration skill card, matching the Computer Use card — it was
the only installed-skill card still limited to a presence-only "Installed"
pill because it omitted the freshnessSkillName prop.
Add a Linear capability section under Settings → Agents that only appears
once the Linear provider is connected. It mirrors the other skill panes:
an orca-linear skill card (Update / Re-check + freshness pill) and a "How
to use it" grid of copyable /orca-linear prompts.
The Orchestration and Linear "How to use it" surfaces now share extracted
SkillUsageExamplesSection / SkillUsageExampleDialog components (the old
orchestration-only dialog is renamed/generalized with a slashCommand prop).
The nav registry and Settings page gate the Linear section on one shared
useLinearProviderConnected() selector so the sidebar entry and the section
appear and disappear together.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { act, type ReactNode } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
ORCA_LINEAR_SKILL_INSTALL_COMMAND,
|
||||
ORCA_LINEAR_SKILL_NAME
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import { getLinearUsageExamples } from '@/lib/linear-usage-examples'
|
||||
import { LinearAgentSkillPane } from './LinearAgentSkillPane'
|
||||
|
||||
const UPDATE_COMMAND = 'npx skills update orca-linear --global'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
panelProps: [] as Record<string, unknown>[],
|
||||
runtime: 'native' as 'native' | 'wsl',
|
||||
skillInstalled: true
|
||||
}))
|
||||
|
||||
vi.mock('./AgentSkillSetupPanel', () => ({
|
||||
AgentSkillSetupPanel: (props: Record<string, unknown> & { footer?: ReactNode }) => {
|
||||
mocks.panelProps.push(props)
|
||||
return (
|
||||
<section>
|
||||
<h3>{String(props.title)}</h3>
|
||||
<span>{props.installed ? 'Installed' : 'Not installed'}</span>
|
||||
<code>{String(props.command)}</code>
|
||||
<code>{String(props.installedCommand)}</code>
|
||||
<span data-testid="freshness">{String(props.freshnessSkillName)}</span>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('./CliSkillRuntimeSetup', () => ({
|
||||
buildSkillCommandForRuntime: (command: string) => command,
|
||||
ensureWslCliAvailableForAgentSkillTerminal: vi.fn(),
|
||||
getWslCliDistroRequest: () => undefined
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/linear-agent-skill-update-command', () => ({
|
||||
getLinearAgentSkillUpdateCommand: () => UPDATE_COMMAND
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/useInstalledAgentSkills', () => ({
|
||||
GLOBAL_AGENT_SKILL_SOURCE_KINDS: ['home'],
|
||||
useInstalledAgentSkillNames: () => ({
|
||||
installed: mocks.skillInstalled,
|
||||
loading: false,
|
||||
error: null,
|
||||
skills: [],
|
||||
refresh: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/useActiveProjectSkillRuntime', () => ({
|
||||
useActiveProjectSkillRuntime: () => ({
|
||||
discoveryTarget: undefined,
|
||||
agentRuntime: { runtime: mocks.runtime },
|
||||
terminalShellOverride: undefined,
|
||||
installDisabledReason: null
|
||||
})
|
||||
}))
|
||||
|
||||
let root: Root | null = null
|
||||
let container: HTMLDivElement | null = null
|
||||
|
||||
async function renderPane(): Promise<HTMLDivElement> {
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
await act(async () => {
|
||||
root?.render(<LinearAgentSkillPane />)
|
||||
})
|
||||
return container
|
||||
}
|
||||
|
||||
describe('LinearAgentSkillPane', () => {
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await act(async () => {
|
||||
root?.unmount()
|
||||
})
|
||||
}
|
||||
root = null
|
||||
container?.remove()
|
||||
container = null
|
||||
mocks.panelProps.length = 0
|
||||
mocks.runtime = 'native'
|
||||
mocks.skillInstalled = true
|
||||
})
|
||||
|
||||
it('renders the Linear skill card and the usage examples', () => {
|
||||
const markup = renderToStaticMarkup(<LinearAgentSkillPane />)
|
||||
|
||||
expect(markup).toContain('Linear skill')
|
||||
expect(markup).toContain('How to use it')
|
||||
const examples = getLinearUsageExamples()
|
||||
expect(examples).toHaveLength(5)
|
||||
for (const example of examples) {
|
||||
expect(markup).toContain(example.title)
|
||||
}
|
||||
})
|
||||
|
||||
it('passes the orca-linear install/update commands and freshness on a local runtime', async () => {
|
||||
await renderPane()
|
||||
|
||||
expect(mocks.panelProps.at(-1)).toEqual(
|
||||
expect.objectContaining({
|
||||
command: ORCA_LINEAR_SKILL_INSTALL_COMMAND,
|
||||
installedCommand: UPDATE_COMMAND,
|
||||
freshnessSkillName: ORCA_LINEAR_SKILL_NAME
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('drops freshness on a WSL runtime the local scan cannot vouch for', async () => {
|
||||
mocks.runtime = 'wsl'
|
||||
await renderPane()
|
||||
|
||||
expect(mocks.panelProps.at(-1)?.freshnessSkillName).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,150 @@
|
||||
import { useMemo } from 'react'
|
||||
import { ArrowRightCircle, BookOpen, Link2, ListTodo, MessageSquarePlus } from 'lucide-react'
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import { LinearIcon } from '@/components/icons/LinearIcon'
|
||||
import {
|
||||
LINEAR_AGENT_SKILL_NAMES,
|
||||
ORCA_LINEAR_SKILL_INSTALL_COMMAND,
|
||||
ORCA_LINEAR_SKILL_NAME
|
||||
} from '@/lib/agent-feature-install-commands'
|
||||
import {
|
||||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
ensureOrcaCliAvailableForAgentSkillTerminal
|
||||
} from '@/lib/agent-skill-cli-prerequisite'
|
||||
import { getLinearAgentSkillUpdateCommand } from '@/lib/linear-agent-skill-update-command'
|
||||
import { getLinearUsageExamples } from '@/lib/linear-usage-examples'
|
||||
import type { SkillUsageExample } from '@/lib/skill-usage-example'
|
||||
import {
|
||||
GLOBAL_AGENT_SKILL_SOURCE_KINDS,
|
||||
useInstalledAgentSkillNames
|
||||
} from '@/hooks/useInstalledAgentSkills'
|
||||
import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime'
|
||||
import { AgentSkillSetupPanel } from './AgentSkillSetupPanel'
|
||||
import {
|
||||
buildSkillCommandForRuntime,
|
||||
ensureWslCliAvailableForAgentSkillTerminal,
|
||||
getWslCliDistroRequest
|
||||
} from './CliSkillRuntimeSetup'
|
||||
import { getLinearAgentSkillPaneSearchEntries } from './linear-agent-skill-search'
|
||||
import { SearchableSetting } from './SearchableSetting'
|
||||
import { SkillUsageExamplesSection } from './SkillUsageExamplesSection'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
export { getLinearAgentSkillPaneSearchEntries } from './linear-agent-skill-search'
|
||||
|
||||
const LINEAR_EXAMPLE_ICONS: Record<string, LucideIcon> = {
|
||||
'read-ticket': BookOpen,
|
||||
'post-update': MessageSquarePlus,
|
||||
'move-state': ArrowRightCircle,
|
||||
'attach-pr': Link2,
|
||||
'triage-followups': ListTodo
|
||||
}
|
||||
|
||||
function resolveLinearExampleIcon(example: SkillUsageExample): LucideIcon {
|
||||
return LINEAR_EXAMPLE_ICONS[example.id] ?? LinearIcon
|
||||
}
|
||||
|
||||
// Why: this section is rendered as a Settings section only when the Linear
|
||||
// provider is connected, so the orca-linear agent skill sits beside the
|
||||
// connection that makes it useful.
|
||||
export function LinearAgentSkillPane(): React.JSX.Element {
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const {
|
||||
installed: linearSkillInstalled,
|
||||
loading: linearSkillLoading,
|
||||
error: linearSkillError,
|
||||
skills: linearSkills,
|
||||
refresh: refreshLinearSkill
|
||||
} = useInstalledAgentSkillNames(LINEAR_AGENT_SKILL_NAMES, {
|
||||
discoveryTarget: activeSkillRuntime.discoveryTarget,
|
||||
sourceKinds: GLOBAL_AGENT_SKILL_SOURCE_KINDS
|
||||
})
|
||||
|
||||
const installCommand = useMemo(
|
||||
() =>
|
||||
activeSkillRuntime.installDisabledReason
|
||||
? ORCA_LINEAR_SKILL_INSTALL_COMMAND
|
||||
: buildSkillCommandForRuntime(
|
||||
ORCA_LINEAR_SKILL_INSTALL_COMMAND,
|
||||
activeSkillRuntime.agentRuntime
|
||||
),
|
||||
[activeSkillRuntime.agentRuntime, activeSkillRuntime.installDisabledReason]
|
||||
)
|
||||
const updateCommand = useMemo(() => {
|
||||
const command = getLinearAgentSkillUpdateCommand(linearSkills, linearSkillInstalled)
|
||||
return activeSkillRuntime.installDisabledReason
|
||||
? command
|
||||
: buildSkillCommandForRuntime(command, activeSkillRuntime.agentRuntime)
|
||||
}, [
|
||||
activeSkillRuntime.agentRuntime,
|
||||
activeSkillRuntime.installDisabledReason,
|
||||
linearSkillInstalled,
|
||||
linearSkills
|
||||
])
|
||||
|
||||
return (
|
||||
<SearchableSetting
|
||||
title={translate('auto.components.settings.LinearAgentSkillPane.title', 'Linear')}
|
||||
description={translate(
|
||||
'auto.components.settings.LinearAgentSkillPane.description',
|
||||
'Give agents the skill to read and update your linked Linear tickets.'
|
||||
)}
|
||||
keywords={getLinearAgentSkillPaneSearchEntries()[0].keywords}
|
||||
className="space-y-5 py-2"
|
||||
>
|
||||
<AgentSkillSetupPanel
|
||||
title={translate(
|
||||
'auto.components.settings.LinearAgentSkillPane.skillTitle',
|
||||
'Linear skill'
|
||||
)}
|
||||
description={translate(
|
||||
'auto.components.settings.LinearAgentSkillPane.skillDescription',
|
||||
'Enables agents to read linked tickets and post updates to Linear through Orca.'
|
||||
)}
|
||||
command={installCommand}
|
||||
installedCommand={updateCommand}
|
||||
terminalTitle="Linear skill setup"
|
||||
terminalAriaLabel="Linear skill install terminal"
|
||||
terminalWorktreeId="settings-linear-skill-terminal"
|
||||
terminalShellOverride={activeSkillRuntime.terminalShellOverride}
|
||||
installed={linearSkillInstalled}
|
||||
loading={linearSkillLoading}
|
||||
error={activeSkillRuntime.installDisabledReason ?? linearSkillError}
|
||||
installDisabled={Boolean(activeSkillRuntime.installDisabledReason)}
|
||||
icon={<LinearIcon className="size-5" />}
|
||||
preInstallNotice={AGENT_SKILL_CLI_PREREQUISITE_NOTICE}
|
||||
getPrerequisiteStatus={() =>
|
||||
activeSkillRuntime.agentRuntime?.runtime === 'wsl'
|
||||
? window.api.cli.getWslInstallStatus(
|
||||
getWslCliDistroRequest(activeSkillRuntime.agentRuntime)
|
||||
)
|
||||
: window.api.cli.getInstallStatus()
|
||||
}
|
||||
onBeforeOpenTerminal={async () => {
|
||||
await (activeSkillRuntime.agentRuntime?.runtime === 'wsl'
|
||||
? ensureWslCliAvailableForAgentSkillTerminal(activeSkillRuntime.agentRuntime)
|
||||
: ensureOrcaCliAvailableForAgentSkillTerminal())
|
||||
}}
|
||||
onRecheck={refreshLinearSkill}
|
||||
// Why: the local-host-only freshness scan cannot vouch for a WSL runtime,
|
||||
// so fall back to the presence-only pill there (mirrors the other skills).
|
||||
freshnessSkillName={
|
||||
activeSkillRuntime.agentRuntime?.runtime === 'wsl' ? undefined : ORCA_LINEAR_SKILL_NAME
|
||||
}
|
||||
/>
|
||||
|
||||
<SkillUsageExamplesSection
|
||||
heading={translate(
|
||||
'auto.components.settings.LinearAgentSkillPane.howToUse',
|
||||
'How to use it'
|
||||
)}
|
||||
description={translate(
|
||||
'auto.components.settings.LinearAgentSkillPane.howToUseDescription',
|
||||
'Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR.'
|
||||
)}
|
||||
examples={getLinearUsageExamples()}
|
||||
resolveIcon={resolveLinearExampleIcon}
|
||||
slashCommand={`/${ORCA_LINEAR_SKILL_NAME}`}
|
||||
/>
|
||||
</SearchableSetting>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { ArrowRightLeft, GitBranch, ListChecks, Workflow } from 'lucide-react'
|
||||
import { ArrowRightLeft, GitBranch, ListChecks, Workflow, type LucideIcon } from 'lucide-react'
|
||||
import { ORCHESTRATION_SKILL_NAME } from '@/lib/agent-feature-install-commands'
|
||||
import type { SkillUsageExample } from '@/lib/skill-usage-example'
|
||||
import {
|
||||
AGENT_SKILL_CLI_PREREQUISITE_NOTICE,
|
||||
ensureOrcaCliAvailableForAgentSkillTerminal
|
||||
@@ -26,7 +27,7 @@ import {
|
||||
getWslCliDistroRequest
|
||||
} from './CliSkillRuntimeSetup'
|
||||
import { OrchestrationSkillAgentCoverage } from './OrchestrationSkillAgentCoverage'
|
||||
import { OrchestrationExampleDialog } from './OrchestrationExamplesDialog'
|
||||
import { SkillUsageExamplesSection } from './SkillUsageExamplesSection'
|
||||
import { OrchestrationSkillPromptDialog } from './OrchestrationSkillPromptDialog'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
@@ -38,10 +39,13 @@ const EXAMPLE_ICONS = {
|
||||
'child-worktrees': Workflow
|
||||
} as const
|
||||
|
||||
function resolveOrchestrationExampleIcon(example: SkillUsageExample): LucideIcon {
|
||||
return EXAMPLE_ICONS[example.id as keyof typeof EXAMPLE_ICONS] ?? Workflow
|
||||
}
|
||||
|
||||
export function OrchestrationPane(): React.JSX.Element {
|
||||
const searchQuery = useAppStore((s) => s.settingsSearchQuery)
|
||||
const showOrchestration = matchesSettingsSearch(searchQuery, getOrchestrationPaneSearchEntries())
|
||||
const [selectedExampleId, setSelectedExampleId] = useState<string | null>(null)
|
||||
const [skillPromptOpen, setSkillPromptOpen] = useState(false)
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const orchestrationInstallCommand = !activeSkillRuntime.installDisabledReason
|
||||
@@ -150,6 +154,11 @@ export function OrchestrationPane(): React.JSX.Element {
|
||||
/>
|
||||
}
|
||||
onRecheck={refreshOrchestrationSkill}
|
||||
// Why: the local-host-only freshness scan cannot vouch for a WSL runtime,
|
||||
// so fall back to the presence-only pill there (mirrors the Computer Use card).
|
||||
freshnessSkillName={
|
||||
activeSkillRuntime.agentRuntime?.runtime === 'wsl' ? undefined : ORCHESTRATION_SKILL_NAME
|
||||
}
|
||||
/>
|
||||
|
||||
<OrchestrationSkillPromptDialog
|
||||
@@ -158,58 +167,19 @@ export function OrchestrationPane(): React.JSX.Element {
|
||||
onOpenChange={setSkillPromptOpen}
|
||||
/>
|
||||
|
||||
<div className="space-y-4 border-t border-border/60 pt-6">
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-medium text-foreground">
|
||||
{translate('auto.components.settings.OrchestrationPane.ae79504732', 'How to use it')}
|
||||
</h3>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.settings.OrchestrationPane.52e0634e2c',
|
||||
'Ask a coordinator agent to use orchestration for handoffs, worktree handovers, and sequential or parallel child agents.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{getOrchestrationUsageExamples().map((example) => {
|
||||
const Icon = EXAMPLE_ICONS[example.id as keyof typeof EXAMPLE_ICONS] ?? Workflow
|
||||
return (
|
||||
<button
|
||||
key={example.id}
|
||||
type="button"
|
||||
className="rounded-md border border-border/60 bg-muted/20 px-4 py-3 text-left transition-colors hover:bg-muted/35 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||
onClick={() => setSelectedExampleId(example.id)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border border-border bg-background text-muted-foreground">
|
||||
<Icon className="size-4" />
|
||||
</div>
|
||||
<div className="min-w-0 space-y-1">
|
||||
<p className="text-sm font-medium text-foreground">{example.title}</p>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">
|
||||
{example.summary}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{getOrchestrationUsageExamples().map((example) => {
|
||||
const Icon = EXAMPLE_ICONS[example.id as keyof typeof EXAMPLE_ICONS] ?? Workflow
|
||||
return (
|
||||
<OrchestrationExampleDialog
|
||||
key={`${example.id}-dialog`}
|
||||
example={example}
|
||||
icon={Icon}
|
||||
open={selectedExampleId === example.id}
|
||||
onOpenChange={(open) => setSelectedExampleId(open ? example.id : null)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<SkillUsageExamplesSection
|
||||
heading={translate(
|
||||
'auto.components.settings.OrchestrationPane.ae79504732',
|
||||
'How to use it'
|
||||
)}
|
||||
description={translate(
|
||||
'auto.components.settings.OrchestrationPane.52e0634e2c',
|
||||
'Ask a coordinator agent to use orchestration for handoffs, worktree handovers, and sequential or parallel child agents.'
|
||||
)}
|
||||
examples={getOrchestrationUsageExamples()}
|
||||
resolveIcon={resolveOrchestrationExampleIcon}
|
||||
slashCommand={`/${ORCHESTRATION_SKILL_NAME}`}
|
||||
/>
|
||||
</SearchableSetting>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ import { SshPane } from './SshPane'
|
||||
import { ExperimentalPane } from './ExperimentalPane'
|
||||
import { AgentsPane } from './AgentsPane'
|
||||
import { OrchestrationPane } from './OrchestrationPane'
|
||||
import { LinearAgentSkillPane } from './LinearAgentSkillPane'
|
||||
import { AccountsPane } from './AccountsPane'
|
||||
import { StatsPane } from '../stats/StatsPane'
|
||||
import { IntegrationsPane } from './IntegrationsPane'
|
||||
@@ -101,6 +102,7 @@ import {
|
||||
useInstalledAgentSkill
|
||||
} from '@/hooks/useInstalledAgentSkills'
|
||||
import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime'
|
||||
import { useLinearProviderConnected } from '@/hooks/useLinearProviderConnected'
|
||||
import { useSkillFreshness } from '@/hooks/useSkillFreshness'
|
||||
import { getSkillFreshnessDisplayStatus } from '@/lib/skill-freshness-display-status'
|
||||
import { deriveNeededSectionIds, getInitialMountedSectionIds } from './settings-load-performance'
|
||||
@@ -344,6 +346,9 @@ function Settings(): React.JSX.Element {
|
||||
const isMac = isMacUserAgent()
|
||||
const isWebClient = isWebClientLocation()
|
||||
const showDesktopOnlySettings = !isWebClient
|
||||
// Why: the Linear capability section mirrors the nav registry's gate so the
|
||||
// sidebar entry and the rendered section appear/disappear together.
|
||||
const linearConnected = useLinearProviderConnected()
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const orchestrationSkill = useInstalledAgentSkill(ORCHESTRATION_SKILL_NAME, {
|
||||
discoveryTarget: activeSkillRuntime.discoveryTarget,
|
||||
@@ -1256,6 +1261,20 @@ function Settings(): React.JSX.Element {
|
||||
{isSectionMounted('orchestration') ? <OrchestrationPane /> : null}
|
||||
</SettingsSection>
|
||||
|
||||
{linearConnected ? (
|
||||
<SettingsSection
|
||||
id="linear"
|
||||
title={translate('auto.components.settings.Settings.linearTitle', 'Linear')}
|
||||
description={translate(
|
||||
'auto.components.settings.Settings.linearDescription',
|
||||
'Give agents the skill to read and update your linked Linear tickets.'
|
||||
)}
|
||||
searchEntries={getSectionSearchEntries('linear')}
|
||||
>
|
||||
{isSectionMounted('linear') ? <LinearAgentSkillPane /> : null}
|
||||
</SettingsSection>
|
||||
) : null}
|
||||
|
||||
{showDesktopOnlySettings ? (
|
||||
<>
|
||||
<SettingsSection
|
||||
|
||||
+20
-19
@@ -1,7 +1,6 @@
|
||||
import { Fragment } from 'react'
|
||||
import { Copy, type LucideIcon } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { ORCHESTRATION_SKILL_NAME } from '@/lib/agent-feature-install-commands'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
@@ -11,14 +10,15 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import type { OrchestrationUsageExample } from '@/lib/orchestration-usage-examples'
|
||||
import type { SkillUsageExample } from '@/lib/skill-usage-example'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
const ORCHESTRATION_SKILL_SLASH_COMMAND = `/${ORCHESTRATION_SKILL_NAME}`
|
||||
|
||||
function OrchestrationExamplePromptText(props: { prompt: string }): React.JSX.Element {
|
||||
const { prompt } = props
|
||||
const parts = prompt.split(ORCHESTRATION_SKILL_SLASH_COMMAND)
|
||||
function SkillUsageExamplePromptText(props: {
|
||||
prompt: string
|
||||
slashCommand: string
|
||||
}): React.JSX.Element {
|
||||
const { prompt, slashCommand } = props
|
||||
const parts = prompt.split(slashCommand)
|
||||
|
||||
if (parts.length === 1) {
|
||||
return <>{prompt}</>
|
||||
@@ -30,9 +30,7 @@ function OrchestrationExamplePromptText(props: { prompt: string }): React.JSX.El
|
||||
<Fragment key={index}>
|
||||
{part}
|
||||
{index < parts.length - 1 ? (
|
||||
<span className="font-semibold text-foreground">
|
||||
{ORCHESTRATION_SKILL_SLASH_COMMAND}
|
||||
</span>
|
||||
<span className="font-semibold text-foreground">{slashCommand}</span>
|
||||
) : null}
|
||||
</Fragment>
|
||||
))}
|
||||
@@ -40,20 +38,23 @@ function OrchestrationExamplePromptText(props: { prompt: string }): React.JSX.El
|
||||
)
|
||||
}
|
||||
|
||||
export function OrchestrationExampleDialog(props: {
|
||||
example: OrchestrationUsageExample
|
||||
export function SkillUsageExampleDialog(props: {
|
||||
example: SkillUsageExample
|
||||
// Why: the copyable prompt highlights the skill's slash command so the reader
|
||||
// sees which skill to invoke; each skill passes its own (e.g. /orchestration).
|
||||
slashCommand: string
|
||||
icon?: LucideIcon
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
}): React.JSX.Element {
|
||||
const { example, icon: Icon, open, onOpenChange } = props
|
||||
const { example, slashCommand, icon: Icon, open, onOpenChange } = props
|
||||
|
||||
const copyPrompt = async (prompt: string): Promise<void> => {
|
||||
try {
|
||||
await window.api.ui.writeClipboardText(prompt)
|
||||
toast.success(
|
||||
translate(
|
||||
'auto.components.settings.OrchestrationExamplesDialog.80c6f2feb8',
|
||||
'auto.components.settings.SkillUsageExampleDialog.copiedPrompt',
|
||||
'Copied example prompt.'
|
||||
)
|
||||
)
|
||||
@@ -62,7 +63,7 @@ export function OrchestrationExampleDialog(props: {
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
'auto.components.settings.OrchestrationExamplesDialog.4e46da1889',
|
||||
'auto.components.settings.SkillUsageExampleDialog.copyFailed',
|
||||
'Failed to copy prompt.'
|
||||
)
|
||||
)
|
||||
@@ -93,7 +94,7 @@ export function OrchestrationExampleDialog(props: {
|
||||
<div className="px-6 py-5">
|
||||
<div className="group relative rounded-md border border-border/70 bg-editor-surface shadow-xs">
|
||||
<p className="px-3 py-3 pr-11 font-mono text-[12px] leading-relaxed text-foreground">
|
||||
<OrchestrationExamplePromptText prompt={example.prompt} />
|
||||
<SkillUsageExamplePromptText prompt={example.prompt} slashCommand={slashCommand} />
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -101,7 +102,7 @@ export function OrchestrationExampleDialog(props: {
|
||||
size="icon-xs"
|
||||
className="absolute top-2 right-2 shrink-0 opacity-70 transition-opacity group-hover:opacity-100"
|
||||
aria-label={translate(
|
||||
'auto.components.settings.OrchestrationExamplesDialog.969cec9739',
|
||||
'auto.components.settings.SkillUsageExampleDialog.copyExampleAria',
|
||||
'Copy {{value0}} example prompt',
|
||||
{ value0: example.title }
|
||||
)}
|
||||
@@ -114,12 +115,12 @@ export function OrchestrationExampleDialog(props: {
|
||||
|
||||
<DialogFooter className="gap-2 border-t border-border/60 bg-muted/10 px-6 py-4">
|
||||
<Button type="button" variant="ghost" size="sm" onClick={() => onOpenChange(false)}>
|
||||
{translate('auto.components.settings.OrchestrationExamplesDialog.9b4c004998', 'Done')}
|
||||
{translate('auto.components.settings.SkillUsageExampleDialog.done', 'Done')}
|
||||
</Button>
|
||||
<Button type="button" size="sm" onClick={() => void copyPrompt(example.prompt)}>
|
||||
<Copy className="size-4" />
|
||||
{translate(
|
||||
'auto.components.settings.OrchestrationExamplesDialog.3d1aa105e3',
|
||||
'auto.components.settings.SkillUsageExampleDialog.copyPrompt',
|
||||
'Copy prompt'
|
||||
)}
|
||||
</Button>
|
||||
@@ -0,0 +1,66 @@
|
||||
import { useState } from 'react'
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import type { SkillUsageExample } from '@/lib/skill-usage-example'
|
||||
import { SkillUsageExampleDialog } from './SkillUsageExampleDialog'
|
||||
|
||||
// Why: orchestration and Linear both present "How to use it" example cards that
|
||||
// open a copyable prompt dialog; sharing one section keeps the two in lockstep.
|
||||
export function SkillUsageExamplesSection({
|
||||
heading,
|
||||
description,
|
||||
examples,
|
||||
resolveIcon,
|
||||
slashCommand
|
||||
}: {
|
||||
heading: string
|
||||
description: string
|
||||
examples: readonly SkillUsageExample[]
|
||||
resolveIcon: (example: SkillUsageExample) => LucideIcon
|
||||
slashCommand: string
|
||||
}): React.JSX.Element {
|
||||
const [selectedExampleId, setSelectedExampleId] = useState<string | null>(null)
|
||||
|
||||
return (
|
||||
<div className="space-y-4 border-t border-border/60 pt-6">
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-medium text-foreground">{heading}</h3>
|
||||
<p className="text-xs text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{examples.map((example) => {
|
||||
const Icon = resolveIcon(example)
|
||||
return (
|
||||
<button
|
||||
key={example.id}
|
||||
type="button"
|
||||
className="rounded-md border border-border/60 bg-muted/20 px-4 py-3 text-left transition-colors hover:bg-muted/35 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
|
||||
onClick={() => setSelectedExampleId(example.id)}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border border-border bg-background text-muted-foreground">
|
||||
<Icon className="size-4" />
|
||||
</div>
|
||||
<div className="min-w-0 space-y-1">
|
||||
<p className="text-sm font-medium text-foreground">{example.title}</p>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground">{example.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{examples.map((example) => (
|
||||
<SkillUsageExampleDialog
|
||||
key={`${example.id}-dialog`}
|
||||
example={example}
|
||||
icon={resolveIcon(example)}
|
||||
slashCommand={slashCommand}
|
||||
open={selectedExampleId === example.id}
|
||||
onOpenChange={(open) => setSelectedExampleId(open ? example.id : null)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -23,6 +23,12 @@ const updateCapableCallers = new Map<string, readonly string[]>([
|
||||
'src/renderer/src/components/settings/ComputerUseSkillSetupPanel.tsx',
|
||||
['COMPUTER_USE_SKILL_UPDATE_COMMAND', 'installedCommand={updateCommand}']
|
||||
],
|
||||
[
|
||||
// Why: the Linear settings section shares getLinearAgentSkillUpdateCommand so
|
||||
// legacy-only installs still update through the installed skill name.
|
||||
'src/renderer/src/components/settings/LinearAgentSkillPane.tsx',
|
||||
['getLinearAgentSkillUpdateCommand', 'installedCommand={updateCommand}']
|
||||
],
|
||||
[
|
||||
'src/renderer/src/components/settings/EphemeralVmsPane.tsx',
|
||||
['EPHEMERAL_VMS_SKILL_UPDATE_COMMAND', 'installedCommand={updateCommand}']
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { translateSearchKeyword } from './settings-search-keywords'
|
||||
import { createLocalizedCatalog } from '@/i18n/localized-catalog'
|
||||
|
||||
export const getLinearAgentSkillPaneSearchEntries = createLocalizedCatalog(() => [
|
||||
{
|
||||
title: translate('auto.components.settings.linear.agent.skill.search.title', 'Linear'),
|
||||
description: translate(
|
||||
'auto.components.settings.linear.agent.skill.search.description',
|
||||
'Give agents the skill to read and update your linked Linear tickets.'
|
||||
),
|
||||
keywords: [
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.linear.agent.skill.search.linear',
|
||||
'linear'
|
||||
),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.linear.agent.skill.search.tickets',
|
||||
'tickets'
|
||||
),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.linear.agent.skill.search.issues',
|
||||
'issues'
|
||||
),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.linear.agent.skill.search.skill',
|
||||
'skill'
|
||||
),
|
||||
...translateSearchKeyword(
|
||||
'auto.components.settings.linear.agent.skill.search.orcaLinear',
|
||||
'orca-linear'
|
||||
)
|
||||
]
|
||||
}
|
||||
])
|
||||
@@ -0,0 +1,13 @@
|
||||
import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
// Why: the Settings sidebar registry and the Settings page must agree on whether
|
||||
// the Linear section is visible; sharing this selector keeps the nav entry and
|
||||
// the rendered section from drifting. The context-key guard rejects a status
|
||||
// fetched for a different runtime environment than the active one.
|
||||
export function useLinearProviderConnected(): boolean {
|
||||
const linearStatus = useAppStore((s) => s.linearStatus)
|
||||
const linearStatusContextKey = useAppStore((s) => s.linearStatusContextKey)
|
||||
const settings = useAppStore((s) => s.settings)
|
||||
return linearStatusContextKey === getProviderRuntimeContextKey(settings) && linearStatus.connected
|
||||
}
|
||||
@@ -13,13 +13,20 @@ const repo = {
|
||||
} satisfies Repo
|
||||
|
||||
function ids(
|
||||
args: { isMac?: boolean; isWindows?: boolean; isWebClient?: boolean; isDev?: boolean } = {}
|
||||
args: {
|
||||
isMac?: boolean
|
||||
isWindows?: boolean
|
||||
isWebClient?: boolean
|
||||
isDev?: boolean
|
||||
isLinearConnected?: boolean
|
||||
} = {}
|
||||
): string[] {
|
||||
return buildSettingsNavigationMetadata({
|
||||
isMac: args.isMac ?? false,
|
||||
isWindows: args.isWindows ?? false,
|
||||
isWebClient: args.isWebClient ?? false,
|
||||
isDev: args.isDev ?? false,
|
||||
isLinearConnected: args.isLinearConnected ?? false,
|
||||
repos: [repo]
|
||||
}).map((section) => section.id)
|
||||
}
|
||||
@@ -40,6 +47,27 @@ describe('settings navigation metadata', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('adds the Linear capability section right after Orchestration only when connected', () => {
|
||||
expect(ids()).not.toContain('linear')
|
||||
|
||||
const connectedIds = ids({ isLinearConnected: true })
|
||||
expect(connectedIds).toContain('linear')
|
||||
expect(connectedIds.indexOf('linear')).toBe(connectedIds.indexOf('orchestration') + 1)
|
||||
|
||||
const linearSection = buildSettingsNavigationMetadata({
|
||||
isMac: false,
|
||||
isWindows: false,
|
||||
isWebClient: false,
|
||||
isLinearConnected: true,
|
||||
repos: [repo]
|
||||
}).find((section) => section.id === 'linear')
|
||||
expect(linearSection?.group).toBe('capabilities')
|
||||
})
|
||||
|
||||
it('keeps the Linear capability section available on web clients when connected', () => {
|
||||
expect(ids({ isWebClient: true, isLinearConnected: true })).toContain('linear')
|
||||
})
|
||||
|
||||
it('places Mobile under Set Up instead of its own sidebar group', () => {
|
||||
const sections = buildSettingsNavigationMetadata({
|
||||
isMac: false,
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
Wrench
|
||||
} from 'lucide-react'
|
||||
import { OrcaLogoSettingsIcon } from '@/components/settings/orca-logo-settings-icon'
|
||||
import { LinearIcon } from '@/components/icons/LinearIcon'
|
||||
import type { Repo } from '../../../shared/types'
|
||||
import { getRepoKindLabel } from '../../../shared/repo-kind'
|
||||
import { useAppStore } from '@/store'
|
||||
@@ -55,6 +56,7 @@ import { getQuickCommandsPaneSearchEntries } from '@/components/settings/quick-c
|
||||
import { getBrowserPaneCombinedSearchEntries } from '@/components/settings/browser-pane-search'
|
||||
import { getNotificationsPaneSearchEntries } from '@/components/settings/notifications-search'
|
||||
import { getOrchestrationPaneSearchEntries } from '@/components/settings/orchestration-search'
|
||||
import { getLinearAgentSkillPaneSearchEntries } from '@/components/settings/linear-agent-skill-search'
|
||||
import {
|
||||
getRuntimeEnvironmentsSearchEntry,
|
||||
getWebRuntimeEnvironmentsSearchEntry
|
||||
@@ -78,6 +80,7 @@ import {
|
||||
useWindowsTerminalCapabilities
|
||||
} from '@/lib/windows-terminal-capabilities'
|
||||
import { getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client'
|
||||
import { useLinearProviderConnected } from '@/hooks/useLinearProviderConnected'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
export { isWebClientLocation } from '@/lib/web-client-location'
|
||||
@@ -113,6 +116,7 @@ export function buildSettingsNavigationMetadata({
|
||||
isWindowsTerminalHost = isWindows,
|
||||
isWebClient,
|
||||
isDev = import.meta.env.DEV,
|
||||
isLinearConnected = false,
|
||||
repos
|
||||
}: {
|
||||
isMac: boolean
|
||||
@@ -120,6 +124,7 @@ export function buildSettingsNavigationMetadata({
|
||||
isWindowsTerminalHost?: boolean
|
||||
isWebClient: boolean
|
||||
isDev?: boolean
|
||||
isLinearConnected?: boolean
|
||||
repos: readonly Repo[]
|
||||
}): SettingsNavSection[] {
|
||||
const showDesktopOnlySettings = !isWebClient
|
||||
@@ -179,6 +184,23 @@ export function buildSettingsNavigationMetadata({
|
||||
searchEntries: getOrchestrationPaneSearchEntries(),
|
||||
group: 'capabilities'
|
||||
},
|
||||
// Why: only surfaced once Linear is connected — a capability that needs a
|
||||
// linked provider before the agent skill has anything to act on.
|
||||
...(isLinearConnected
|
||||
? [
|
||||
{
|
||||
id: 'linear',
|
||||
title: translate('auto.hooks.useSettingsNavigationMetadata.linearTitle', 'Linear'),
|
||||
description: translate(
|
||||
'auto.hooks.useSettingsNavigationMetadata.linearDescription',
|
||||
'Give agents the skill to read and update your linked Linear tickets.'
|
||||
),
|
||||
icon: LinearIcon,
|
||||
searchEntries: getLinearAgentSkillPaneSearchEntries(),
|
||||
group: 'capabilities'
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(showDesktopOnlySettings
|
||||
? [
|
||||
{
|
||||
@@ -584,6 +606,7 @@ export function useSettingsNavigationMetadata(): SettingsNavSection[] {
|
||||
const isMac = isMacUserAgent()
|
||||
const isWindows = isWindowsUserAgent()
|
||||
const isWebClient = isWebClientLocation()
|
||||
const isLinearConnected = useLinearProviderConnected()
|
||||
const windowsTerminalCapabilityOwnerKey = getWindowsTerminalCapabilityOwnerKey(
|
||||
settings?.activeRuntimeEnvironmentId
|
||||
)
|
||||
@@ -607,9 +630,10 @@ export function useSettingsNavigationMetadata(): SettingsNavSection[] {
|
||||
isWindowsTerminalHost,
|
||||
isWebClient,
|
||||
isDev: import.meta.env.DEV,
|
||||
isLinearConnected,
|
||||
repos
|
||||
}),
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps -- activeLocale is read implicitly by the translate() calls inside buildSettingsNavigationMetadata; without it the memo keeps the previous language's sections.
|
||||
[isMac, isWindows, isWindowsTerminalHost, isWebClient, repos, activeLocale]
|
||||
[isMac, isWindows, isWindowsTerminalHost, isWebClient, isLinearConnected, repos, activeLocale]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -611,6 +611,17 @@
|
||||
"de50bef680": "macOS is blocking Orca notifications"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linear": {
|
||||
"usage": {
|
||||
"examples": {
|
||||
"readTicket": "Read the linked ticket",
|
||||
"postUpdate": "Post a progress update",
|
||||
"moveState": "Move the ticket forward",
|
||||
"attachPr": "Attach the PR link",
|
||||
"triageFollowups": "Triage and create follow-ups"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
@@ -728,7 +739,9 @@
|
||||
"devSearchKeywordSonner": "sonner",
|
||||
"devSearchKeywordError": "error",
|
||||
"devSearchKeywordNotification": "notification",
|
||||
"projectHostsSummary": "{{value0}} hosts"
|
||||
"projectHostsSummary": "{{value0}} hosts",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"useAppMenuPaste": {
|
||||
"pasteTooLarge": "Paste is too large."
|
||||
@@ -6004,13 +6017,6 @@
|
||||
"3743ed080c": "Set command",
|
||||
"f79084947b": "New app"
|
||||
},
|
||||
"OrchestrationExamplesDialog": {
|
||||
"3d1aa105e3": "Copy prompt",
|
||||
"9b4c004998": "Done",
|
||||
"969cec9739": "Copy {{value0}} example prompt",
|
||||
"4e46da1889": "Failed to copy prompt.",
|
||||
"80c6f2feb8": "Copied example prompt."
|
||||
},
|
||||
"OrchestrationPane": {
|
||||
"52e0634e2c": "Ask a coordinator agent to use orchestration for handoffs, worktree handovers, and sequential or parallel child agents.",
|
||||
"ae79504732": "How to use it",
|
||||
@@ -6522,7 +6528,9 @@
|
||||
"2309068a6f": "destructive",
|
||||
"65358016ea": "Discard",
|
||||
"dev": "Dev Tools",
|
||||
"devDescription": "Dev-only tools for exercising UI states."
|
||||
"devDescription": "Dev-only tools for exercising UI states.",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"SettingsFormControls": {
|
||||
"42a4d15a30": "No matching fonts.",
|
||||
@@ -9111,6 +9119,10 @@
|
||||
"description": "Let your agents read and edit Linear tasks.",
|
||||
"copyCommand": "Copy command"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9191,6 +9203,21 @@
|
||||
"availability": "Available on",
|
||||
"testFlight": "TestFlight",
|
||||
"androidApk": "Android APK"
|
||||
},
|
||||
"SkillUsageExampleDialog": {
|
||||
"copiedPrompt": "Copied example prompt.",
|
||||
"copyFailed": "Failed to copy prompt.",
|
||||
"copyExampleAria": "Copy {{value0}} example prompt",
|
||||
"done": "Done",
|
||||
"copyPrompt": "Copy prompt"
|
||||
},
|
||||
"LinearAgentSkillPane": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets.",
|
||||
"skillTitle": "Linear skill",
|
||||
"skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.",
|
||||
"howToUse": "How to use it",
|
||||
"howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR."
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -588,6 +588,17 @@
|
||||
"de50bef680": "macOS está bloqueando las notificaciones de Orca"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linear": {
|
||||
"usage": {
|
||||
"examples": {
|
||||
"readTicket": "Read the linked ticket",
|
||||
"postUpdate": "Post a progress update",
|
||||
"moveState": "Move the ticket forward",
|
||||
"attachPr": "Attach the PR link",
|
||||
"triageFollowups": "Triage and create follow-ups"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
@@ -705,7 +716,9 @@
|
||||
"devSearchKeywordSonner": "sonner",
|
||||
"devSearchKeywordError": "error",
|
||||
"devSearchKeywordNotification": "notification",
|
||||
"projectHostsSummary": "{{value0}} hosts"
|
||||
"projectHostsSummary": "{{value0}} hosts",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"useAppMenuPaste": {
|
||||
"pasteTooLarge": "El contenido pegado es demasiado grande."
|
||||
@@ -5944,13 +5957,6 @@
|
||||
"3743ed080c": "Establecer comando",
|
||||
"f79084947b": "Nueva aplicación"
|
||||
},
|
||||
"OrchestrationExamplesDialog": {
|
||||
"3d1aa105e3": "Copiar prompt",
|
||||
"9b4c004998": "Hecho",
|
||||
"969cec9739": "Copiar prompt de ejemplo {{value0}}",
|
||||
"4e46da1889": "No se pudo copiar el prompt.",
|
||||
"80c6f2feb8": "Prompt de ejemplo copiado."
|
||||
},
|
||||
"OrchestrationPane": {
|
||||
"52e0634e2c": "Pídele a un agente coordinador que use la orquestación para handoffs, handoffs de worktree y agentes secundarios secuenciales o paralelos.",
|
||||
"ae79504732": "Cómo usarlo",
|
||||
@@ -6462,7 +6468,9 @@
|
||||
"2309068a6f": "destructivo",
|
||||
"65358016ea": "Desechar",
|
||||
"dev": "Herramientas de desarrollo",
|
||||
"devDescription": "Herramientas solo de desarrollo para probar estados de la UI."
|
||||
"devDescription": "Herramientas solo de desarrollo para probar estados de la UI.",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"SettingsFormControls": {
|
||||
"42a4d15a30": "No hay fuentes coincidentes.",
|
||||
@@ -9088,6 +9096,10 @@
|
||||
"description": "Permite que tus agentes lean y editen tareas de Linear.",
|
||||
"copyCommand": "Copiar comando"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9168,6 +9180,21 @@
|
||||
"availability": "Disponible en",
|
||||
"testFlight": "TestFlight",
|
||||
"androidApk": "APK de Android"
|
||||
},
|
||||
"SkillUsageExampleDialog": {
|
||||
"copiedPrompt": "Copied example prompt.",
|
||||
"copyFailed": "Failed to copy prompt.",
|
||||
"copyExampleAria": "Copy {{value0}} example prompt",
|
||||
"done": "Done",
|
||||
"copyPrompt": "Copy prompt"
|
||||
},
|
||||
"LinearAgentSkillPane": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets.",
|
||||
"skillTitle": "Linear skill",
|
||||
"skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.",
|
||||
"howToUse": "How to use it",
|
||||
"howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR."
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -588,6 +588,17 @@
|
||||
"de50bef680": "macOS が Orca の通知をブロックしています"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linear": {
|
||||
"usage": {
|
||||
"examples": {
|
||||
"readTicket": "Read the linked ticket",
|
||||
"postUpdate": "Post a progress update",
|
||||
"moveState": "Move the ticket forward",
|
||||
"attachPr": "Attach the PR link",
|
||||
"triageFollowups": "Triage and create follow-ups"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
@@ -705,7 +716,9 @@
|
||||
"devSearchKeywordSonner": "sonner",
|
||||
"devSearchKeywordError": "error",
|
||||
"devSearchKeywordNotification": "notification",
|
||||
"projectHostsSummary": "{{value0}} 台のホスト"
|
||||
"projectHostsSummary": "{{value0}} 台のホスト",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"useAppMenuPaste": {
|
||||
"pasteTooLarge": "貼り付け内容が大きすぎます。"
|
||||
@@ -5966,13 +5979,6 @@
|
||||
"3743ed080c": "セットコマンド",
|
||||
"f79084947b": "新規アプリ"
|
||||
},
|
||||
"OrchestrationExamplesDialog": {
|
||||
"3d1aa105e3": "プロンプトのコピー",
|
||||
"9b4c004998": "完了",
|
||||
"969cec9739": "{{value0}} プロンプトの例をコピーします",
|
||||
"4e46da1889": "プロンプトのコピーに失敗しました。",
|
||||
"80c6f2feb8": "プロンプトの例をコピーしました。"
|
||||
},
|
||||
"OrchestrationPane": {
|
||||
"52e0634e2c": "コーディネーター agents に、ハンドオフ、ワークツリー ハンドオーバー、および順次または並列の子 agents にオーケストレーションを使用するように依頼します。",
|
||||
"ae79504732": "使い方",
|
||||
@@ -6484,7 +6490,9 @@
|
||||
"2309068a6f": "破壊的な",
|
||||
"65358016ea": "破棄",
|
||||
"dev": "Dev Tools",
|
||||
"devDescription": "Dev-only tools for exercising UI states."
|
||||
"devDescription": "Dev-only tools for exercising UI states.",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"SettingsFormControls": {
|
||||
"42a4d15a30": "一致するフォントがありません。",
|
||||
@@ -9088,6 +9096,10 @@
|
||||
"description": "エージェントにLinearタスクの読み取りと編集を許可します。",
|
||||
"copyCommand": "コマンドをコピー"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9168,6 +9180,21 @@
|
||||
"availability": "利用可能:",
|
||||
"testFlight": "TestFlight",
|
||||
"androidApk": "Android APK"
|
||||
},
|
||||
"SkillUsageExampleDialog": {
|
||||
"copiedPrompt": "Copied example prompt.",
|
||||
"copyFailed": "Failed to copy prompt.",
|
||||
"copyExampleAria": "Copy {{value0}} example prompt",
|
||||
"done": "Done",
|
||||
"copyPrompt": "Copy prompt"
|
||||
},
|
||||
"LinearAgentSkillPane": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets.",
|
||||
"skillTitle": "Linear skill",
|
||||
"skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.",
|
||||
"howToUse": "How to use it",
|
||||
"howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR."
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -588,6 +588,17 @@
|
||||
"de50bef680": "macOS가 Orca 알림을 차단하고 있습니다"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linear": {
|
||||
"usage": {
|
||||
"examples": {
|
||||
"readTicket": "Read the linked ticket",
|
||||
"postUpdate": "Post a progress update",
|
||||
"moveState": "Move the ticket forward",
|
||||
"attachPr": "Attach the PR link",
|
||||
"triageFollowups": "Triage and create follow-ups"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
@@ -705,7 +716,9 @@
|
||||
"devSearchKeywordSonner": "sonner",
|
||||
"devSearchKeywordError": "error",
|
||||
"devSearchKeywordNotification": "notification",
|
||||
"projectHostsSummary": "호스트 {{value0}}개"
|
||||
"projectHostsSummary": "호스트 {{value0}}개",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"useAppMenuPaste": {
|
||||
"pasteTooLarge": "붙여넣기 내용이 너무 큽니다."
|
||||
@@ -5929,13 +5942,6 @@
|
||||
"3743ed080c": "명령 설정",
|
||||
"f79084947b": "새로운 앱"
|
||||
},
|
||||
"OrchestrationExamplesDialog": {
|
||||
"3d1aa105e3": "프롬프트 복사",
|
||||
"9b4c004998": "완료",
|
||||
"969cec9739": "{{value0}} 예시 프롬프트 복사",
|
||||
"4e46da1889": "프롬프트를 복사하지 못했습니다.",
|
||||
"80c6f2feb8": "예시 프롬프트를 복사했습니다."
|
||||
},
|
||||
"OrchestrationPane": {
|
||||
"52e0634e2c": "핸드오프, 작업 트리 핸드오버, 순차 또는 병렬 하위 agents에 대한 오케스트레이션을 사용하도록 코디네이터 agents에 요청하세요.",
|
||||
"ae79504732": "사용방법",
|
||||
@@ -6447,7 +6453,9 @@
|
||||
"2309068a6f": "파괴적",
|
||||
"65358016ea": "버리기",
|
||||
"dev": "Dev Tools",
|
||||
"devDescription": "Dev-only tools for exercising UI states."
|
||||
"devDescription": "Dev-only tools for exercising UI states.",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"SettingsFormControls": {
|
||||
"42a4d15a30": "일치하는 글꼴이 없습니다.",
|
||||
@@ -9088,6 +9096,10 @@
|
||||
"description": "에이전트가 Linear 작업을 읽고 편집할 수 있도록 합니다.",
|
||||
"copyCommand": "명령 복사"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9168,6 +9180,21 @@
|
||||
"availability": "이용 가능:",
|
||||
"testFlight": "TestFlight",
|
||||
"androidApk": "Android APK"
|
||||
},
|
||||
"SkillUsageExampleDialog": {
|
||||
"copiedPrompt": "Copied example prompt.",
|
||||
"copyFailed": "Failed to copy prompt.",
|
||||
"copyExampleAria": "Copy {{value0}} example prompt",
|
||||
"done": "Done",
|
||||
"copyPrompt": "Copy prompt"
|
||||
},
|
||||
"LinearAgentSkillPane": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets.",
|
||||
"skillTitle": "Linear skill",
|
||||
"skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.",
|
||||
"howToUse": "How to use it",
|
||||
"howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR."
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -588,6 +588,17 @@
|
||||
"de50bef680": "macOS 正在阻止 Orca 通知"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linear": {
|
||||
"usage": {
|
||||
"examples": {
|
||||
"readTicket": "Read the linked ticket",
|
||||
"postUpdate": "Post a progress update",
|
||||
"moveState": "Move the ticket forward",
|
||||
"attachPr": "Attach the PR link",
|
||||
"triageFollowups": "Triage and create follow-ups"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
@@ -705,7 +716,9 @@
|
||||
"devSearchKeywordSonner": "sonner",
|
||||
"devSearchKeywordError": "error",
|
||||
"devSearchKeywordNotification": "notification",
|
||||
"projectHostsSummary": "{{value0}} 个主机"
|
||||
"projectHostsSummary": "{{value0}} 个主机",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"useAppMenuPaste": {
|
||||
"pasteTooLarge": "粘贴内容过大。"
|
||||
@@ -5929,13 +5942,6 @@
|
||||
"3743ed080c": "设置命令",
|
||||
"f79084947b": "新应用程序"
|
||||
},
|
||||
"OrchestrationExamplesDialog": {
|
||||
"3d1aa105e3": "复制提示",
|
||||
"9b4c004998": "完成",
|
||||
"969cec9739": "复制 {{value0}} 示例提示",
|
||||
"4e46da1889": "复制提示失败。",
|
||||
"80c6f2feb8": "复制示例提示。"
|
||||
},
|
||||
"OrchestrationPane": {
|
||||
"52e0634e2c": "要求协调智能体使用编排进行切换、工作树切换以及顺序或并行子智能体。",
|
||||
"ae79504732": "如何使用",
|
||||
@@ -6447,7 +6453,9 @@
|
||||
"2309068a6f": "destructive",
|
||||
"65358016ea": "放弃",
|
||||
"dev": "开发工具",
|
||||
"devDescription": "仅用于调试 UI 状态的开发工具。"
|
||||
"devDescription": "仅用于调试 UI 状态的开发工具。",
|
||||
"linearTitle": "Linear",
|
||||
"linearDescription": "Give agents the skill to read and update your linked Linear tickets."
|
||||
},
|
||||
"SettingsFormControls": {
|
||||
"42a4d15a30": "没有匹配的字体。",
|
||||
@@ -9088,6 +9096,10 @@
|
||||
"description": "让您的智能体读取和编辑 Linear 任务。",
|
||||
"copyCommand": "复制命令"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9168,6 +9180,21 @@
|
||||
"availability": "可用平台:",
|
||||
"testFlight": "TestFlight",
|
||||
"androidApk": "Android APK"
|
||||
},
|
||||
"SkillUsageExampleDialog": {
|
||||
"copiedPrompt": "Copied example prompt.",
|
||||
"copyFailed": "Failed to copy prompt.",
|
||||
"copyExampleAria": "Copy {{value0}} example prompt",
|
||||
"done": "Done",
|
||||
"copyPrompt": "Copy prompt"
|
||||
},
|
||||
"LinearAgentSkillPane": {
|
||||
"title": "Linear",
|
||||
"description": "Give agents the skill to read and update your linked Linear tickets.",
|
||||
"skillTitle": "Linear skill",
|
||||
"skillDescription": "Enables agents to read linked tickets and post updates to Linear through Orca.",
|
||||
"howToUse": "How to use it",
|
||||
"howToUseDescription": "Ask an agent working a Linear-linked worktree to read context, post updates, move the ticket, or attach the PR."
|
||||
}
|
||||
},
|
||||
"right": {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { createLocalizedCatalog } from '@/i18n/localized-catalog'
|
||||
import type { SkillUsageExample } from './skill-usage-example'
|
||||
|
||||
export const getLinearUsageExamples = createLocalizedCatalog((): SkillUsageExample[] => [
|
||||
{
|
||||
id: 'read-ticket',
|
||||
title: translate('auto.lib.linear.usage.examples.readTicket', 'Read the linked ticket'),
|
||||
summary: "Pull the linked Linear issue's full context before starting work.",
|
||||
prompt:
|
||||
'Use /orca-linear to read the linked Linear issue for this worktree, then summarize the goal and acceptance criteria before you start.'
|
||||
},
|
||||
{
|
||||
id: 'post-update',
|
||||
title: translate('auto.lib.linear.usage.examples.postUpdate', 'Post a progress update'),
|
||||
summary: 'Comment progress or a completion summary back to the Linear issue.',
|
||||
prompt:
|
||||
'Use /orca-linear to post a completion update on the linked Linear issue with what changed and how it was verified.'
|
||||
},
|
||||
{
|
||||
id: 'move-state',
|
||||
title: translate('auto.lib.linear.usage.examples.moveState', 'Move the ticket forward'),
|
||||
summary: 'Advance the Linear workflow state as the work progresses.',
|
||||
prompt:
|
||||
'Use /orca-linear to move the linked Linear issue to In Review now that the change is ready.'
|
||||
},
|
||||
{
|
||||
id: 'attach-pr',
|
||||
title: translate('auto.lib.linear.usage.examples.attachPr', 'Attach the PR link'),
|
||||
summary: 'Link the pull request to the Linear issue when you open it.',
|
||||
prompt: 'Use /orca-linear to attach this PR to the linked Linear issue with a PR link.'
|
||||
},
|
||||
{
|
||||
id: 'triage-followups',
|
||||
title: translate(
|
||||
'auto.lib.linear.usage.examples.triageFollowups',
|
||||
'Triage and create follow-ups'
|
||||
),
|
||||
summary: 'Set assignee, priority, or estimate, and file parented follow-up tickets.',
|
||||
prompt:
|
||||
'Use /orca-linear to triage the linked Linear issue — set priority and estimate — and create a parented follow-up ticket for the deferred cleanup.'
|
||||
}
|
||||
])
|
||||
@@ -1,13 +1,8 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { createLocalizedCatalog } from '@/i18n/localized-catalog'
|
||||
export type OrchestrationUsageExample = {
|
||||
id: string
|
||||
title: string
|
||||
summary: string
|
||||
prompt: string
|
||||
}
|
||||
import type { SkillUsageExample } from './skill-usage-example'
|
||||
|
||||
export const getOrchestrationUsageExamples = createLocalizedCatalog(() => [
|
||||
export const getOrchestrationUsageExamples = createLocalizedCatalog((): SkillUsageExample[] => [
|
||||
{
|
||||
id: 'handoff',
|
||||
title: translate('auto.lib.orchestration.usage.examples.5e0d489fe1', 'Hand off an active task'),
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export type SkillUsageExample = {
|
||||
id: string
|
||||
title: string
|
||||
summary: string
|
||||
prompt: string
|
||||
}
|
||||
Reference in New Issue
Block a user