mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
import {
|
||
recognizeAgentProcessFromCommandLine,
|
||
type RecognizedAgentProcess
|
||
} from './agent-status/process/agent-process-recognition'
|
||
|
||
export type ForegroundProcessCandidate = {
|
||
pid: number
|
||
ppid: number
|
||
command: string
|
||
depth: number
|
||
stat?: string
|
||
}
|
||
|
||
export type SelectedForegroundProcess = {
|
||
candidate: ForegroundProcessCandidate
|
||
recognized: RecognizedAgentProcess
|
||
}
|
||
|
||
/** Prevent vendor helpers from stealing an outer agent’s identity within one process lineage. */
|
||
export function selectForegroundProcessCandidate(
|
||
candidates: readonly ForegroundProcessCandidate[],
|
||
ancestryCandidates: readonly ForegroundProcessCandidate[] = candidates
|
||
): SelectedForegroundProcess | null {
|
||
const recognized = candidates.flatMap((candidate) => {
|
||
const agent = recognizeAgentProcessFromCommandLine(candidate.command)
|
||
return agent ? [{ candidate, recognized: agent }] : []
|
||
})
|
||
if (recognized.length === 0) {
|
||
return null
|
||
}
|
||
|
||
const agentNames = new Set(recognized.map(({ recognized: agent }) => agent.agent))
|
||
if (agentNames.size > 1) {
|
||
const candidatesByPid = new Map(
|
||
ancestryCandidates.map((candidate) => [candidate.pid, candidate])
|
||
)
|
||
const outer = [...recognized].sort(
|
||
(left, right) => left.candidate.depth - right.candidate.depth
|
||
)[0]
|
||
if (!outer) {
|
||
return null
|
||
}
|
||
const descendsFromOuter = makeAncestorReachabilityTest(outer.candidate, candidatesByPid)
|
||
if (!recognized.every((entry) => descendsFromOuter(entry.candidate))) {
|
||
// Distinct sibling agents do not provide a trustworthy identity.
|
||
return null
|
||
}
|
||
return outer
|
||
}
|
||
|
||
return recognized.reduce((best, current) =>
|
||
foregroundCandidateScore(current.candidate) > foregroundCandidateScore(best.candidate)
|
||
? current
|
||
: best
|
||
)
|
||
}
|
||
|
||
function foregroundCandidateScore(candidate: ForegroundProcessCandidate): number {
|
||
return (candidate.stat?.includes('+') ? 10_000 : 0) + candidate.depth
|
||
}
|
||
|
||
/** Memoize ancestor reachability for one ancestor and process-table snapshot only; rebuild for every fresh capture. */
|
||
function makeAncestorReachabilityTest(
|
||
ancestor: ForegroundProcessCandidate,
|
||
candidatesByPid: ReadonlyMap<number, ForegroundProcessCandidate>
|
||
): (descendant: ForegroundProcessCandidate) => boolean {
|
||
const reaches = new Map<number, boolean>()
|
||
return (descendant) => {
|
||
let currentPid = descendant.pid
|
||
// Share the verdict across the walk; visited also terminates reparented/wrapped ppid cycles.
|
||
const visited = new Set<number>()
|
||
let matches = true
|
||
while (currentPid !== ancestor.pid) {
|
||
const cached = reaches.get(currentPid)
|
||
if (cached !== undefined) {
|
||
matches = cached
|
||
break
|
||
}
|
||
if (visited.has(currentPid)) {
|
||
matches = false
|
||
break
|
||
}
|
||
visited.add(currentPid)
|
||
const current = candidatesByPid.get(currentPid)
|
||
if (!current) {
|
||
matches = false
|
||
break
|
||
}
|
||
currentPid = current.ppid
|
||
}
|
||
for (const pid of visited) {
|
||
reaches.set(pid, matches)
|
||
}
|
||
return matches
|
||
}
|
||
}
|