feat(sidebar): distinguish and filter CLI-created workspaces (#10712)

This commit is contained in:
Neil 2026-07-26 00:41:03 -07:00 committed by GitHub
parent 627cde33d5
commit 28dfc13654
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 629 additions and 137 deletions

View File

@ -246,6 +246,9 @@ export const WORKTREE_HANDLERS: Record<string, CommandHandler> = {
...(cwdParentWorktree ? { cwdParentWorktree } : {}),
noParent,
callerTerminalHandle,
// Why: marks the workspace as CLI-created so the sidebar can badge and
// filter it. Sent on every `worktree create` — hand-typed or agent-run.
cliProvenanceRequest: callerTerminalHandle ? { callerTerminalHandle } : {},
...(startupAgent
? {
startupAgent,

View File

@ -1163,7 +1163,8 @@ describe('orca cli worktree awareness', () => {
parentWorktree: undefined,
cwdParentWorktree: 'id:repo-1::/tmp/repo',
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -1211,7 +1212,8 @@ describe('orca cli worktree awareness', () => {
activate: false,
parentWorktree: undefined,
noParent: true,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -1330,7 +1332,8 @@ describe('orca cli worktree awareness', () => {
parentWorktree: undefined,
cwdParentWorktree: 'id:repo-1::/tmp/repo',
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -1400,7 +1403,8 @@ describe('orca cli worktree awareness', () => {
activate: false,
parentWorktree: undefined,
noParent: true,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -1539,7 +1543,8 @@ describe('orca cli worktree awareness', () => {
activate: false,
parentWorktree: 'id:repo-1::/tmp/repo/parent',
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -1584,7 +1589,8 @@ describe('orca cli worktree awareness', () => {
activate: false,
parentWorktree: 'branch:feature/parent',
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -1651,7 +1657,8 @@ describe('orca cli worktree awareness', () => {
parentWorktree: undefined,
parentWorkspace: testCase.parentWorkspace,
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
}
})
@ -1695,7 +1702,8 @@ describe('orca cli worktree awareness', () => {
envParentWorkspace: 'folder:folder-1',
cwdParentWorktree: 'id:repo-1::/tmp/repo',
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -1737,7 +1745,8 @@ describe('orca cli worktree awareness', () => {
activate: false,
parentWorktree: 'id:repo-1::/tmp/repo/parent',
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -1798,7 +1807,8 @@ describe('orca cli worktree awareness', () => {
parentWorktree: undefined,
parentWorkspace: 'folder:folder-1',
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
}
})
@ -1945,7 +1955,8 @@ describe('orca cli worktree awareness', () => {
parentWorktree: undefined,
parentWorkspace: 'folder:missing',
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
expect(output).toContain('"ok": false')
expect(output).toContain('Parent selector was not found.')
@ -1985,7 +1996,8 @@ describe('orca cli worktree awareness', () => {
activate: false,
parentWorktree: undefined,
noParent: true,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -2020,10 +2032,37 @@ describe('orca cli worktree awareness', () => {
parentWorktree: undefined,
cwdParentWorktree: 'id:repo-1::/tmp/repo',
noParent: false,
callerTerminalHandle: 'term_parent'
callerTerminalHandle: 'term_parent',
cliProvenanceRequest: { callerTerminalHandle: 'term_parent' }
})
})
it('marks every worktree.create as CLI-created even from an external shell', async () => {
// Why: the sidebar badge/filter must catch hand-typed creates too, so the
// provenance request is sent with no terminal handle rather than omitted.
delete process.env.ORCA_TERMINAL_HANDLE
queueFixtures(
callMock,
okFixture('req_create_external', {
worktree: buildWorktree('/tmp/repo/child', 'child', 'abc', 'repo-1'),
lineage: null,
warnings: []
})
)
vi.spyOn(console, 'log').mockImplementation(() => {})
vi.spyOn(console, 'error').mockImplementation(() => {})
await main(
['worktree', 'create', '--repo', 'id:repo-1', '--name', 'child', '--no-parent', '--json'],
'/tmp/repo'
)
expect(callMock).toHaveBeenCalledWith(
'worktree.create',
expect.objectContaining({ cliProvenanceRequest: {} })
)
})
it('starts a foreground headless server through `serve`', async () => {
serveOrcaAppMock.mockResolvedValue(0)
process.env.ORCA_ENVIRONMENT = 'stale-env'
@ -2766,7 +2805,8 @@ describe('orca cli worktree awareness', () => {
parentWorktree: undefined,
cwdParentWorktree: 'id:repo-1::/tmp/repo',
noParent: false,
callerTerminalHandle: undefined
callerTerminalHandle: undefined,
cliProvenanceRequest: {}
})
})
@ -2814,6 +2854,7 @@ describe('orca cli worktree awareness', () => {
cwdParentWorktree: 'id:repo-1::/tmp/repo',
noParent: false,
callerTerminalHandle: undefined,
cliProvenanceRequest: {},
startupAgent: 'codex',
startupPrompt: 'hi'
})
@ -2858,6 +2899,7 @@ describe('orca cli worktree awareness', () => {
cwdParentWorktree: 'id:repo-1::/tmp/repo',
noParent: false,
callerTerminalHandle: undefined,
cliProvenanceRequest: {},
startupAgent: 'codex',
startupPrompt: 'hi'
})

View File

@ -47,6 +47,7 @@ export function mergeWorktree(
...(meta?.automationProvenance !== undefined
? { automationProvenance: meta.automationProvenance }
: {}),
...(meta?.cliProvenance !== undefined ? { cliProvenance: meta.cliProvenance } : {}),
...(meta?.pendingFirstAgentMessageRename !== undefined
? { pendingFirstAgentMessageRename: meta.pendingFirstAgentMessageRename }
: {}),

View File

@ -8,6 +8,7 @@ import { randomUUID } from 'node:crypto'
import type { Store } from '../persistence'
import type {
AutomationWorkspaceProvenance,
CliWorkspaceProvenance,
CreateWorktreeArgs,
CreateWorktreeResult,
GitPushTarget,
@ -66,6 +67,7 @@ import {
type CreateWorktreeArgsWithSystemProvenance = CreateWorktreeArgs & {
automationProvenance?: AutomationWorkspaceProvenance
cliProvenance?: CliWorkspaceProvenance
}
import {
sanitizeWorktreeName,
@ -1823,6 +1825,7 @@ export async function createRemoteWorktree(
orcaCreationSource: 'ssh',
orcaCreationWorkspaceLayout: getWorktreeCreationLayout(repo, settings),
...(args.automationProvenance ? { automationProvenance: args.automationProvenance } : {}),
...(args.cliProvenance ? { cliProvenance: args.cliProvenance } : {}),
baseRef: metadataBaseRef,
...(checkoutExistingBranch ? { preserveBranchOnDelete: true } : {}),
...(configuredPushTarget ? { pushTarget: configuredPushTarget } : {}),
@ -2405,6 +2408,7 @@ export async function createLocalWorktree(
orcaCreationSource: 'desktop',
orcaCreationWorkspaceLayout: getWorktreeCreationLayout(repo, settings),
...(args.automationProvenance ? { automationProvenance: args.automationProvenance } : {}),
...(args.cliProvenance ? { cliProvenance: args.cliProvenance } : {}),
baseRef: metadataBaseRef,
...(checkoutExistingBranch ? { preserveBranchOnDelete: true } : {}),
...(configuredPushTarget ? { pushTarget: configuredPushTarget } : {}),

View File

@ -17,6 +17,7 @@ import { projectResolvedWorktreeLineage } from '../../shared/resolved-worktree-l
import { deleteWorktreeHistoryDir } from '../terminal-history'
import type {
AutomationWorkspaceProvenance,
CliWorkspaceProvenance,
CreateWorktreeArgs,
CreateWorktreeResult,
DetectedWorktree,
@ -110,6 +111,7 @@ import { shouldEmitBoundedWarning } from './bounded-warning-dedupe'
type CreateWorktreeArgsWithSystemProvenance = CreateWorktreeArgs & {
automationProvenance?: AutomationWorkspaceProvenance
cliProvenance?: CliWorkspaceProvenance
}
type RemoveWorktreeArgs = {
@ -841,6 +843,7 @@ function mergeFolderWorkspace(repo: Repo, worktreeId: string, meta: WorktreeMeta
...(meta.automationProvenance !== undefined
? { automationProvenance: meta.automationProvenance }
: {}),
...(meta.cliProvenance !== undefined ? { cliProvenance: meta.cliProvenance } : {}),
...(meta.priorWorktreeIds !== undefined ? { priorWorktreeIds: meta.priorWorktreeIds } : {}),
workspaceStatus: meta.workspaceStatus ?? DEFAULT_WORKSPACE_STATUS_ID,
diffComments: meta.diffComments,
@ -931,6 +934,7 @@ function createFolderWorkspace(
orcaCreatedAt: now,
orcaCreationSource: 'desktop',
...(args.automationProvenance ? { automationProvenance: args.automationProvenance } : {}),
...(args.cliProvenance ? { cliProvenance: args.cliProvenance } : {}),
...(args.createdWithAgent ? { createdWithAgent: args.createdWithAgent } : {}),
...(args.linkedIssue !== undefined ? { linkedIssue: args.linkedIssue } : {}),
...(args.linkedPR !== undefined ? { linkedPR: args.linkedPR } : {}),

View File

@ -105,6 +105,7 @@ import type {
} from '../../shared/automations-types'
import type {
AutomationWorkspaceProvenance,
CliWorkspaceProvenance,
BaseRefSearchResult,
CreateWorktreeResult,
DetectedWorktree,
@ -1855,6 +1856,7 @@ function mergeRuntimeFolderWorkspace(repo: Repo, worktreeId: string, meta: Workt
...(meta.automationProvenance !== undefined
? { automationProvenance: meta.automationProvenance }
: {}),
...(meta.cliProvenance !== undefined ? { cliProvenance: meta.cliProvenance } : {}),
...(meta.priorWorktreeIds !== undefined ? { priorWorktreeIds: meta.priorWorktreeIds } : {}),
workspaceStatus: meta.workspaceStatus ?? DEFAULT_WORKSPACE_STATUS_ID,
diffComments: meta.diffComments,
@ -18256,6 +18258,7 @@ export class OrcaRuntimeService {
startupPrompt?: string
pendingFirstAgentMessageRename?: boolean
automationProvenance?: AutomationWorkspaceProvenance
cliProvenance?: CliWorkspaceProvenance
startup?: WorktreeStartupLaunch
startupDraft?: string
startupDraftPaste?: WorktreeStartupDraftPaste
@ -18316,6 +18319,7 @@ export class OrcaRuntimeService {
nestWorkspaces: settings.nestWorkspaces
},
...(args.automationProvenance ? { automationProvenance: args.automationProvenance } : {}),
...(args.cliProvenance ? { cliProvenance: args.cliProvenance } : {}),
...(args.linkedIssue !== undefined ? { linkedIssue: args.linkedIssue } : {}),
...(args.linkedPR !== undefined ? { linkedPR: args.linkedPR } : {}),
...(args.linkedLinearIssue !== undefined
@ -18921,6 +18925,7 @@ export class OrcaRuntimeService {
? { pendingFirstAgentMessageRename: true }
: {}),
...(args.automationProvenance ? { automationProvenance: args.automationProvenance } : {}),
...(args.cliProvenance ? { cliProvenance: args.cliProvenance } : {}),
...(args.comment !== undefined ? { comment: args.comment } : {}),
...(args.manualOrder !== undefined ? { manualOrder: args.manualOrder } : {}),
...(args.workspaceStatus !== undefined ? { workspaceStatus: args.workspaceStatus } : {})
@ -19270,6 +19275,7 @@ export class OrcaRuntimeService {
createdWithAgent?: TuiAgent
pendingFirstAgentMessageRename?: boolean
automationProvenance?: AutomationWorkspaceProvenance
cliProvenance?: CliWorkspaceProvenance
startup?: WorktreeStartupLaunch
startupFollowup?: WorktreeStartupFollowup
startupDraftPaste?: WorktreeStartupDraftPaste
@ -19321,7 +19327,8 @@ export class OrcaRuntimeService {
...(args.pendingFirstAgentMessageRename === true
? { pendingFirstAgentMessageRename: true }
: {}),
...(args.automationProvenance ? { automationProvenance: args.automationProvenance } : {})
...(args.automationProvenance ? { automationProvenance: args.automationProvenance } : {}),
...(args.cliProvenance ? { cliProvenance: args.cliProvenance } : {})
},
repo,
this.store as unknown as Store,

View File

@ -197,6 +197,7 @@ export const UiUpdate = z
.optional(),
hideDefaultBranchWorkspace: z.boolean().optional(),
hideAutomationGeneratedWorkspaces: z.boolean().optional(),
hideCliCreatedWorkspaces: z.boolean().optional(),
filterRepoIds: StringArray.optional(),
collapsedGroups: StringArray.optional(),
uiZoomLevel: z.number().finite().optional(),

View File

@ -29,6 +29,13 @@ const AutomationWorkspaceProvenanceRequest = z.object({
createRequestId: z.string()
})
// Why no dispatch token (unlike automation provenance): this is a descriptive
// origin marker for sidebar filtering, not an authority grant. The host stamps
// createdAt itself so a client clock can't skew sort order.
const CliWorkspaceProvenanceRequest = z.object({
callerTerminalHandle: OptionalString
})
export const WorktreeListParams = z.object({
repo: OptionalString,
limit: OptionalFiniteNumber
@ -149,7 +156,8 @@ export const WorktreeCreate = z
// Why: mobile retries a create interrupted by a connection migration with the
// same key so the host dedupes instead of spawning a duplicate worktree.
clientMutationId: z.string().min(1).max(128).optional(),
automationProvenanceRequest: AutomationWorkspaceProvenanceRequest.optional()
automationProvenanceRequest: AutomationWorkspaceProvenanceRequest.optional(),
cliProvenanceRequest: CliWorkspaceProvenanceRequest.optional()
})
.superRefine((params, ctx) => {
if ((params.parentWorkspace || params.parentWorktree) && params.noParent === true) {

View File

@ -3,6 +3,7 @@ import {
releaseAutomationWorkspaceProvenanceRequest,
resolveAutomationWorkspaceProvenance
} from '../../../automations/workspace-provenance'
import { buildCliWorkspaceProvenance } from '../../../../shared/cli-workspace-provenance'
import { defineMethod, type RpcMethod } from '../core'
import { resolveRuntimeNavigationTarget } from '../../../../shared/runtime-navigation'
import {
@ -119,6 +120,10 @@ export const WORKTREE_METHODS: RpcMethod[] = [
setupDecision: params.setupDecision,
createdWithAgent: params.createdWithAgent ?? params.startupAgent,
automationProvenance,
cliProvenance: buildCliWorkspaceProvenance(params.cliProvenanceRequest, {
startupAgent: params.startupAgent ?? params.createdWithAgent,
createdAt: Date.now()
}),
startup: params.startupCommand
? {
command: params.startupCommand,

View File

@ -22,7 +22,8 @@ const ORCA_OWNED_PROVENANCE_META_KEYS = [
'orcaCreatedAt',
'orcaCreationSource',
'orcaCreationWorkspaceLayout',
'automationProvenance'
'automationProvenance',
'cliProvenance'
] as const
type UnregisteredOrcaCleanupMeta = Pick<
WorktreeMeta,

View File

@ -638,6 +638,7 @@ function App(): React.JSX.Element {
const showSleepingWorkspaces = useAppStore((s) => s.showSleepingWorkspaces)
const hideDefaultBranchWorkspace = useAppStore((s) => s.hideDefaultBranchWorkspace)
const hideAutomationGeneratedWorkspaces = useAppStore((s) => s.hideAutomationGeneratedWorkspaces)
const hideCliCreatedWorkspaces = useAppStore((s) => s.hideCliCreatedWorkspaces)
const showDotfilesByWorktree = useAppStore((s) => s.showDotfilesByWorktree)
const filterRepoIds = useAppStore((s) => s.filterRepoIds)
const acknowledgedAgentsByPaneKey = useAppStore((s) => s.acknowledgedAgentsByPaneKey)
@ -1337,6 +1338,7 @@ function App(): React.JSX.Element {
showSleepingWorkspaces,
hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces,
showDotfilesByWorktree,
filterRepoIds,
// Why (#9002): activeView is deliberately NOT included here. It used to
@ -1367,6 +1369,7 @@ function App(): React.JSX.Element {
showSleepingWorkspaces,
hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces,
showDotfilesByWorktree,
filterRepoIds,
acknowledgedAgentsByPaneKey

View File

@ -30,6 +30,7 @@ import type { LinkedWorkItemSummary } from '@/lib/new-workspace'
import { sortWorktreesSmart } from '@/components/sidebar/smart-sort'
import {
isAutomationGeneratedWorkspace,
isCliCreatedWorkspace,
isDefaultBranchWorkspace
} from '@/components/sidebar/visible-worktrees'
import { getLiveAgentStatusByWorktreeId, isInactiveWorkspace } from '@/lib/worktree-activity-state'
@ -392,6 +393,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
const runtimeStatusByEnvironmentId = useAppStore((s) => s.runtimeStatusByEnvironmentId)
const hideDefaultBranchWorkspace = useAppStore((s) => s.hideDefaultBranchWorkspace)
const hideAutomationGeneratedWorkspaces = useAppStore((s) => s.hideAutomationGeneratedWorkspaces)
const hideCliCreatedWorkspaces = useAppStore((s) => s.hideCliCreatedWorkspaces)
const showSleepingWorkspaces = useAppStore((s) => s.showSleepingWorkspaces)
const lastVisitedAtByWorktreeId = useAppStore((s) => s.lastVisitedAtByWorktreeId)
const workspacePortScan = useAppStore((s) => s.workspacePortScan?.result ?? null)
@ -482,6 +484,9 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
if (hideAutomationGeneratedWorkspaces && isAutomationGeneratedWorkspace(worktree)) {
return false
}
if (hideCliCreatedWorkspaces && isCliCreatedWorkspace(worktree)) {
return false
}
if (
!showSleepingWorkspaces &&
isInactiveWorkspace(
@ -500,6 +505,7 @@ export default function WorktreeJumpPalette(): React.JSX.Element | null {
allWorktrees,
browserTabsByWorktree,
hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces,
hideDefaultBranchWorkspace,
ptyIdsByTabId,
showSleepingWorkspaces,

View File

@ -1,5 +1,14 @@
import React, { useCallback, useMemo, useState } from 'react'
import { CalendarClock, Check, FolderPlus, GitBranch, ListFilter, Moon, Server } from 'lucide-react'
import {
CalendarClock,
Check,
FolderPlus,
GitBranch,
ListFilter,
Moon,
Server,
SquareTerminal
} from 'lucide-react'
import { useAppStore } from '@/store'
import { Button } from '@/components/ui/button'
import {
@ -48,6 +57,8 @@ const SidebarFilter = React.memo(function SidebarFilter({
const setHideAutomationGeneratedWorkspaces = useAppStore(
(s) => s.setHideAutomationGeneratedWorkspaces
)
const hideCliCreatedWorkspaces = useAppStore((s) => s.hideCliCreatedWorkspaces)
const setHideCliCreatedWorkspaces = useAppStore((s) => s.setHideCliCreatedWorkspaces)
const filterRepoIds = useAppStore((s) => s.filterRepoIds)
const setFilterRepoIds = useAppStore((s) => s.setFilterRepoIds)
const repos = useAppStore((s) => s.repos)
@ -98,11 +109,13 @@ const SidebarFilter = React.memo(function SidebarFilter({
hasSleepingFilter ||
hideDefaultBranchWorkspace ||
hideAutomationGeneratedWorkspaces ||
hideCliCreatedWorkspaces ||
hasRepoFilter
const activeFilterCount =
(hasSleepingFilter ? 1 : 0) +
(hideDefaultBranchWorkspace ? 1 : 0) +
(hideAutomationGeneratedWorkspaces ? 1 : 0) +
(hideCliCreatedWorkspaces ? 1 : 0) +
selectedCount
const filteredRepos = useMemo(() => searchRepos(repos, query), [repos, query])
@ -116,11 +129,13 @@ const SidebarFilter = React.memo(function SidebarFilter({
setShowSleepingWorkspaces(DEFAULT_SHOW_SLEEPING_WORKSPACES)
setHideDefaultBranchWorkspace(false)
setHideAutomationGeneratedWorkspaces(false)
setHideCliCreatedWorkspaces(false)
setFilterRepoIds([])
}, [
setShowSleepingWorkspaces,
setHideDefaultBranchWorkspace,
setHideAutomationGeneratedWorkspaces,
setHideCliCreatedWorkspaces,
setFilterRepoIds
])
@ -208,6 +223,12 @@ const SidebarFilter = React.memo(function SidebarFilter({
checked={hideAutomationGeneratedWorkspaces}
onChange={setHideAutomationGeneratedWorkspaces}
/>
<FilterToggleRow
icon={<SquareTerminal className="size-3.5" />}
label={translate('auto.components.sidebar.SidebarFilter.cliCreated', 'Hide CLI-created')}
checked={hideCliCreatedWorkspaces}
onChange={setHideCliCreatedWorkspaces}
/>
{canFilterRepos && (
<>

View File

@ -1,5 +1,5 @@
import React from 'react'
import { CalendarClock, GitBranch, Moon } from 'lucide-react'
import { CalendarClock, GitBranch, Moon, SquareTerminal } from 'lucide-react'
import { useAppStore } from '@/store'
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
@ -13,6 +13,8 @@ const SidebarWorkspaceFilterSection = React.memo(function SidebarWorkspaceFilter
const setHideAutomationGeneratedWorkspaces = useAppStore(
(s) => s.setHideAutomationGeneratedWorkspaces
)
const hideCliCreatedWorkspaces = useAppStore((s) => s.hideCliCreatedWorkspaces)
const setHideCliCreatedWorkspaces = useAppStore((s) => s.setHideCliCreatedWorkspaces)
return (
<>
@ -48,6 +50,15 @@ const SidebarWorkspaceFilterSection = React.memo(function SidebarWorkspaceFilter
checked={hideAutomationGeneratedWorkspaces}
onChange={setHideAutomationGeneratedWorkspaces}
/>
<FilterToggleRow
icon={<SquareTerminal className="size-3.5" />}
label={translate(
'auto.components.sidebar.SidebarWorkspaceFilterSection.cliCreated',
'Hide CLI-created'
)}
checked={hideCliCreatedWorkspaces}
onChange={setHideCliCreatedWorkspaces}
/>
</>
)
})

View File

@ -38,6 +38,7 @@ const SidebarWorkspaceOptionsMenu = React.memo(function SidebarWorkspaceOptionsM
const showSleepingWorkspaces = useAppStore((s) => s.showSleepingWorkspaces)
const hideDefaultBranchWorkspace = useAppStore((s) => s.hideDefaultBranchWorkspace)
const hideAutomationGeneratedWorkspaces = useAppStore((s) => s.hideAutomationGeneratedWorkspaces)
const hideCliCreatedWorkspaces = useAppStore((s) => s.hideCliCreatedWorkspaces)
const filterRepoIds = useAppStore((s) => s.filterRepoIds)
const repos = useAppStore((s) => s.repos)
const setWorkspaceHostScope = useAppStore((s) => s.setWorkspaceHostScope)
@ -80,12 +81,14 @@ const SidebarWorkspaceOptionsMenu = React.memo(function SidebarWorkspaceOptionsM
hasSleepingFilter ||
hideDefaultBranchWorkspace ||
hideAutomationGeneratedWorkspaces ||
hideCliCreatedWorkspaces ||
hasRepoFilter ||
hasHostVisibilityFilter
const activeFilterCount =
(hasSleepingFilter ? 1 : 0) +
(hideDefaultBranchWorkspace ? 1 : 0) +
(hideAutomationGeneratedWorkspaces ? 1 : 0) +
(hideCliCreatedWorkspaces ? 1 : 0) +
(hasHostVisibilityFilter ? 1 : 0) +
selectedCount
const activeFilterLabel = `${activeFilterCount} ${activeFilterCount === 1 ? 'filter' : 'filters'}`

View File

@ -640,6 +640,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
const showLinearIssue = cardProps.includes('linear-issue')
const showPR = cardProps.includes('pr')
const showAutomation = cardProps.includes('automation')
const showCli = cardProps.includes('cli')
const showComment = cardProps.includes('comment')
const showPorts = cardProps.includes('ports')
const shouldRefreshHostedReview = newCardStyle ? showStatus : showPR
@ -1056,6 +1057,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
const metaLinearIssue = showLinearIssue ? hoverLinearIssue : null
const metaReview = showPR ? hoverReview : null
const metaAutomationProvenance = showAutomation ? worktree.automationProvenance : null
const metaCliProvenance = showCli ? worktree.cliProvenance : null
const metaComment = showComment ? hoverComment : null
const showInlineAgentList = cardProps.includes('inline-agents') && (newCardStyle || !compactCards)
const compactInlineAgentRows = useWorktreeAgentRows(
@ -1156,7 +1158,8 @@ const WorktreeCard = React.memo(function WorktreeCard({
linearIssue: metaLinearIssue,
review: newCardStyle ? null : metaReview,
comment: metaComment,
automationProvenance: metaAutomationProvenance
automationProvenance: metaAutomationProvenance,
cliProvenance: metaCliProvenance
})
const hasPorts = showPorts && workspacePorts.length > 0
const cacheStartedAt = usePromptCacheCountdownStartedAt(worktree.id, showAggregateCacheTimer)
@ -1228,7 +1231,8 @@ const WorktreeCard = React.memo(function WorktreeCard({
linearIssue: hoverLinearIssue,
review: hoverReview,
comment: hoverComment,
automationProvenance: metaAutomationProvenance
automationProvenance: metaAutomationProvenance,
cliProvenance: metaCliProvenance
}) ||
workspacePorts.length > 0 ||
hasHoverIdentity)
@ -1245,6 +1249,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
review={metaReview}
comment={metaComment}
automationProvenance={metaAutomationProvenance}
cliProvenance={metaCliProvenance}
automationHostId={worktree.hostId}
branchName={showBranchIdentityHover ? branch : undefined}
workspaceTitle={worktree.displayName}
@ -1300,6 +1305,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
review={newCardStyle ? null : metaReview}
comment={metaComment}
automationProvenance={metaAutomationProvenance}
cliProvenance={metaCliProvenance}
className="ml-0 pr-0"
/>
)}
@ -1313,6 +1319,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
review={metaReview}
comment={metaComment}
automationProvenance={metaAutomationProvenance}
cliProvenance={metaCliProvenance}
automationHostId={worktree.hostId}
detailsAfter={hasPorts ? <WorktreeCardPortsDetails ports={workspacePorts} /> : null}
hoverControl={detailsHoverControl}
@ -1806,6 +1813,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
review={hoverReview}
comment={hoverComment}
automationProvenance={metaAutomationProvenance}
cliProvenance={metaCliProvenance}
automationHostId={worktree.hostId}
branchName={hoverBranchName}
workspaceTitle={hoverWorkspaceTitle}

View File

@ -0,0 +1,51 @@
import React from 'react'
import { SquareTerminal } from 'lucide-react'
import type { CliWorkspaceProvenance } from '../../../../shared/types'
import { formatAgentTypeLabel } from '../../../../shared/agent-type-label'
import {
WorktreeCardDetailSection,
WorktreeCardDetailSectionContent
} from './WorktreeCardDetailSection'
import { DetailHeader } from './WorktreeCardMetadataControls'
import { translate } from '@/i18n/i18n'
export function WorktreeCardCliDetailSection({
provenance
}: {
provenance: CliWorkspaceProvenance
}): React.JSX.Element {
const agentLabel = provenance.startupAgent
? formatAgentTypeLabel(provenance.startupAgent)
: undefined
return (
<WorktreeCardDetailSection>
<DetailHeader
icon={<SquareTerminal className="size-3 text-muted-foreground" />}
label={translate('auto.components.sidebar.WorktreeCardMeta.cliHeader', 'Orca CLI')}
/>
<WorktreeCardDetailSectionContent className="space-y-1.5">
<div className="text-[13px] font-semibold leading-snug text-foreground break-words">
{provenance.callerTerminalHandle
? translate(
'auto.components.sidebar.WorktreeCardMeta.cliCreatedFromAgent',
'Created by an agent via `orca worktree create`'
)
: translate(
'auto.components.sidebar.WorktreeCardMeta.cliCreatedFromShell',
'Created via `orca worktree create`'
)}
</div>
{agentLabel ? (
<div className="text-[11.5px] leading-snug text-muted-foreground break-words">
{translate(
'auto.components.sidebar.WorktreeCardMeta.cliStartupAgent',
'Started with {{value0}}',
{ value0: agentLabel }
)}
</div>
) : null}
</WorktreeCardDetailSectionContent>
</WorktreeCardDetailSection>
)
}

View File

@ -1,9 +1,8 @@
import React from 'react'
import { Badge } from '@/components/ui/badge'
import { HoverCard, HoverCardTrigger, HoverCardContent } from '@/components/ui/hover-card'
import { CalendarClock, CircleDot, ExternalLink, MonitorUp, Pencil, StickyNote } from 'lucide-react'
import { ExternalLink, MonitorUp, Pencil, StickyNote } from 'lucide-react'
import { toast } from 'sonner'
import { cn } from '@/lib/utils'
import { LinearIcon } from '@/components/icons/LinearIcon'
import { SelectedTextCopyMenu } from '@/components/SelectedTextCopyMenu'
import CommentMarkdown from './CommentMarkdown'
@ -12,10 +11,11 @@ import {
WorktreeCardDetailSection,
WorktreeCardDetailSectionContent
} from './WorktreeCardDetailSection'
import { DetailHeader, MetaIconBadge, MetadataActionIcon } from './WorktreeCardMetadataControls'
import { DetailHeader, MetadataActionIcon } from './WorktreeCardMetadataControls'
import { hasWorktreeCardDetails, WorktreeCardMetaBadges } from './WorktreeCardMetaBadges'
import { LinearStateBadge } from './WorktreeCardMetadataStatusBadges'
import { useWorktreeCardDetailsHoverControl } from './worktree-card-details-hover-state'
import { getReviewLabel, ReviewIcon } from './worktree-review-helpers'
import { getReviewLabel } from './worktree-review-helpers'
import type {
WorktreeCardIssueDisplay,
WorktreeCardLinearIssueDisplay,
@ -26,6 +26,7 @@ import type {
import { translate } from '@/i18n/i18n'
import { WorktreeCardReviewDetailSection } from './WorktreeCardReviewDetailSection'
import { WorktreeCardAutomationDetailSection } from './WorktreeCardAutomationDetailSection'
import { WorktreeCardCliDetailSection } from './WorktreeCardCliDetailSection'
import { WorktreeCardIssueDetailSection } from './WorktreeCardIssueDetailSection'
import { WorktreeCardHoverIdentityHeader } from './WorktreeCardHoverIdentityHeader'
@ -37,106 +38,21 @@ export type {
WorktreeCardDetailsHoverProps
}
// Re-exported so existing importers keep their entry point after the badge
// row moved into its own module.
export { hasWorktreeCardDetails, WorktreeCardMetaBadges }
function hasComment(comment: string | null): boolean {
return (comment ?? '').trim().length > 0
}
export function hasWorktreeCardDetails({
issue,
linearIssue,
review,
comment,
automationProvenance
}: WorktreeCardMetaBadgesProps): boolean {
return Boolean(issue || linearIssue || review || hasComment(comment) || automationProvenance)
}
export const WorktreeCardMetaBadges = React.forwardRef<
HTMLDivElement,
WorktreeCardMetaBadgesRootProps
>(function WorktreeCardMetaBadges(
{ issue, linearIssue, review, comment, automationProvenance, className, ...props },
ref
): React.JSX.Element | null {
if (!hasWorktreeCardDetails({ issue, linearIssue, review, comment, automationProvenance })) {
return null
}
return (
// Why: Radix HoverCardTrigger uses `asChild`, so this group must forward
// trigger props/ref to the actual DOM node for attachment-only hover.
<div
ref={ref}
{...props}
className={cn('ml-auto flex shrink-0 items-center gap-1 pr-1.5', className)}
aria-label={translate(
'auto.components.sidebar.WorktreeCardMeta.3e65e11cc6',
'Workspace metadata'
)}
>
{hasComment(comment) && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.fe075cb851',
'Workspace notes'
)}
>
<StickyNote className="text-muted-foreground" />
</MetaIconBadge>
)}
{automationProvenance && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.automationCreated',
'Created by automation'
)}
>
<CalendarClock className="text-muted-foreground" />
</MetaIconBadge>
)}
{issue && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.3f2649eeb8',
'Linked issue #{{value0}}',
{ value0: issue.number }
)}
>
<CircleDot className="text-muted-foreground" />
</MetaIconBadge>
)}
{linearIssue && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.b105fd3057',
'Linked Linear {{value0}}',
{ value0: linearIssue.identifier }
)}
>
<LinearIcon className="text-muted-foreground" />
</MetaIconBadge>
)}
{review && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.3ea2702e62',
'Linked {{value0}} #{{value1}}',
{ value0: getReviewLabel(review), value1: review.number }
)}
>
<ReviewIcon review={review} />
</MetaIconBadge>
)}
</div>
)
})
export function WorktreeCardDetailsHover({
issue,
linearIssue,
review,
comment,
automationProvenance,
cliProvenance,
children,
branchName,
workspaceTitle,
@ -241,7 +157,14 @@ export function WorktreeCardDetailsHover({
if (
!showIdentityHeader &&
!hasWorktreeCardDetails({ issue, linearIssue, review, comment, automationProvenance }) &&
!hasWorktreeCardDetails({
issue,
linearIssue,
review,
comment,
automationProvenance,
cliProvenance
}) &&
!detailsAfter
) {
return children
@ -365,6 +288,8 @@ export function WorktreeCardDetailsHover({
/>
)}
{cliProvenance && <WorktreeCardCliDetailSection provenance={cliProvenance} />}
{hasComment(comment) && (
<WorktreeCardDetailSection>
<DetailHeader

View File

@ -0,0 +1,127 @@
import React from 'react'
import { CalendarClock, CircleDot, SquareTerminal, StickyNote } from 'lucide-react'
import { cn } from '@/lib/utils'
import { LinearIcon } from '@/components/icons/LinearIcon'
import { MetaIconBadge } from './WorktreeCardMetadataControls'
import { getReviewLabel, ReviewIcon } from './worktree-review-helpers'
import type {
WorktreeCardMetaBadgesProps,
WorktreeCardMetaBadgesRootProps
} from './worktree-card-meta-types'
import { translate } from '@/i18n/i18n'
function hasComment(comment: string | null): boolean {
return (comment ?? '').trim().length > 0
}
export function hasWorktreeCardDetails({
issue,
linearIssue,
review,
comment,
automationProvenance,
cliProvenance
}: WorktreeCardMetaBadgesProps): boolean {
return Boolean(
issue || linearIssue || review || hasComment(comment) || automationProvenance || cliProvenance
)
}
export const WorktreeCardMetaBadges = React.forwardRef<
HTMLDivElement,
WorktreeCardMetaBadgesRootProps
>(function WorktreeCardMetaBadges(
{ issue, linearIssue, review, comment, automationProvenance, cliProvenance, className, ...props },
ref
): React.JSX.Element | null {
if (
!hasWorktreeCardDetails({
issue,
linearIssue,
review,
comment,
automationProvenance,
cliProvenance
})
) {
return null
}
return (
// Why: Radix HoverCardTrigger uses `asChild`, so this group must forward
// trigger props/ref to the actual DOM node for attachment-only hover.
<div
ref={ref}
{...props}
className={cn('ml-auto flex shrink-0 items-center gap-1 pr-1.5', className)}
aria-label={translate(
'auto.components.sidebar.WorktreeCardMeta.3e65e11cc6',
'Workspace metadata'
)}
>
{hasComment(comment) && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.fe075cb851',
'Workspace notes'
)}
>
<StickyNote className="text-muted-foreground" />
</MetaIconBadge>
)}
{automationProvenance && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.automationCreated',
'Created by automation'
)}
>
<CalendarClock className="text-muted-foreground" />
</MetaIconBadge>
)}
{cliProvenance && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.cliCreated',
'Created by Orca CLI'
)}
>
<SquareTerminal className="text-muted-foreground" />
</MetaIconBadge>
)}
{issue && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.3f2649eeb8',
'Linked issue #{{value0}}',
{ value0: issue.number }
)}
>
<CircleDot className="text-muted-foreground" />
</MetaIconBadge>
)}
{linearIssue && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.b105fd3057',
'Linked Linear {{value0}}',
{ value0: linearIssue.identifier }
)}
>
<LinearIcon className="text-muted-foreground" />
</MetaIconBadge>
)}
{review && (
<MetaIconBadge
label={translate(
'auto.components.sidebar.WorktreeCardMeta.3ea2702e62',
'Linked {{value0}} #{{value1}}',
{ value0: getReviewLabel(review), value1: review.number }
)}
>
<ReviewIcon review={review} />
</MetaIconBadge>
)}
</div>
)
})

View File

@ -5212,6 +5212,7 @@ const WorktreeList = React.memo(function WorktreeList({
const agentStatusEpoch = useAppStore((s) => (!showSleepingWorkspaces ? s.agentStatusEpoch : 0))
const hideDefaultBranchWorkspace = useAppStore((s) => s.hideDefaultBranchWorkspace)
const hideAutomationGeneratedWorkspaces = useAppStore((s) => s.hideAutomationGeneratedWorkspaces)
const hideCliCreatedWorkspaces = useAppStore((s) => s.hideCliCreatedWorkspaces)
const filterRepoIds = useAppStore((s) => s.filterRepoIds)
const openModal = useAppStore((s) => s.openModal)
const openSettingsPage = useAppStore((s) => s.openSettingsPage)
@ -5497,6 +5498,7 @@ const WorktreeList = React.memo(function WorktreeList({
),
hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces,
repoMap,
workspaceHostScope,
visibleWorkspaceHostIds,
@ -5512,6 +5514,7 @@ const WorktreeList = React.memo(function WorktreeList({
showSleepingWorkspaces,
hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces,
workspaceHostScope,
visibleWorkspaceHostIds,
settings,
@ -6482,6 +6485,7 @@ const WorktreeList = React.memo(function WorktreeList({
filterRepoIds,
hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces,
visibleWorkspaceHostIds,
workspaceHostScope
}),
@ -6490,6 +6494,7 @@ const WorktreeList = React.memo(function WorktreeList({
filterRepoIds,
hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces,
visibleWorkspaceHostIds,
workspaceHostScope
]
@ -6500,6 +6505,7 @@ const WorktreeList = React.memo(function WorktreeList({
const setHideAutomationGeneratedWorkspaces = useAppStore(
(s) => s.setHideAutomationGeneratedWorkspaces
)
const setHideCliCreatedWorkspaces = useAppStore((s) => s.setHideCliCreatedWorkspaces)
const setFilterRepoIds = useAppStore((s) => s.setFilterRepoIds)
const setVisibleWorkspaceHostIds = useAppStore((s) => s.setVisibleWorkspaceHostIds)
@ -6517,6 +6523,9 @@ const WorktreeList = React.memo(function WorktreeList({
if (actions.resetHideAutomationGeneratedWorkspaces) {
setHideAutomationGeneratedWorkspaces(false)
}
if (actions.resetHideCliCreatedWorkspaces) {
setHideCliCreatedWorkspaces(false)
}
if (actions.resetVisibleWorkspaceHostIds) {
setVisibleWorkspaceHostIds(null)
}
@ -6525,6 +6534,7 @@ const WorktreeList = React.memo(function WorktreeList({
setFilterRepoIds,
setHideDefaultBranchWorkspace,
setHideAutomationGeneratedWorkspaces,
setHideCliCreatedWorkspaces,
setVisibleWorkspaceHostIds,
filterState
])

View File

@ -96,6 +96,13 @@ const BASE_WORKTREE_CARD_PROPERTY_OPTIONS: WorktreeCardPropertyOption[] = [
)
}
},
{
id: 'cli',
properties: ['cli'],
get label() {
return translate('auto.components.sidebar.SidebarWorkspaceOptionsMenu.cli', 'Orca CLI')
}
},
{
id: 'ports',
properties: ['ports'],

View File

@ -20,6 +20,7 @@ export function useVisibleWorkspaceKanbanWorktreeIds({
const showSleepingWorkspaces = useAppStore((s) => s.showSleepingWorkspaces)
const hideDefaultBranchWorkspace = useAppStore((s) => s.hideDefaultBranchWorkspace)
const hideAutomationGeneratedWorkspaces = useAppStore((s) => s.hideAutomationGeneratedWorkspaces)
const hideCliCreatedWorkspaces = useAppStore((s) => s.hideCliCreatedWorkspaces)
const workspaceHostScope = useAppStore((s) => s.workspaceHostScope)
const visibleWorkspaceHostIds = useAppStore((s) => s.visibleWorkspaceHostIds)
const settings = useAppStore((s) => s.settings)
@ -57,6 +58,7 @@ export function useVisibleWorkspaceKanbanWorktreeIds({
worktreeIdsWithLiveAgent,
hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces,
repoMap,
workspaceHostScope,
visibleWorkspaceHostIds,
@ -73,6 +75,7 @@ export function useVisibleWorkspaceKanbanWorktreeIds({
filterRepoIds,
hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces,
workspaceHostScope,
visibleWorkspaceHostIds,
settings,

View File

@ -82,6 +82,7 @@ function visibleOptions(overrides: Partial<VisibleOptions> = {}): VisibleOptions
worktreeIdsWithLiveAgent: new Set(),
hideDefaultBranchWorkspace: false,
hideAutomationGeneratedWorkspaces: false,
hideCliCreatedWorkspaces: false,
repoMap,
workspaceHostScope: 'all',
defaultHostId: LOCAL_EXECUTION_HOST_ID,
@ -98,6 +99,7 @@ function filterState(overrides: Partial<FilterState> = {}): FilterState {
filterRepoIds: [],
hideDefaultBranchWorkspace: false,
hideAutomationGeneratedWorkspaces: false,
hideCliCreatedWorkspaces: false,
workspaceHostScope: 'all',
...overrides
}
@ -163,6 +165,57 @@ describe('computeVisibleWorktreeIds', () => {
expect(result).toEqual([manual.id])
})
it('hides CLI-created workspaces when the CLI filter is enabled', () => {
const manual = makeWorktree('manual')
const cliCreated = {
...makeWorktree('cli-created'),
cliProvenance: {
kind: 'created-by-cli' as const,
createdAt: 123,
callerTerminalHandle: 'terminal-1',
startupAgent: 'claude' as const
}
}
const result = computeVisibleWorktreeIds(
{ repo1: [manual, cliCreated] },
[manual.id, cliCreated.id],
visibleOptions({ hideCliCreatedWorkspaces: true })
)
expect(result).toEqual([manual.id])
})
it('keeps CLI-created workspaces visible while the CLI filter is off', () => {
const manual = makeWorktree('manual')
const cliCreated = {
...makeWorktree('cli-created'),
cliProvenance: { kind: 'created-by-cli' as const, createdAt: 123 }
}
const result = computeVisibleWorktreeIds(
{ repo1: [manual, cliCreated] },
[manual.id, cliCreated.id],
visibleOptions()
)
expect(result).toEqual([manual.id, cliCreated.id])
})
it('keeps workspaces without CLI provenance visible when the CLI filter is enabled', () => {
// Why: workspaces persisted before cliProvenance existed have no marker and
// must never be filtered as CLI-created.
const legacy = makeWorktree('legacy')
const result = computeVisibleWorktreeIds(
{ repo1: [legacy] },
[legacy.id],
visibleOptions({ hideCliCreatedWorkspaces: true })
)
expect(result).toEqual([legacy.id])
})
it('does not treat slept wake-hint tabs as live surfaces', () => {
const wt = makeWorktree('wt-slept')
@ -539,6 +592,10 @@ describe('computeVisibleWorktreeIds', () => {
hostId: 'local'
}
expect(run(automationParent, { hideAutomationGeneratedWorkspaces: true })).toEqual([child.id])
const cliParent = makeWorktree('cli-parent')
cliParent.cliProvenance = { kind: 'created-by-cli', createdAt: 1 }
expect(run(cliParent, { hideCliCreatedWorkspaces: true })).toEqual([child.id])
})
it('includes inline lineage ancestors when send-target mode forces a filtered child visible', () => {
@ -733,6 +790,10 @@ describe('sidebarHasActiveFilters', () => {
)
})
it('returns true when only CLI-created workspaces are hidden', () => {
expect(sidebarHasActiveFilters(filterState({ hideCliCreatedWorkspaces: true }))).toBe(true)
})
it('returns true when sleeping workspaces are hidden', () => {
expect(sidebarHasActiveFilters(filterState({ showSleepingWorkspaces: false }))).toBe(true)
})
@ -753,6 +814,7 @@ describe('computeClearFilterActions', () => {
resetFilterRepoIds: false,
resetHideDefaultBranchWorkspace: false,
resetHideAutomationGeneratedWorkspaces: false,
resetHideCliCreatedWorkspaces: false,
resetVisibleWorkspaceHostIds: false
})
})
@ -766,6 +828,7 @@ describe('computeClearFilterActions', () => {
resetFilterRepoIds: false,
resetHideDefaultBranchWorkspace: true,
resetHideAutomationGeneratedWorkspaces: false,
resetHideCliCreatedWorkspaces: false,
resetVisibleWorkspaceHostIds: false
})
})
@ -778,6 +841,18 @@ describe('computeClearFilterActions', () => {
resetFilterRepoIds: false,
resetHideDefaultBranchWorkspace: false,
resetHideAutomationGeneratedWorkspaces: true,
resetHideCliCreatedWorkspaces: false,
resetVisibleWorkspaceHostIds: false
})
})
it('flags only hideCliCreatedWorkspaces for reset when it is the sole filter', () => {
expect(computeClearFilterActions(filterState({ hideCliCreatedWorkspaces: true }))).toEqual({
resetShowSleepingWorkspaces: false,
resetFilterRepoIds: false,
resetHideDefaultBranchWorkspace: false,
resetHideAutomationGeneratedWorkspaces: false,
resetHideCliCreatedWorkspaces: true,
resetVisibleWorkspaceHostIds: false
})
})
@ -801,6 +876,7 @@ describe('computeClearFilterActions', () => {
resetFilterRepoIds: false,
resetHideDefaultBranchWorkspace: false,
resetHideAutomationGeneratedWorkspaces: false,
resetHideCliCreatedWorkspaces: false,
resetVisibleWorkspaceHostIds: true
})
})
@ -821,6 +897,7 @@ describe('computeClearFilterActions', () => {
resetFilterRepoIds: true,
resetHideDefaultBranchWorkspace: true,
resetHideAutomationGeneratedWorkspaces: true,
resetHideCliCreatedWorkspaces: false,
resetVisibleWorkspaceHostIds: true
})
})

View File

@ -33,12 +33,17 @@ export function isAutomationGeneratedWorkspace(worktree: Worktree): boolean {
return worktree.automationProvenance?.kind === 'created-by-automation'
}
export function isCliCreatedWorkspace(worktree: Worktree): boolean {
return worktree.cliProvenance?.kind === 'created-by-cli'
}
/** Inputs describing sidebar filter settings that the Clear Filters path owns. */
export type SidebarFilterState = {
showSleepingWorkspaces: boolean
filterRepoIds: readonly string[]
hideDefaultBranchWorkspace: boolean
hideAutomationGeneratedWorkspaces: boolean
hideCliCreatedWorkspaces: boolean
visibleWorkspaceHostIds?: readonly ExecutionHostId[] | null
workspaceHostScope?: ExecutionHostScope
}
@ -58,6 +63,7 @@ export function sidebarHasActiveFilters(state: SidebarFilterState): boolean {
state.filterRepoIds.length > 0 ||
state.hideDefaultBranchWorkspace ||
state.hideAutomationGeneratedWorkspaces ||
state.hideCliCreatedWorkspaces ||
state.visibleWorkspaceHostIds != null ||
(state.workspaceHostScope != null && state.workspaceHostScope !== ALL_EXECUTION_HOSTS_SCOPE)
)
@ -70,6 +76,7 @@ export type ClearFilterActions = {
resetFilterRepoIds: boolean
resetHideDefaultBranchWorkspace: boolean
resetHideAutomationGeneratedWorkspaces: boolean
resetHideCliCreatedWorkspaces: boolean
resetVisibleWorkspaceHostIds: boolean
}
@ -89,6 +96,7 @@ export function computeClearFilterActions(state: SidebarFilterState): ClearFilte
resetFilterRepoIds: state.filterRepoIds.length > 0,
resetHideDefaultBranchWorkspace: state.hideDefaultBranchWorkspace,
resetHideAutomationGeneratedWorkspaces: state.hideAutomationGeneratedWorkspaces,
resetHideCliCreatedWorkspaces: state.hideCliCreatedWorkspaces,
resetVisibleWorkspaceHostIds:
state.visibleWorkspaceHostIds != null ||
(state.workspaceHostScope != null && state.workspaceHostScope !== ALL_EXECUTION_HOSTS_SCOPE)
@ -123,6 +131,7 @@ export function computeVisibleWorktreeIds(
// forgetting to pass it.
hideDefaultBranchWorkspace: boolean
hideAutomationGeneratedWorkspaces: boolean
hideCliCreatedWorkspaces: boolean
repoMap: Map<string, Repo>
workspaceHostScope: ExecutionHostScope
visibleWorkspaceHostIds?: readonly ExecutionHostId[] | null
@ -149,6 +158,10 @@ export function computeVisibleWorktreeIds(
all = all.filter((w) => !isAutomationGeneratedWorkspace(w))
}
if (opts.hideCliCreatedWorkspaces) {
all = all.filter((w) => !isCliCreatedWorkspace(w))
}
const visibleHostIds =
opts.visibleWorkspaceHostIds ??
(opts.workspaceHostScope === ALL_EXECUTION_HOSTS_SCOPE ? null : [opts.workspaceHostScope])
@ -325,6 +338,7 @@ export function getVisibleWorktreeIds(): string[] {
),
hideDefaultBranchWorkspace: state.hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces: state.hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces: state.hideCliCreatedWorkspaces,
repoMap,
workspaceHostScope: state.workspaceHostScope,
visibleWorkspaceHostIds: state.visibleWorkspaceHostIds,

View File

@ -1,5 +1,9 @@
import type { ExecutionHostId } from '../../../../shared/execution-host'
import type { AutomationWorkspaceProvenance, IssueInfo } from '../../../../shared/types'
import type {
AutomationWorkspaceProvenance,
CliWorkspaceProvenance,
IssueInfo
} from '../../../../shared/types'
import type { WorktreeCardPrDisplay } from './worktree-card-pr-display'
import type { WorktreeCardDetailsHoverControl } from './worktree-card-details-hover-state'
@ -27,6 +31,7 @@ export type WorktreeCardMetaBadgesProps = {
review: WorktreeCardPrDisplay | null
comment: string | null
automationProvenance?: AutomationWorkspaceProvenance | null
cliProvenance?: CliWorkspaceProvenance | null
}
export type WorktreeCardMetaBadgesRootProps = WorktreeCardMetaBadgesProps &

View File

@ -4124,7 +4124,8 @@
"75405270ed": "Edit filters ({{value0}} active)",
"489d1c8c9f": "Search projects...",
"ee240a39eb": "Edit filters",
"automationCreated": "Hide automation-created"
"automationCreated": "Hide automation-created",
"cliCreated": "Hide CLI-created"
},
"SidebarHeader": {
"25a95899c9": "Add Project",
@ -4187,7 +4188,8 @@
"c3fa13dc2e": "Hide default branch",
"ed1611b65b": "Hide sleeping",
"82594419ba": "Filters",
"automationCreated": "Hide automation-created"
"automationCreated": "Hide automation-created",
"cliCreated": "Hide CLI-created"
},
"sidebarHostOptions": {
"3e102f111c": "All hosts",
@ -4250,7 +4252,8 @@
"2d74665a56": "Ports",
"65a9820bd1": "Agent statuses",
"219ebf1961": "Branch name",
"folderPathIdentity": "Branch / folder path"
"folderPathIdentity": "Branch / folder path",
"cli": "Orca CLI"
},
"SshDisconnectedDialog": {
"ca4a7892af": "Connecting...",
@ -4371,7 +4374,12 @@
"checkingAutomationAvailability": "Checking automation availability...",
"automationMissing": "Automation no longer available.",
"automationRunMissing": "Run history no longer available.",
"automationAvailabilityUnavailable": "Automation availability could not be checked."
"automationAvailabilityUnavailable": "Automation availability could not be checked.",
"cliHeader": "Orca CLI",
"cliCreatedFromAgent": "Created by an agent via `orca worktree create`",
"cliCreatedFromShell": "Created via `orca worktree create`",
"cliStartupAgent": "Started with {{value0}}",
"cliCreated": "Created by Orca CLI"
},
"WorktreeCardMetadataStatusBadges": {
"fe188062a1": "State: Open",

View File

@ -4086,7 +4086,8 @@
"75405270ed": "Editar filtros ({{value0}} activo)",
"489d1c8c9f": "Buscar proyectos...",
"ee240a39eb": "Editar filtros",
"automationCreated": "Ocultar creados por automatizaciones"
"automationCreated": "Ocultar creados por automatizaciones",
"cliCreated": "Hide CLI-created"
},
"SidebarHeader": {
"92154beb7e": "Nuevo espacio de trabajo",
@ -4149,7 +4150,8 @@
"c3fa13dc2e": "Ocultar rama predeterminada",
"ed1611b65b": "Ocultar en reposo",
"82594419ba": "Filtros",
"automationCreated": "Ocultar creados por automatizaciones"
"automationCreated": "Ocultar creados por automatizaciones",
"cliCreated": "Hide CLI-created"
},
"SidebarWorkspaceOptionsMenu": {
"95c9754653": "Diseño de actividad del Agent",
@ -4204,7 +4206,8 @@
"2d74665a56": "Puertos",
"65a9820bd1": "Estados de Agents",
"219ebf1961": "Nombre de rama",
"folderPathIdentity": "Rama / ruta de carpeta"
"folderPathIdentity": "Rama / ruta de carpeta",
"cli": "Orca CLI"
},
"SshDisconnectedDialog": {
"ca4a7892af": "Conectando...",
@ -4325,7 +4328,12 @@
"copyLinkSuccess": "{{value0}} copiado",
"copyLinkFailure": "No se pudo copiar el enlace",
"issueLinkLabel": "Enlace del issue",
"reviewLinkLabel": "Enlace de {{value0}}"
"reviewLinkLabel": "Enlace de {{value0}}",
"cliHeader": "Orca CLI",
"cliCreatedFromAgent": "Created by an agent via `orca worktree create`",
"cliCreatedFromShell": "Created via `orca worktree create`",
"cliStartupAgent": "Started with {{value0}}",
"cliCreated": "Created by Orca CLI"
},
"WorktreeCardMetadataStatusBadges": {
"fe188062a1": "Estado: Abierto",

View File

@ -4067,7 +4067,8 @@
"75405270ed": "フィルターの編集 ({{value0}} がアクティブ)",
"489d1c8c9f": "プロジェクトを検索...",
"ee240a39eb": "フィルターの編集",
"automationCreated": "自動化で作成されたワークスペースを非表示"
"automationCreated": "自動化で作成されたワークスペースを非表示",
"cliCreated": "Hide CLI-created"
},
"SidebarHeader": {
"92154beb7e": "新規ワークスペース",
@ -4130,7 +4131,8 @@
"c3fa13dc2e": "デフォルトのブランチを非表示にする",
"ed1611b65b": "スリープ中を非表示",
"82594419ba": "フィルター",
"automationCreated": "自動化で作成されたワークスペースを非表示"
"automationCreated": "自動化で作成されたワークスペースを非表示",
"cliCreated": "Hide CLI-created"
},
"SidebarWorkspaceOptionsMenu": {
"95c9754653": "Agent アクティビティのレイアウト",
@ -4185,7 +4187,8 @@
"65a9820bd1": "Agent ステータス",
"219ebf1961": "ブランチ名",
"automation": "自動化",
"folderPathIdentity": "ブランチ / フォルダパス"
"folderPathIdentity": "ブランチ / フォルダパス",
"cli": "Orca CLI"
},
"SshDisconnectedDialog": {
"ca4a7892af": "接続中...",
@ -4306,7 +4309,12 @@
"copyLinkSuccess": "{{value0}} をコピーしました",
"copyLinkFailure": "リンクのコピーに失敗しました",
"issueLinkLabel": "Issue リンク",
"reviewLinkLabel": "{{value0}} リンク"
"reviewLinkLabel": "{{value0}} リンク",
"cliHeader": "Orca CLI",
"cliCreatedFromAgent": "Created by an agent via `orca worktree create`",
"cliCreatedFromShell": "Created via `orca worktree create`",
"cliStartupAgent": "Started with {{value0}}",
"cliCreated": "Created by Orca CLI"
},
"WorktreeCardMetadataStatusBadges": {
"fe188062a1": "状態: オープン",

View File

@ -4067,7 +4067,8 @@
"75405270ed": "필터 편집({{value0}} 활성)",
"489d1c8c9f": "프로젝트 검색...",
"ee240a39eb": "필터 편집",
"automationCreated": "자동화로 생성된 워크스페이스 숨기기"
"automationCreated": "자동화로 생성된 워크스페이스 숨기기",
"cliCreated": "Hide CLI-created"
},
"SidebarHeader": {
"92154beb7e": "새로운 워크스페이스",
@ -4130,7 +4131,8 @@
"c3fa13dc2e": "기본 브랜치 숨기기",
"ed1611b65b": "슬립 중인 항목 숨기기",
"82594419ba": "필터",
"automationCreated": "자동화로 생성된 워크스페이스 숨기기"
"automationCreated": "자동화로 생성된 워크스페이스 숨기기",
"cliCreated": "Hide CLI-created"
},
"SidebarWorkspaceOptionsMenu": {
"95c9754653": "Agent 활동 레이아웃",
@ -4185,7 +4187,8 @@
"65a9820bd1": "Agent 상태",
"219ebf1961": "브랜치 이름",
"automation": "자동화",
"folderPathIdentity": "브랜치 / 폴더 경로"
"folderPathIdentity": "브랜치 / 폴더 경로",
"cli": "Orca CLI"
},
"SshDisconnectedDialog": {
"ca4a7892af": "연결 중...",
@ -4306,7 +4309,12 @@
"copyLinkSuccess": "{{value0}} 복사됨",
"copyLinkFailure": "링크 복사 실패",
"issueLinkLabel": "이슈 링크",
"reviewLinkLabel": "{{value0}} 링크"
"reviewLinkLabel": "{{value0}} 링크",
"cliHeader": "Orca CLI",
"cliCreatedFromAgent": "Created by an agent via `orca worktree create`",
"cliCreatedFromShell": "Created via `orca worktree create`",
"cliStartupAgent": "Started with {{value0}}",
"cliCreated": "Created by Orca CLI"
},
"WorktreeCardMetadataStatusBadges": {
"fe188062a1": "상태: 열림",

View File

@ -4067,7 +4067,8 @@
"75405270ed": "编辑筛选条件({{value0}} 个已启用)",
"489d1c8c9f": "搜索项目...",
"ee240a39eb": "编辑筛选条件",
"automationCreated": "隐藏自动化创建的工作区"
"automationCreated": "隐藏自动化创建的工作区",
"cliCreated": "Hide CLI-created"
},
"SidebarHeader": {
"92154beb7e": "新工作区",
@ -4130,7 +4131,8 @@
"c3fa13dc2e": "隐藏默认分支",
"ed1611b65b": "隐藏休眠项",
"82594419ba": "筛选条件",
"automationCreated": "隐藏自动化创建的工作区"
"automationCreated": "隐藏自动化创建的工作区",
"cliCreated": "Hide CLI-created"
},
"SidebarWorkspaceOptionsMenu": {
"95c9754653": "智能体活动布局",
@ -4185,7 +4187,8 @@
"65a9820bd1": "智能体状态",
"219ebf1961": "分支名称",
"automation": "自动化",
"folderPathIdentity": "分支 / 文件夹路径"
"folderPathIdentity": "分支 / 文件夹路径",
"cli": "Orca CLI"
},
"SshDisconnectedDialog": {
"ca4a7892af": "正在连接...",
@ -4306,7 +4309,12 @@
"copyLinkSuccess": "{{value0}} 已复制",
"copyLinkFailure": "复制链接失败",
"issueLinkLabel": "议题链接",
"reviewLinkLabel": "{{value0}} 链接"
"reviewLinkLabel": "{{value0}} 链接",
"cliHeader": "Orca CLI",
"cliCreatedFromAgent": "Created by an agent via `orca worktree create`",
"cliCreatedFromShell": "Created via `orca worktree create`",
"cliStartupAgent": "Started with {{value0}}",
"cliCreated": "Created by Orca CLI"
},
"WorktreeCardMetadataStatusBadges": {
"fe188062a1": "状态:开放",

View File

@ -48,6 +48,7 @@ export function getStartupErrorFallbackUI(uiHydrated: boolean): PersistedUIState
hideSleepingWorkspaces: DEFAULT_HIDE_SLEEPING_WORKSPACES,
showSleepingWorkspaces: DEFAULT_SHOW_SLEEPING_WORKSPACES,
hideDefaultBranchWorkspace: false,
hideCliCreatedWorkspaces: false,
hideAutomationGeneratedWorkspaces: false,
filterRepoIds: [],
collapsedGroups: [],

View File

@ -364,6 +364,9 @@ export function activateAndRevealWorktree(
) {
state.setHideAutomationGeneratedWorkspaces(false)
}
if (state.hideCliCreatedWorkspaces && wt.cliProvenance?.kind === 'created-by-cli') {
state.setHideCliCreatedWorkspaces(false)
}
// 6. Reveal in sidebar
if (opts?.revealInSidebar !== false) {

View File

@ -1650,6 +1650,7 @@ describe('reconnectPersistedTerminals', () => {
worktreeIdsWithLiveAgent: new Set(),
hideDefaultBranchWorkspace: false,
hideAutomationGeneratedWorkspaces: false,
hideCliCreatedWorkspaces: false,
repoMap: new Map(s.repos.map((repo) => [repo.id, repo])),
workspaceHostScope: 'all',
defaultHostId: LOCAL_EXECUTION_HOST_ID,
@ -1995,6 +1996,7 @@ describe('reconnectPersistedTerminals', () => {
worktreeIdsWithLiveAgent: new Set(),
hideDefaultBranchWorkspace: false,
hideAutomationGeneratedWorkspaces: false,
hideCliCreatedWorkspaces: false,
repoMap: new Map(s.repos.map((repo) => [repo.id, repo])),
workspaceHostScope: 'all',
defaultHostId: LOCAL_EXECUTION_HOST_ID,

View File

@ -866,6 +866,8 @@ export type UISlice = {
setHideDefaultBranchWorkspace: (v: boolean) => void
hideAutomationGeneratedWorkspaces: boolean
setHideAutomationGeneratedWorkspaces: (v: boolean) => void
hideCliCreatedWorkspaces: boolean
setHideCliCreatedWorkspaces: (v: boolean) => void
showDotfilesByWorktree: Record<string, boolean>
setShowDotfilesForWorktree: (worktreeId: string, showDotfiles: boolean) => void
toggleShowDotfilesForWorktree: (worktreeId: string) => void
@ -2029,6 +2031,8 @@ export const createUISlice: StateCreator<AppState, [], [], UISlice> = (set, get)
setHideDefaultBranchWorkspace: (v) => set({ hideDefaultBranchWorkspace: v }),
hideAutomationGeneratedWorkspaces: false,
setHideAutomationGeneratedWorkspaces: (v) => set({ hideAutomationGeneratedWorkspaces: v }),
hideCliCreatedWorkspaces: false,
setHideCliCreatedWorkspaces: (v) => set({ hideCliCreatedWorkspaces: v }),
showDotfilesByWorktree: {},
setShowDotfilesForWorktree: (worktreeId, showDotfiles) =>
@ -2427,6 +2431,7 @@ export const createUISlice: StateCreator<AppState, [], [], UISlice> = (set, get)
repos: orderedRepos,
hideDefaultBranchWorkspace: ui.hideDefaultBranchWorkspace ?? false,
hideAutomationGeneratedWorkspaces: ui.hideAutomationGeneratedWorkspaces === true,
hideCliCreatedWorkspaces: ui.hideCliCreatedWorkspaces === true,
showDotfilesByWorktree: sanitizeShowDotfilesByWorktree(ui.showDotfilesByWorktree),
// Why: startup hydrates UI before repo catalogs, so defer repo-filter validation to the all-host refresh.
filterRepoIds:

View File

@ -0,0 +1,49 @@
import { describe, expect, it } from 'vitest'
import { buildCliWorkspaceProvenance } from './cli-workspace-provenance'
describe('buildCliWorkspaceProvenance', () => {
it('returns undefined when the caller sent no CLI provenance request', () => {
// Why: desktop/mobile/automation creates must stay unmarked.
expect(buildCliWorkspaceProvenance(undefined, { createdAt: 5 })).toBeUndefined()
})
it('marks a bare create with the host-stamped timestamp', () => {
expect(buildCliWorkspaceProvenance({}, { createdAt: 5 })).toEqual({
kind: 'created-by-cli',
createdAt: 5
})
})
it('ignores a client-supplied createdAt so a skewed CLI clock cannot drive sort order', () => {
const provenance = buildCliWorkspaceProvenance(
{ createdAt: 9_999 } as Parameters<typeof buildCliWorkspaceProvenance>[0],
{ createdAt: 5 }
)
expect(provenance?.createdAt).toBe(5)
})
it('records the caller terminal handle for agent-issued creates', () => {
expect(
buildCliWorkspaceProvenance({ callerTerminalHandle: 'term-1' }, { createdAt: 5 })
).toEqual({
kind: 'created-by-cli',
createdAt: 5,
callerTerminalHandle: 'term-1'
})
})
it('omits an empty terminal handle rather than persisting a blank field', () => {
expect(buildCliWorkspaceProvenance({ callerTerminalHandle: '' }, { createdAt: 5 })).toEqual({
kind: 'created-by-cli',
createdAt: 5
})
})
it('records the startup agent when the create passed --agent', () => {
expect(buildCliWorkspaceProvenance({}, { createdAt: 5, startupAgent: 'claude' })).toEqual({
kind: 'created-by-cli',
createdAt: 5,
startupAgent: 'claude'
})
})
})

View File

@ -0,0 +1,22 @@
import type { CliWorkspaceProvenance, TuiAgent } from './types'
/** Client-supplied context for a `orca worktree create`. Descriptive only
* the host stamps `createdAt` so a skewed client clock can't affect sort order. */
export type CliWorkspaceProvenanceRequest = {
callerTerminalHandle?: string
}
export function buildCliWorkspaceProvenance(
request: CliWorkspaceProvenanceRequest | undefined,
options: { startupAgent?: TuiAgent; createdAt: number }
): CliWorkspaceProvenance | undefined {
if (!request) {
return undefined
}
return {
kind: 'created-by-cli',
createdAt: options.createdAt,
...(request.callerTerminalHandle ? { callerTerminalHandle: request.callerTerminalHandle } : {}),
...(options.startupAgent ? { startupAgent: options.startupAgent } : {})
}
}

View File

@ -459,6 +459,7 @@ export function getDefaultUIState(): PersistedUIState {
showSleepingWorkspaces: DEFAULT_SHOW_SLEEPING_WORKSPACES,
hideDefaultBranchWorkspace: false,
hideAutomationGeneratedWorkspaces: false,
hideCliCreatedWorkspaces: false,
showDotfilesByWorktree: {},
filterRepoIds: [],
collapsedGroups: [],

View File

@ -535,8 +535,22 @@ export type Worktree = {
diffComments?: DiffComment[]
mobileDiffReview?: MobileDiffReviewState
automationProvenance?: AutomationWorkspaceProvenance
cliProvenance?: CliWorkspaceProvenance
} & GitWorktreeInfo
/** Provenance for workspaces created through `orca worktree create`. Absent on
* workspaces created before this field existed and on every non-CLI create, so
* consumers must read "missing" as "not CLI-created". */
export type CliWorkspaceProvenance = {
kind: 'created-by-cli'
createdAt: number
/** Orca terminal the CLI ran inside, when the caller had one distinguishes
* an agent-issued create from one hand-typed in an external shell. */
callerTerminalHandle?: string
/** Agent requested via `--agent`, when one was passed. */
startupAgent?: TuiAgent
}
export type AutomationWorkspaceProvenance = {
kind: 'created-by-automation'
automationId: string
@ -646,6 +660,8 @@ export type WorktreeMeta = {
mobileDiffReview?: MobileDiffReviewState
/** System-owned provenance for workspaces created by automation new-per-run dispatches. */
automationProvenance?: AutomationWorkspaceProvenance
/** System-owned provenance for workspaces created via `orca worktree create`. */
cliProvenance?: CliWorkspaceProvenance
}
export type WorktreeOwnership = 'orca-managed' | 'external' | 'unknown-legacy' | 'agent-scratch'
@ -3137,6 +3153,8 @@ export type WorktreeCardProperty =
| 'linear-issue'
| 'pr'
| 'automation'
// Badge marking workspaces created through `orca worktree create`.
| 'cli'
| 'comment'
| 'ports'
// Inline agent-activity list rendered in each workspace card; on by default (see DEFAULT_WORKTREE_CARD_PROPERTIES in shared/constants.ts).
@ -3245,6 +3263,8 @@ export type PersistedUIState = {
hideDefaultBranchWorkspace: boolean
/** Hide workspaces created by automation new-per-run dispatches. */
hideAutomationGeneratedWorkspaces?: boolean
/** Hide workspaces created through `orca worktree create`. */
hideCliCreatedWorkspaces?: boolean
/** Per-worktree Explorer dotfile visibility. Missing entries inherit the default: show. */
showDotfilesByWorktree?: Record<string, boolean>
filterRepoIds: string[]

View File

@ -14,6 +14,7 @@ export const DEFAULT_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = [
...TASK_WORKTREE_CARD_PROPERTIES,
'pr',
'automation',
'cli',
'comment',
'ports',
// Why: agent activity is the primary reason users opt into the feature, so
@ -47,6 +48,7 @@ const WORKTREE_CARD_PROPERTY_ORDER: WorktreeCardProperty[] = [
'linear-issue',
'pr',
'automation',
'cli',
'comment',
'ports',
'inline-agents'