Files
orca/src/main/ipc/worktree-path-comparison.ts
T
Brennan BensonandBrennan Benson 0b65d725c9 "Hide sleeping" never hides a workspace with an open agent session (#7197) (#7511)
* Keep running-agent workspaces visible under "Hide sleeping" (#7197)

The "Hide sleeping" sidebar filter judged a workspace active only when it
had a live PTY (or a browser tab), so a workspace with a running agent
whose live-PTY entry was momentarily absent — an SSH reconnect grace
window, an unmounted pane, a remote surface not yet `ready`, or an
orchestration worker reporting before its tab is mirrored — was
classified as "sleeping" and hidden while its session was still open.

The smart sort already treats a fresh `agentStatusByPaneKey` entry as
"working" independent of live-PTY, so the filter and sort disagreed. Add
`getWorktreeIdsWithLiveAgent`, which derives the worktrees with an open
agent session from the live agent-status map (sleep/teardown drop those
entries via dropAgentStatusByWorktree, so slept/hibernated workspaces
still hide), and consult it in `hasActiveWorkspaceActivity`. Wire it
through the sidebar list, Cmd+J jump palette, and kanban board.

* fix(agent-status): align live workspace attribution

* fix(mobile): preserve live-agent workspace activity

* fix(mobile): prefer newest agent status source

* chore: restore unrelated benchmark formatting

* fix(mobile): resolve projected agent worktree ids

* perf(mobile): index projected worktree summaries

* fix(mobile): preserve projected activity under limits

* fix(mobile): preserve POSIX path identity

* perf(mobile): cache projected summary fallbacks

* fix(mobile): preserve remote path and priority contracts

* perf(mobile): index projected paths by host flavor

* test(mobile): prove projected path index keys

* perf(mobile): bound projected path fallback

* perf(mobile): reuse projected repo platforms

* test(mobile): enforce projected lookup bounds

* chore(runtime): remove review instrumentation

* perf(mobile): skip unresolved repo platform scans

* perf(mobile): batch represented project runtimes

* perf(mobile): batch cold project runtime scans

* fix(mobile): couple worktree platform snapshots

* fix(sidebar): prioritize attributed headless agents

* fix(sidebar): activate smart sort for headless agents

* fix(sidebar): prefer mirrored agent ownership

* fix(mobile): follow mirrored agent ownership

* fix(sidebar): resolve mirrored unstamped agents

---------

Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
2026-07-13 01:10:38 -07:00

119 lines
4.2 KiB
TypeScript

import { posix, win32 } from 'node:path'
import { isWindowsAbsolutePathLike } from '../../shared/cross-platform-path'
export function areWorktreePathsEqual(
leftPath: string,
rightPath: string,
platform = process.platform
): boolean {
if (looksLikePosixAbsolutePath(leftPath) || looksLikePosixAbsolutePath(rightPath)) {
// Why: local WSL projects run POSIX paths on a Windows desktop; comparing
// them with win32 rules can delete or dedupe the wrong runtime-owned path.
if (!looksLikePosixAbsolutePath(leftPath) || !looksLikePosixAbsolutePath(rightPath)) {
return false
}
const left = normalizePosixWorktreePathForComparison(leftPath, platform)
const right = normalizePosixWorktreePathForComparison(rightPath, platform)
return left === right
}
if (
platform === 'win32' ||
isWindowsAbsolutePathLike(leftPath) ||
isWindowsAbsolutePathLike(rightPath)
) {
const left = normalizeWindowsWorktreePathForComparison(leftPath)
const right = normalizeWindowsWorktreePathForComparison(rightPath)
// Why: Git can report the same Windows path with different slash styles or
// drive-letter casing; treating them as distinct creates duplicate worktrees.
return left === right
}
const left = normalizePosixWorktreePathForComparison(leftPath, platform)
const right = normalizePosixWorktreePathForComparison(rightPath, platform)
return left === right
}
export function worktreePathComparisonKey(pathValue: string, platform = process.platform): string {
if (looksLikePosixAbsolutePath(pathValue)) {
return `posix:${normalizePosixWorktreePathForComparison(pathValue, platform)}`
}
if (platform === 'win32' || isWindowsAbsolutePathLike(pathValue)) {
return `windows:${normalizeWindowsWorktreePathForComparison(pathValue)}`
}
return `posix:${normalizePosixWorktreePathForComparison(pathValue, platform)}`
}
export function dedupeWorktreesByPath<T extends { path: string }>(
worktrees: readonly T[],
platform = process.platform
): T[] {
// Why: large Git/relay listings should normalize each path once while still
// preserving the first row under the cross-platform equality contract above.
const unique: T[] = []
const posixAbsoluteKeys = new Set<string>()
const windowsKeys = new Set<string>()
const windowsPaths: string[] = []
const relativePaths: string[] = []
for (const worktree of worktrees) {
const pathValue = worktree.path
if (looksLikePosixAbsolutePath(pathValue)) {
const key = normalizePosixWorktreePathForComparison(pathValue, platform)
if (posixAbsoluteKeys.has(key)) {
continue
}
posixAbsoluteKeys.add(key)
unique.push(worktree)
continue
}
const windowsKey = normalizeWindowsWorktreePathForComparison(pathValue)
if (platform === 'win32' || isWindowsAbsolutePathLike(pathValue)) {
if (
windowsKeys.has(windowsKey) ||
relativePaths.some((existing) => areWorktreePathsEqual(existing, pathValue, platform))
) {
continue
}
windowsKeys.add(windowsKey)
windowsPaths.push(pathValue)
unique.push(worktree)
continue
}
// Why: Git normally reports absolute paths. Retain pair-aware comparison
// only for malformed/legacy relative rows whose flavor depends on its peer.
if (
relativePaths.some((existing) => areWorktreePathsEqual(existing, pathValue, platform)) ||
windowsPaths.some((existing) => areWorktreePathsEqual(existing, pathValue, platform))
) {
continue
}
relativePaths.push(pathValue)
unique.push(worktree)
}
return unique
}
function looksLikePosixAbsolutePath(pathValue: string): boolean {
return pathValue.startsWith('/') && !pathValue.startsWith('//')
}
function normalizeWindowsWorktreePathForComparison(pathValue: string): string {
return win32.normalize(win32.resolve(pathValue)).toLowerCase()
}
function normalizePosixWorktreePathForComparison(
pathValue: string,
platform: NodeJS.Platform
): string {
const normalized = posix.normalize(posix.resolve(pathValue))
if (platform !== 'darwin') {
return normalized
}
if (normalized === '/private/tmp') {
return '/tmp'
}
return normalized.startsWith('/private/tmp/') ? normalized.slice('/private'.length) : normalized
}