mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* [comment audit] Align legacy failure-read counts with cached display * [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci] * [comment audit] Condense shared generation, path and preview comments [skip ci] * [comment audit] Condense host, file and Git contract rationale [skip ci] * [comment audit] Tighten Git lifecycle and provider contract comments [skip ci] * [comment audit] Clarify shared host authority and launch isolation [skip ci] * [comment audit] simplify local access mobile and native chat contracts [skip ci] * [comment audit] simplify native chat presentation and state contracts [skip ci] * [comment audit] simplify native runtime and provider contracts [skip ci] * [comment audit] simplify migration and orchestration contracts [skip ci] * [comment audit] simplify plugin trust and process capture rationale [skip ci] * [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci] * [comment audit] clarify remote recovery and launch wire rationale [skip ci] * [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci] * [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci] * [comment audit] clarify sidebar, skill and SSH invariants [skip ci] * [comment audit] condense structured chat delivery and timing rationale [skip ci] * [comment audit] condense terminal replay and telemetry invariants [skip ci] * [comment audit] clarify launch permission and terminal delivery rationale [skip ci] * [comment audit] clarify workspace ownership and layout contracts [skip ci] * [comment audit] wait for fitted and painted SSH terminal links * [comment audit] clarify layout persistence and session ownership [skip ci] * [comment audit] clarify host routing and workspace lifecycle contracts [skip ci] * [comment audit] clarify startup persistence and app lifetime comments [skip ci] * [comment audit] clarify recovery and activity boundaries [skip ci] * [comment audit] condense activity and artifact invariants [skip ci] * [comment audit] condense automation authority contracts [skip ci] * [comment audit] simplify automation scheduling and history contracts [skip ci] * [comment audit] clarify browser chrome and annotation lifecycles [skip ci] * [comment audit] clarify browser routing and retained guest ownership [skip ci] * [comment audit] clarify browser recovery and document ownership comments [skip ci] * [comment audit] restore hosted runners required by native IME fixtures * [comment audit] shorten preview, palette and dashboard invariants [skip ci] * [comment audit] clarify terminal preview, dashboard and retention comments [skip ci] * [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci] * [comment audit] shorten rich-text and combined-diff ownership comments [skip ci] * [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci] * [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci] * [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci] * [comment audit] clarify rich Markdown source and selection safeguards [skip ci] * [comment audit] clarify editor loading and emulator stream lifetimes [skip ci] * [comment audit] trim emulator and feature tour narration [skip ci] * [comment audit] clarify floating workspace and GitHub cache ownership [skip ci] * [comment audit] trim provider controls and Linear filter explanations [skip ci] * [comment audit] clarify pairing and chat resume lifetimes [skip ci] * [comment audit] clarify chat delivery and transcript lifetimes [skip ci] * [comment audit] clarify session options and host-owned status [skip ci] * [comment audit] clarify draft durability and live-status precedence [skip ci] * [comment audit] Trim chat send and model ownership explanations [skip ci] * [comment audit] Shorten transcript ownership and send recovery comments [skip ci] * [comment audit] Simplify native chat composer and transcript hook comments [skip ci] * [comment audit] Shorten native chat scroll and structured session explanations [skip ci] * [comment audit] Trim workspace picker and onboarding commentary [skip ci] * [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci] * [comment audit] Trim sidebar and session history explanations [skip ci] * [comment audit] Condense session resume and review checks comments [skip ci] * [comment audit] Shorten checks state and file explorer refresh comments [skip ci] * [comment audit] Trim plugin bridge and source-control action rationale [skip ci] * [comment audit] Trim source-control listing and review rationale [skip ci] * [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci] * [comment audit] Condense Settings account and appearance rationale [skip ci] * [comment audit] Trim pairing and repository Settings explanations [skip ci] * [comment audit] Condense Settings search SSH and terminal safeguards [skip ci] * [comment audit] Shorten Settings voice and provider ownership comments [skip ci] * [comment audit] Condense Settings draft persistence and host-state rationale [skip ci] * [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci] * [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci] * [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci] * [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci] * [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci] * [comment audit] Prune focus-read baseline after comment cleanup * [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci] * [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci] * [comment audit] Clarify viewport geometry and skill update safeguards [skip ci] * [comment audit] Tighten skill inventory, usage and host status comments [skip ci] * [comment audit] Clarify usage ownership and resource kill safeguards [skip ci] * [comment audit] Tighten tab selection, rename and search contracts [skip ci] * [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci] * [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci] * [comment audit] Tighten provider loading and terminal recovery comments [skip ci] * [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci] * [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci] * [comment audit] Tighten PTY startup and replay explanations [skip ci] * [comment audit] Clarify PTY identity and delivery ownership [skip ci] * [comment audit] Shorten remote reconnect and input replay rationale [skip ci] * [comment audit] Condense terminal parking clipboard and file link comments [skip ci] * [comment audit] Clarify IME ownership and trim trace history [skip ci] * [comment audit] Shorten pane recovery and parked watcher explanations [skip ci] * [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci] * [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci] * [comment audit] Trim renderer lifecycle and menu rationale [skip ci] * [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci] * [comment audit] Clarify renderer hook evidence and source ownership [skip ci] * [comment audit] Tighten IPC ownership and reconnect explanations [skip ci] * [comment audit] Trim renderer focus, paste and attention comments [skip ci] * [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci] * [comment audit] Trim agent launch, resume and ownership commentary [skip ci] * [comment audit] Shorten account, browser and editor comments [skip ci] * [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci] * [comment audit] Condense launch recovery and editor tokenization comments [skip ci] * [comment audit] Condense workspace selection and terminal pane rationale [skip ci] * [comment audit] Condense terminal fitting and rendering recovery comments [skip ci] * [comment audit] Condense terminal input and host routing explanations [skip ci] * [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci] * [comment audit] Condense structured launch and terminal link explanations [skip ci] * [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci] * [comment audit] Condense session persistence and workspace activation rationale [skip ci] * [comment audit] Condense workspace routing and browser ownership rationale [skip ci] * [comment audit] Condense mirror recovery and layout authority rationale [skip ci] * [comment audit] Condense runtime transport and file safety rationale [skip ci] * [comment audit] Condense runtime graph and structured session rationale [skip ci] * [comment audit] Condense browser creation and remote tab recovery rationale [skip ci] * [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci] * [comment audit] Condense frame, startup and provider cache rationale [skip ci] * [comment audit] Condense host twin and agent lifecycle rationale [skip ci] * [comment audit] Condense browser authority and editor cleanup rationale [skip ci] * [comment audit] Condense editor buffer identity and review cache rationale [skip ci] * [comment audit] Condense provider lifetime and repository ownership rationale [skip ci] * [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci] * [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci] * [comment audit] Condense cleanup ownership and listing admission rationale [skip ci] * [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci] * [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci] * [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci] * [comment audit] Condense cloud rollout and assignment safety rationale [skip ci] * [comment audit] Condense relay session and database recovery rationale [skip ci] * [comment audit] Condense relay placement and protocol compatibility rationale [skip ci] * [comment audit] Condense build and packaging constraints [skip ci] * [comment audit] Condense CI gates and validation isolation rationale [skip ci] * [comment audit] Condense catalog identity and browser validation rationale [skip ci] * [comment audit] Condense mobile browser and frame-budget rationale [skip ci] * [comment audit] Shorten mobile route and browser test explanations [skip ci] * [comment audit] Condense editor and session seam test rationale [skip ci] * [comment audit] Trim session and terminal closure history [skip ci] * [comment audit] Shorten terminal browser and census explanations [skip ci] * [comment audit] Preserve root-only E2E diagnostic baseline * [comment audit] Condense mobile seam and HTML preview test rationale [skip ci] * [comment audit] Condense preview instrumentation and render harness rationale [skip ci] * [comment audit] Condense bundle contracts and native-build constraints [skip ci] * [comment audit] Shorten native packaging and CI routing rationale [skip ci] * [comment audit] Condense release tooling and benchmark constraints [skip ci] * [comment audit] Condense packaging and platform gate rationale [skip ci] * [comment audit] Shorten mobile route and history rationale [skip ci] * [comment audit] Condense mobile browser and component rationale [skip ci] * [comment audit] Shorten drawer and workspace-form rationale [skip ci] * [comment audit] Trim mobile review and editor-document explanations [skip ci] * [comment audit] Condense editor lifecycle and Markdown explanations [skip ci] * [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci] * [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci] * [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci] * [comment audit] Condense bridge stream, native request and audio rationale [skip ci] * [comment audit] Reduce bridge schema and page-client commentary [skip ci] * [comment audit] Tighten mobile page bridge contracts [skip ci] * [comment audit] Clarify mobile cache and session constraints [skip ci] * [comment audit] Condense mobile route and lifecycle rationale [skip ci] * [comment audit] Tighten mobile shell hook timing rationale [skip ci] * [comment audit] Clarify route handoff and notification compatibility [skip ci] * [comment audit] Clarify push registration and dictation cleanup constraints [skip ci] * [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci] * [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci] * [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci] * [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci] * [comment audit] Tighten stream replay and queued session ownership rationale [skip ci] * [comment audit] Condense mobile composer and reply-schema constraints [skip ci] * [comment audit] Clarify mobile transcript recovery and session ownership [skip ci] * [comment audit] Clarify mobile queue and source-control compatibility [skip ci] * [comment audit] Condense storage and task launch constraints [skip ci] * [comment audit] Clarify task schemas and terminal document ownership [skip ci] * [comment audit] Tighten terminal input and viewport rationale [skip ci] * [comment audit] Shorten terminal and RPC recorder contracts [skip ci] * [comment audit] Condense recorder engine and connection contracts [skip ci] * [comment audit] Shorten host credential and relay recovery comments [skip ci] * [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci] * [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci] * [comment audit] Condense catalog and relay entry contracts [skip ci] * [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci] * [comment audit] Condense WSL relay and E2E regression rationale [skip ci] * [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci] * [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci] * [comment audit] Shorten layout oracle and regression explanations [skip ci] * [comment audit] Condense paired recovery and ownership regression contracts [skip ci] * [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci] * [comment audit] Condense terminal rendering and IME regression rationale [skip ci] * [comment audit] Shorten terminal recovery and benchmark contracts [skip ci] * [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci] * [comment audit] Condense workflow routing and release rationale [skip ci] * [comment audit] Condense mobile security and installer contracts [skip ci] * [comment audit] Shorten native rationale and stylesheet workarounds [skip ci] * [comment audit] Shorten Windows visibility probe rationale [skip ci] * [comment audit] Condense embedded release and signing rationale [skip ci] * [comment audit] Retain current main test behavior in final reconciliation [skip ci] * [comment audit] Complete final comment review across current source inventory * [comment audit] Handle consolidated audit reports before final CI * [comment audit] Preserve credential storage census detail [skip ci] * [comment audit] Scale broad changed E2E selections with verified timing shards * [comment audit] Align E2E assertions with browser close and settled session metadata * [comment audit] Match existing E2Es to current resource and windowing contracts * [comment audit] Observe the shared server outage notice in reconnect coverage * [comment audit] Make existing renderer probes measure settled tab and PTY work * [comment audit] Align palette E2E selector with task search * [comment audit] Refresh remaining palette search selectors * [comment audit] Keep selector correction within selected audit specs * [comment audit] Await emoji fixture settings inside renderer evaluation * [comment audit] Cancel fixture fit retries between terminal tests --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
343 lines
11 KiB
TypeScript
343 lines
11 KiB
TypeScript
import { execFile as execFileCb } from 'node:child_process'
|
||
import { readFile } from 'node:fs/promises'
|
||
import { promisify } from 'node:util'
|
||
import {
|
||
captureDarwinProcessTable,
|
||
resetDarwinProcessTableCaptureForTests
|
||
} from './darwin-process-table-capture'
|
||
import {
|
||
PROCESS_TABLE_SNAPSHOT_MAX_STALENESS_MS,
|
||
PS_ARGS,
|
||
PS_MAX_BUFFER_BYTES,
|
||
ProcessTableCaptureError,
|
||
SHELL_FOREGROUND_PS_ARGS,
|
||
parseProcessTableRows,
|
||
parseShellForegroundRows,
|
||
parseStrictProcessTableRows,
|
||
type ProcessTableRow
|
||
} from './process-table-snapshot'
|
||
|
||
export { PROCESS_TABLE_SNAPSHOT_MAX_STALENESS_MS, PS_ARGS, PS_MAX_BUFFER_BYTES }
|
||
|
||
const execFile = promisify(execFileCb)
|
||
|
||
// command= costs 1.15s for 1,948 idle processes, 1.3–6s at load 27; 3s failed 6/20 captures.
|
||
// Allow 15s for identity reads on busy hosts while bounding wedged ps.
|
||
export const PS_TIMEOUT_MS = 15_000
|
||
|
||
type Snapshot<T> = { value: T; capturedAtMs: number; completedAtMs: number }
|
||
|
||
type ProcessTableSnapshotReaderDeps<T> = {
|
||
runPs: () => Promise<T>
|
||
now: () => number
|
||
ttlMs?: number
|
||
}
|
||
|
||
/** Build a process-table reader that coalesces concurrent and recent captures. */
|
||
export function createProcessTableSnapshotReader<T = string>(
|
||
deps: ProcessTableSnapshotReaderDeps<T>
|
||
): {
|
||
getSnapshot: () => Promise<T>
|
||
getSnapshotWithAge: () => Promise<{ value: T; capturedAgeMs: number }>
|
||
getFreshSnapshot: () => Promise<T>
|
||
reset: () => void
|
||
} {
|
||
const ttlMs = deps.ttlMs ?? PROCESS_TABLE_SNAPSHOT_MAX_STALENESS_MS
|
||
let cached: Snapshot<T> | null = null
|
||
let inFlight: Promise<T> | null = null
|
||
let sequence = 0
|
||
let freshQueued: { promise: Promise<T>; startSequence: number | null } | null = null
|
||
|
||
async function runSnapshot(): Promise<T> {
|
||
// Stamp evidence age at capture start, TTL at completion: slow captures must remain old yet still coalesce
|
||
// instead of spawning one whole-machine ps per caller on loaded hosts.
|
||
const capturedAtMs = deps.now()
|
||
const promise = deps.runPs()
|
||
inFlight = promise
|
||
try {
|
||
const value = await promise
|
||
cached = { value, capturedAtMs, completedAtMs: deps.now() }
|
||
return value
|
||
} finally {
|
||
if (inFlight === promise) {
|
||
inFlight = null
|
||
}
|
||
}
|
||
}
|
||
|
||
async function getSnapshot(): Promise<T> {
|
||
if (cached && deps.now() - cached.completedAtMs < ttlMs) {
|
||
return cached.value
|
||
}
|
||
if (inFlight) {
|
||
return inFlight
|
||
}
|
||
if (freshQueued) {
|
||
return freshQueued.promise
|
||
}
|
||
return runSnapshot()
|
||
}
|
||
|
||
async function getSnapshotWithAge(): Promise<{ value: T; capturedAgeMs: number }> {
|
||
const value = await getSnapshot()
|
||
const capturedAtMs = cached?.value === value ? cached.capturedAtMs : deps.now()
|
||
return { value, capturedAgeMs: Math.max(0, deps.now() - capturedAtMs) }
|
||
}
|
||
|
||
function getFreshSnapshot(): Promise<T> {
|
||
const requestSequence = ++sequence
|
||
if (freshQueued?.startSequence === null) {
|
||
return freshQueued.promise
|
||
}
|
||
const priorFresh = freshQueued?.promise ?? null
|
||
const priorScan = inFlight
|
||
const entry: { promise: Promise<T>; startSequence: number | null } = {
|
||
promise: Promise.resolve(undefined as never),
|
||
startSequence: null
|
||
}
|
||
entry.promise = Promise.resolve().then(async () => {
|
||
for (const prior of [priorFresh, priorScan]) {
|
||
if (!prior) {
|
||
continue
|
||
}
|
||
try {
|
||
await prior
|
||
} catch {
|
||
// The post-boundary scan below owns the confirmation result.
|
||
}
|
||
}
|
||
entry.startSequence = ++sequence
|
||
if (entry.startSequence <= requestSequence) {
|
||
throw new Error('fresh process snapshot did not start after request')
|
||
}
|
||
return runSnapshot()
|
||
})
|
||
freshQueued = entry
|
||
const clearQueued = (): void => {
|
||
if (freshQueued === entry) {
|
||
freshQueued = null
|
||
}
|
||
}
|
||
void entry.promise.then(clearQueued, clearQueued)
|
||
return entry.promise
|
||
}
|
||
|
||
return {
|
||
getSnapshot,
|
||
getSnapshotWithAge,
|
||
getFreshSnapshot,
|
||
reset: () => {
|
||
cached = null
|
||
inFlight = null
|
||
sequence = 0
|
||
freshQueued = null
|
||
}
|
||
}
|
||
}
|
||
|
||
type ProcessTableCapture = {
|
||
lenient: () => ProcessTableRow[]
|
||
strict: () => ProcessTableRow[]
|
||
}
|
||
|
||
function applyProcessStartTimes(
|
||
rows: ProcessTableRow[],
|
||
startTimesByPid: ReadonlyMap<number, string> | undefined,
|
||
dropUnstableStartTimes = false
|
||
): ProcessTableRow[] {
|
||
if ((!startTimesByPid || startTimesByPid.size === 0) && !dropUnstableStartTimes) {
|
||
return rows
|
||
}
|
||
return rows.map((row) => {
|
||
const startTime = startTimesByPid?.get(row.pid)
|
||
if (startTime) {
|
||
return { ...row, startTime }
|
||
}
|
||
if (dropUnstableStartTimes && row.startTime !== undefined) {
|
||
const { startTime: _unstable, ...withoutStartTime } = row
|
||
return withoutStartTime
|
||
}
|
||
return row
|
||
})
|
||
}
|
||
|
||
function createProcessTableCapture(
|
||
stdout: string,
|
||
startTimesByPid?: ReadonlyMap<number, string>,
|
||
dropUnstableStartTimes = false
|
||
): ProcessTableCapture {
|
||
let lenientRows: ProcessTableRow[] | null = null
|
||
let strictResult: { rows: ProcessTableRow[] } | { error: unknown } | null = null
|
||
return {
|
||
lenient: () =>
|
||
(lenientRows ??= applyProcessStartTimes(
|
||
parseProcessTableRows(stdout),
|
||
startTimesByPid,
|
||
dropUnstableStartTimes
|
||
)),
|
||
strict: () => {
|
||
if (strictResult === null) {
|
||
try {
|
||
strictResult = {
|
||
rows: applyProcessStartTimes(
|
||
parseStrictProcessTableRows(stdout),
|
||
startTimesByPid,
|
||
dropUnstableStartTimes
|
||
)
|
||
}
|
||
} catch (error) {
|
||
strictResult = { error }
|
||
}
|
||
}
|
||
if ('error' in strictResult) {
|
||
throw strictResult.error
|
||
}
|
||
return strictResult.rows
|
||
}
|
||
}
|
||
}
|
||
|
||
/** Reject captures truncated at the subprocess ceiling or containing no rows. */
|
||
function assertWholeCapture(stdout: string): string {
|
||
if (Buffer.byteLength(stdout, 'utf-8') >= PS_MAX_BUFFER_BYTES) {
|
||
throw new ProcessTableCaptureError('capture_truncated')
|
||
}
|
||
if (!/\S/.test(stdout)) {
|
||
throw new ProcessTableCaptureError('empty_capture')
|
||
}
|
||
return stdout
|
||
}
|
||
|
||
/** Field 22 (`starttime`) of `/proc/<pid>/stat`, read past the parenthesised comm. */
|
||
export function parseLinuxProcStatStartTime(stat: string): string | null {
|
||
const closingParen = stat.lastIndexOf(')')
|
||
if (closingParen === -1) {
|
||
return null
|
||
}
|
||
const tail = stat
|
||
.slice(closingParen + 1)
|
||
.trim()
|
||
.split(/\s+/)
|
||
return tail[19] || null
|
||
}
|
||
|
||
/** Read Linux's stable PID start-time ticks without spawning another process. */
|
||
async function readLinuxProcessStartTimes(
|
||
rows: readonly ProcessTableRow[]
|
||
): Promise<ReadonlyMap<number, string>> {
|
||
const candidates = rows.filter((row) => row.tty !== undefined && row.tty !== '?')
|
||
const starts = await Promise.all(
|
||
candidates.map(async (row) => {
|
||
try {
|
||
const startTime = parseLinuxProcStatStartTime(
|
||
await readFile(`/proc/${row.pid}/stat`, 'utf8')
|
||
)
|
||
return startTime ? ([row.pid, startTime] as const) : null
|
||
} catch {
|
||
return null
|
||
}
|
||
})
|
||
)
|
||
const result = new Map<number, string>()
|
||
for (const entry of starts) {
|
||
if (entry) {
|
||
result.set(entry[0], entry[1])
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
async function captureProcessTable(args: readonly string[], timeout = PS_TIMEOUT_MS) {
|
||
let stdout: string
|
||
try {
|
||
;({ stdout } = await execFile('ps', [...args], {
|
||
encoding: 'utf-8',
|
||
timeout,
|
||
maxBuffer: PS_MAX_BUFFER_BYTES
|
||
}))
|
||
} catch (error) {
|
||
// A ceiling hit is truncation, not absence: name it in the domain vocabulary.
|
||
if ((error as { code?: unknown } | null)?.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
|
||
throw new ProcessTableCaptureError('capture_truncated')
|
||
}
|
||
throw error
|
||
}
|
||
return assertWholeCapture(stdout)
|
||
}
|
||
|
||
const processTableReader = createProcessTableSnapshotReader<ProcessTableCapture>({
|
||
runPs: async () => {
|
||
const stdout =
|
||
process.platform === 'darwin'
|
||
? await captureDarwinProcessTable(captureProcessTable, assertWholeCapture, PS_TIMEOUT_MS)
|
||
: await captureProcessTable(PS_ARGS)
|
||
if (process.platform !== 'linux') {
|
||
return createProcessTableCapture(stdout)
|
||
}
|
||
const startTimesByPid = await readLinuxProcessStartTimes(parseProcessTableRows(stdout))
|
||
return createProcessTableCapture(stdout, startTimesByPid, true)
|
||
},
|
||
now: () => Date.now()
|
||
})
|
||
|
||
// Separate shell reader avoids macOS terminal-name costs and waiting on full captures it cannot use.
|
||
const shellForegroundReader = createProcessTableSnapshotReader<ProcessTableRow[]>({
|
||
runPs: async () => parseShellForegroundRows(await captureProcessTable(SHELL_FOREGROUND_PS_ARGS)),
|
||
now: () => Date.now()
|
||
})
|
||
|
||
export async function getFreshShellForegroundSnapshot(): Promise<ProcessTableRow[]> {
|
||
return process.platform === 'darwin'
|
||
? shellForegroundReader.getFreshSnapshot()
|
||
: getFreshProcessTableSnapshot()
|
||
}
|
||
|
||
export async function getProcessTableSnapshot(): Promise<ProcessTableRow[]> {
|
||
return (await processTableReader.getSnapshot()).lenient()
|
||
}
|
||
|
||
export async function getFreshProcessTableSnapshot(): Promise<ProcessTableRow[]> {
|
||
return (await processTableReader.getFreshSnapshot()).lenient()
|
||
}
|
||
|
||
export async function getStrictProcessTableSnapshot(): Promise<ProcessTableRow[]> {
|
||
return (await processTableReader.getSnapshot()).strict()
|
||
}
|
||
|
||
/** Evidence needs current observations, unlike identity probes that wait up to 15s. Idle command= costs ~1.15s/1,948 processes;
|
||
* 2s freshness ceiling minus up to 500ms cached age and transit leaves 1.2s. Slower captures fail age gates anyway
|
||
* (measured 4–18.6s at load 46, 2.5–9s on a 2,002-process laptop), so return unverifiable promptly. */
|
||
export const PROCESS_TABLE_EVIDENCE_BUDGET_MS = 1_200
|
||
|
||
/** Bound only this wait; leave shared identity captures running to fill the cache instead of spawning another expensive ps. */
|
||
export async function withEvidenceBudget<T>(pending: Promise<T>): Promise<T> {
|
||
let timer: ReturnType<typeof setTimeout> | undefined
|
||
try {
|
||
return await Promise.race([
|
||
pending,
|
||
new Promise<never>((_resolve, reject) => {
|
||
timer = setTimeout(
|
||
() => reject(new ProcessTableCaptureError('capture_over_budget')),
|
||
PROCESS_TABLE_EVIDENCE_BUDGET_MS
|
||
)
|
||
})
|
||
])
|
||
} finally {
|
||
clearTimeout(timer)
|
||
}
|
||
}
|
||
|
||
export async function getStrictProcessTableSnapshotWithAge(): Promise<{
|
||
rows: ProcessTableRow[]
|
||
capturedAgeMs: number
|
||
}> {
|
||
const snapshot = await withEvidenceBudget(processTableReader.getSnapshotWithAge())
|
||
return { rows: snapshot.value.strict(), capturedAgeMs: snapshot.capturedAgeMs }
|
||
}
|
||
|
||
export function resetProcessTableSnapshotForTests(): void {
|
||
processTableReader.reset()
|
||
shellForegroundReader.reset()
|
||
resetDarwinProcessTableCaptureForTests()
|
||
}
|