mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 18:47:40 +00:00
The desktop card shows SSH status on any host count, so the phone now reads target summaries whenever a repo is on SSH and badges an unhealthy host alone too. Labels and health share one target-to-host keying, and a hook-level test pins the reply-to-row wiring. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
114 lines
3.8 KiB
TypeScript
114 lines
3.8 KiB
TypeScript
import {
|
|
getExecutionHostLabel,
|
|
getLocalExecutionHostLabel,
|
|
normalizeExecutionHostId,
|
|
parseExecutionHostId,
|
|
toSshExecutionHostId,
|
|
type ExecutionHostId
|
|
} from '../execution-host'
|
|
import type { GlobalSettings } from '../global-settings-types'
|
|
import { getHostDisplayLabelOverrides } from '../host-setting-overrides'
|
|
|
|
/** Inputs used by every client when spelling a host in a workspace row. */
|
|
export type HostContextLabelSources = {
|
|
/** Explicit labels (SSH target names and per-host display overrides). */
|
|
hostLabelById?: ReadonlyMap<string, string>
|
|
/** The execution host's platform; clients must not use the device platform. */
|
|
hostPlatform?: NodeJS.Platform | null
|
|
}
|
|
|
|
/** Canonical user-facing host label used by desktop and mobile workspace rows. */
|
|
export function getHostContextLabel(
|
|
hostId: ExecutionHostId,
|
|
sources: HostContextLabelSources = {}
|
|
): string {
|
|
const override = sources.hostLabelById?.get(hostId)?.trim()
|
|
if (override) {
|
|
return override
|
|
}
|
|
if (parseExecutionHostId(hostId)?.kind === 'local') {
|
|
// An explicit null means the paired host platform is unknown (mobile); an
|
|
// omitted platform means use the current process (desktop).
|
|
if (sources.hostPlatform === null) {
|
|
return 'This computer'
|
|
}
|
|
return sources.hostPlatform !== undefined
|
|
? getLocalExecutionHostLabel(sources.hostPlatform)
|
|
: getExecutionHostLabel(hostId)
|
|
}
|
|
return getExecutionHostLabel(hostId)
|
|
}
|
|
|
|
/**
|
|
* The host id an SSH target summary names. Summaries have carried both raw target ids and
|
|
* canonical `ssh:` ids across protocol versions, so accept either representation.
|
|
*/
|
|
export function sshTargetSummaryHostId(targetId: string): ExecutionHostId {
|
|
return normalizeExecutionHostId(targetId) ?? toSshExecutionHostId(targetId)
|
|
}
|
|
|
|
/** Build labels for SSH targets and apply persisted display-name overrides. */
|
|
export function buildHostLabelById(args: {
|
|
sshTargets: readonly { id: string; label: string }[]
|
|
hostSettingOverrides: unknown
|
|
}): Map<ExecutionHostId, string> {
|
|
const labels = new Map<ExecutionHostId, string>()
|
|
for (const target of args.sshTargets) {
|
|
const label = target.label.trim()
|
|
if (!target.id.trim() || !label) {
|
|
continue
|
|
}
|
|
labels.set(sshTargetSummaryHostId(target.id), label)
|
|
}
|
|
const overrides =
|
|
args.hostSettingOverrides && typeof args.hostSettingOverrides === 'object'
|
|
? getHostDisplayLabelOverrides({
|
|
hostSettingOverrides: args.hostSettingOverrides as GlobalSettings['hostSettingOverrides']
|
|
})
|
|
: new Map<ExecutionHostId, string>()
|
|
for (const [hostId, label] of overrides) {
|
|
const normalized = normalizeExecutionHostId(hostId) ?? toSshExecutionHostId(hostId)
|
|
if (normalized) {
|
|
labels.set(normalized, label)
|
|
}
|
|
}
|
|
return labels
|
|
}
|
|
|
|
/** Generic mixed-host projection shared by desktop grouping and mobile sections. */
|
|
export function getMixedHostContextLabels<T>(
|
|
items: readonly T[],
|
|
args: {
|
|
getHostId: (item: T) => ExecutionHostId
|
|
getIdentity: (item: T) => string
|
|
sources?: HostContextLabelSources
|
|
}
|
|
): Map<string, string> | undefined {
|
|
// Why two passes: on a single-host install the map is built for every item and then discarded.
|
|
// Deciding first costs one getHostId per item and skips the label work entirely.
|
|
let firstHostId: ExecutionHostId | undefined
|
|
let isMixed = false
|
|
for (const item of items) {
|
|
const hostId = args.getHostId(item)
|
|
if (firstHostId === undefined) {
|
|
firstHostId = hostId
|
|
continue
|
|
}
|
|
if (hostId !== firstHostId) {
|
|
isMixed = true
|
|
break
|
|
}
|
|
}
|
|
if (!isMixed) {
|
|
return undefined
|
|
}
|
|
const labelsByIdentity = new Map<string, string>()
|
|
for (const item of items) {
|
|
labelsByIdentity.set(
|
|
args.getIdentity(item),
|
|
getHostContextLabel(args.getHostId(item), args.sources)
|
|
)
|
|
}
|
|
return labelsByIdentity
|
|
}
|