Files
orca/src/main/runtime/runtime-worktree-ps-activity.ts
T
Neil a261091cd6 refactor: drop the runtimeWorktreeIdsEqual alias and name the WSL hook relay gate for what it gates (#30264)
Importers call worktreeIdsEqual from shared/worktree/id directly (the alias was a renamed
re-export). isRemoteAgentHooksEnabled becomes isWslAgentHookRelayEnabled; the env var and its
parsing are unchanged. ws-transport inlines its one-line HTTP server factory, and a duplicated
acquisition-id doc comment goes.
2026-10-18 01:27:32 -07:00

255 lines
9.2 KiB
TypeScript

import type { RuntimeSyncedTab, RuntimeWorktreePsSummary } from '../../shared/runtime-types'
import type { Repo } from '../../shared/repo-types'
import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types'
import { getRepoExecutionHostId, type ExecutionHostId } from '../../shared/execution-host'
import { parsePaneKey } from '../../shared/stable-pane-id'
import { getWorkspaceSessionTabActivityIds } from '../../shared/workspace-session-tab-activity'
import type { RuntimeStore } from './runtime-store-contract'
import type { RuntimeLeafRecord, RuntimePtyWorktreeRecord } from './runtime-terminal-state-records'
import type { RuntimeWorktreePsSummaryLookup } from './runtime-worktree-summary-paths'
import {
getLatestPtyTitle,
getLeafDisplayRecord,
getLeafWorktreeStatus,
getPtyDisplayRecord,
getSavedTabWorktreeStatus,
maxTimestamp,
mergeWorktreeSummaryStatus,
type TitleDisplayClear
} from './runtime-worktree-status-projection'
import { worktreeIdsEqual } from '../../shared/worktree/id'
export type RuntimeWorkingTerminalEvidence = {
paneKey: string | null
ptyId: string | null
tabId: string | null
}
export function applyRuntimeWorktreePsTerminalActivity(args: {
freshPtyLiveness: ReadonlySet<string> | null
/** Filled with every PTY this pass counts as live. */
countedPtyIds: Set<string>
leaves: Iterable<RuntimeLeafRecord>
ptysById: ReadonlyMap<string, RuntimePtyWorktreeRecord>
tabs: ReadonlyMap<string, RuntimeSyncedTab>
session: WorkspaceSessionState | null | undefined
getPaneKey: (leaf: RuntimeLeafRecord) => string
getTitleDisplayClear: (ptyId: string) => TitleDisplayClear | null
getSummary: RuntimeWorktreePsSummaryLookup
}): Map<string, RuntimeWorkingTerminalEvidence[]> {
const workingEvidence = new Map<string, RuntimeWorkingTerminalEvidence[]>()
const savedTabOwnerById = new Map<string, { worktreeId: string; title: string }>()
for (const [worktreeId, tabs] of Object.entries(args.session?.tabsByWorktree ?? {})) {
for (const tab of tabs) {
savedTabOwnerById.set(tab.id, { worktreeId, title: tab.title })
}
}
const savedLayoutTabIdByPtyId = new Map<string, string>()
for (const [tabId, layout] of Object.entries(args.session?.terminalLayoutsByTabId ?? {})) {
for (const ptyId of Object.values(layout?.ptyIdsByLeafId ?? {})) {
if (ptyId) {
savedLayoutTabIdByPtyId.set(ptyId, tabId)
}
}
}
const countedPtyIds = args.countedPtyIds
for (const leaf of args.leaves) {
if (
!leaf.ptyId ||
!leaf.connected ||
(args.freshPtyLiveness !== null && !args.freshPtyLiveness.has(leaf.ptyId))
) {
continue
}
const freshOwner = args.ptysById.get(leaf.ptyId)
if (
args.freshPtyLiveness !== null &&
freshOwner?.connected &&
!worktreeIdsEqual(freshOwner.worktreeId, leaf.worktreeId)
) {
continue
}
const summary = args.getSummary(leaf.worktreeId)
if (!summary) {
continue
}
countedPtyIds.add(leaf.ptyId)
summary.hasHostSidebarActivity = true
const previousLastOutputAt = summary.lastOutputAt
summary.liveTerminalCount += 1
summary.hasAttachedPty = true
summary.lastOutputAt = maxTimestamp(summary.lastOutputAt, leaf.lastOutputAt)
const leafStatus = getLeafWorktreeStatus(
getLeafDisplayRecord(leaf, args.getTitleDisplayClear(leaf.ptyId)),
args.tabs.get(leaf.tabId)?.title ?? null
)
if (leafStatus === 'working') {
addWorkingTerminalEvidence(workingEvidence, summary.worktreeId, {
paneKey: args.getPaneKey(leaf),
ptyId: leaf.ptyId,
tabId: leaf.tabId
})
}
mergeWorktreeSummaryStatus(summary, leafStatus)
if (
leaf.preview &&
(summary.preview.length === 0 || (leaf.lastOutputAt ?? -1) >= (previousLastOutputAt ?? -1))
) {
summary.preview = leaf.preview
}
}
for (const pty of args.ptysById.values()) {
if (
!pty.connected ||
countedPtyIds.has(pty.ptyId) ||
(args.freshPtyLiveness !== null && !args.freshPtyLiveness.has(pty.ptyId))
) {
continue
}
const persistedTabId = savedLayoutTabIdByPtyId.get(pty.ptyId)
const displayTitle = getLatestPtyTitle(
getPtyDisplayRecord(pty, args.getTitleDisplayClear(pty.ptyId))
)
let owner = persistedTabId ? savedTabOwnerById.get(persistedTabId) : undefined
if (args.freshPtyLiveness !== null) {
owner = { worktreeId: pty.worktreeId, title: owner?.title ?? displayTitle ?? '' }
}
if (!owner && persistedTabId && pty.tabId === persistedTabId) {
owner = { worktreeId: pty.worktreeId, title: displayTitle ?? '' }
}
const pane = parsePaneKey(pty.paneKey ?? '')
const hasExplicitOwner =
pty.tabId !== null && pane?.tabId === pty.tabId && pane.leafId.length > 0
const savedOwner = pty.tabId ? savedTabOwnerById.get(pty.tabId) : undefined
const hasSavedLayout =
pty.tabId !== null && Object.hasOwn(args.session?.terminalLayoutsByTabId ?? {}, pty.tabId)
if (!owner && hasExplicitOwner && !hasSavedLayout) {
owner = {
worktreeId: savedOwner?.worktreeId ?? pty.worktreeId,
title: savedOwner?.title ?? displayTitle ?? ''
}
}
if (!owner) {
continue
}
const summary = args.getSummary(owner.worktreeId)
if (!summary) {
continue
}
countedPtyIds.add(pty.ptyId)
const previousLastOutputAt = summary.lastOutputAt
summary.liveTerminalCount += 1
summary.hasAttachedPty = true
summary.hasHostSidebarActivity = true
summary.lastOutputAt = maxTimestamp(summary.lastOutputAt, pty.lastOutputAt)
const ptyStatus = getSavedTabWorktreeStatus(owner.title, true)
if (ptyStatus === 'working') {
addWorkingTerminalEvidence(workingEvidence, summary.worktreeId, {
paneKey: pty.paneKey,
ptyId: pty.ptyId,
tabId: pty.tabId ?? persistedTabId ?? null
})
}
mergeWorktreeSummaryStatus(summary, ptyStatus)
if (
pty.preview &&
(summary.preview.length === 0 || (pty.lastOutputAt ?? -1) >= (previousLastOutputAt ?? -1))
) {
summary.preview = pty.preview
}
}
return workingEvidence
}
function addWorkingTerminalEvidence(
evidenceByWorktreeId: Map<string, RuntimeWorkingTerminalEvidence[]>,
worktreeId: string,
evidence: RuntimeWorkingTerminalEvidence
): void {
const existing = evidenceByWorktreeId.get(worktreeId)
if (existing) {
existing.push(evidence)
} else {
evidenceByWorktreeId.set(worktreeId, [evidence])
}
}
export function applyRuntimeWorktreePsSessionActivity(args: {
store: RuntimeStore | null
summaries: Map<string, RuntimeWorktreePsSummary>
repoById: ReadonlyMap<string, Repo>
ptysById: ReadonlyMap<string, RuntimePtyWorktreeRecord>
tabs: ReadonlyMap<string, RuntimeSyncedTab>
/** Non-minting: a listing must not issue handles, only recognise the ones already bound. */
getTerminalHandlesForPty: (ptyId: string) => readonly string[]
getSummary: RuntimeWorktreePsSummaryLookup
}): {
mirroredWorktreeIdByTabId: Map<string, string>
connectedPtyEvidence: {
tabIds: Set<string>
paneKeys: Set<string>
ptyIdByTerminalHandle: Map<string, string>
}
} {
const mirroredWorktreeIdByTabId = new Map<string, string>()
const sessionsByHostId = new Map<ExecutionHostId, WorkspaceSessionState>()
for (const summary of args.summaries.values()) {
const repo = args.repoById.get(summary.repoId)
const session = args.store?.getWorkspaceSession?.(repo ? getRepoExecutionHostId(repo) : 'local')
if (session) {
sessionsByHostId.set(repo ? getRepoExecutionHostId(repo) : 'local', session)
}
}
for (const session of sessionsByHostId.values()) {
for (const [worktreeId, tabs] of Object.entries(session.tabsByWorktree ?? {})) {
for (const tab of tabs) {
mirroredWorktreeIdByTabId.set(tab.id, worktreeId)
}
if (tabs.length === 0) {
continue
}
const summary = args.getSummary(worktreeId)
if (summary && tabs.some((tab) => tab.ptyId && args.ptysById.get(tab.ptyId)?.connected)) {
summary.hasHostSidebarActivity = true
}
}
for (const worktreeId of getWorkspaceSessionTabActivityIds(session)) {
const summary = args.getSummary(worktreeId)
if (summary) {
summary.hasHostSidebarActivity = true
}
}
if (session.activeWorktreeId) {
const summary = args.getSummary(session.activeWorktreeId)
if (summary) {
summary.isActive = true
}
}
}
for (const [tabId, tab] of args.tabs) {
if (!mirroredWorktreeIdByTabId.has(tabId)) {
mirroredWorktreeIdByTabId.set(tabId, tab.worktreeId)
}
}
const connectedPtyEvidence = {
tabIds: new Set<string>(),
paneKeys: new Set<string>(),
ptyIdByTerminalHandle: new Map<string, string>()
}
for (const pty of args.ptysById.values()) {
if (!pty.connected) {
continue
}
if (pty.tabId) {
connectedPtyEvidence.tabIds.add(pty.tabId)
}
if (pty.paneKey) {
connectedPtyEvidence.paneKeys.add(pty.paneKey)
}
for (const terminalHandle of args.getTerminalHandlesForPty(pty.ptyId)) {
connectedPtyEvidence.ptyIdByTerminalHandle.set(terminalHandle, pty.ptyId)
}
}
return { mirroredWorktreeIdByTabId, connectedPtyEvidence }
}