feat(sidebar): distinguish and filter CLI-created workspaces (#10712)
This commit is contained in:
parent
627cde33d5
commit
28dfc13654
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
: {}),
|
||||
|
|
|
|||
|
|
@ -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 } : {}),
|
||||
|
|
|
|||
|
|
@ -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 } : {}),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ const ORCA_OWNED_PROVENANCE_META_KEYS = [
|
|||
'orcaCreatedAt',
|
||||
'orcaCreationSource',
|
||||
'orcaCreationWorkspaceLayout',
|
||||
'automationProvenance'
|
||||
'automationProvenance',
|
||||
'cliProvenance'
|
||||
] as const
|
||||
type UnregisteredOrcaCleanupMeta = Pick<
|
||||
WorktreeMeta,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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'}`
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
|
@ -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
|
||||
])
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 &
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": "状態: オープン",
|
||||
|
|
|
|||
|
|
@ -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": "상태: 열림",
|
||||
|
|
|
|||
|
|
@ -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": "状态:开放",
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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 } : {})
|
||||
}
|
||||
}
|
||||
|
|
@ -459,6 +459,7 @@ export function getDefaultUIState(): PersistedUIState {
|
|||
showSleepingWorkspaces: DEFAULT_SHOW_SLEEPING_WORKSPACES,
|
||||
hideDefaultBranchWorkspace: false,
|
||||
hideAutomationGeneratedWorkspaces: false,
|
||||
hideCliCreatedWorkspaces: false,
|
||||
showDotfilesByWorktree: {},
|
||||
filterRepoIds: [],
|
||||
collapsedGroups: [],
|
||||
|
|
|
|||
|
|
@ -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[]
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Reference in New Issue