Files
orca/src/main/windows/windows-process-table.ts
T
OrcaWinandOrcaWin 7d6603aa17 [comment audit] Complete repository-wide comment cleanup (#33249)
* [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>
2026-10-25 04:05:46 -07:00

388 lines
15 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { createProcessTableSnapshotReader } from '../../shared/process-table-snapshot-reader'
import { ORCAD_WINDOWS_PROCESS_TREE_FILENAME } from '../../shared/orcad-artifacts'
import { reportWindowsCommandLineRecoveryHealth } from './windows-command-line-recovery-health'
import { readWindowsProcessRowsWithCim } from './windows-process-table-cim-scan'
import { WindowsProcessTableTimeoutError } from './windows-process-table-timeout-error'
/** Shared Windows snapshots: serialized native identity/command-line views, with CIM fallback only when no usable binding exists. Both views request creation times through limited query handles; Memory stays off. See docs/reference/windows-process-enumeration.md. */
/** PID/parent/name identity, with native creation time when available. */
export type WindowsProcessIdentityRow = {
pid: number
ppid: number
name: string
/** Process creation time in Unix milliseconds, when the native snapshot provides it. */
creationTimeMs?: number
}
/** Adds the kernel-supplied command line. Only ask for this if you read it. */
export type WindowsProcessRow = WindowsProcessIdentityRow & {
/** Full command line. Empty when the process denied a query handle. */
command: string
}
type NativeProcessInfo = {
pid: number
ppid: number
name: string
commandLine?: string
creationTimeMs?: number
}
type WindowsProcessTreeModule = {
ProcessDataFlag: {
None: number
CommandLine: number
CreationTime?: number
}
/** Compiled addon capabilities; patched JS enums may sit above an older prebuilt binary. */
supportedProcessDataFlags?: number
getProcessCreationTime?: (pid: number) => number | undefined
getAllProcesses: (
callback: (processes: NativeProcessInfo[] | undefined) => void,
flags?: number
) => void
}
const requireFromMain = createRequire(__filename)
/** `resolve` is optional so a test can inject a bare function for the require alone. */
type NativeRequire = ((specifier: string) => unknown) & {
resolve?: (specifier: string) => string
}
// createRequire bypasses vi.mock; inject actual resolution so relay specifiers are tested.
let requireNative: NativeRequire = requireFromMain
/** Bare relay addon has no queue; nativeReadGate must serialize it to prevent concurrent native snapshots. The npm wrapper adds its own coalescing queue. */
type WindowsProcessTreeAddon = {
getProcessCreationTime?: (pid: number) => number | undefined
getProcessList: (
callback: (processes: NativeProcessInfo[] | undefined) => void,
flags: number
) => void
supportedProcessDataFlags?: number
}
/** Raw addon flags; Memory stays off. CreationTime in this enum requests it but only supportedProcessDataFlags proves binary support. */
const PROCESS_DATA_FLAG = { None: 0, Memory: 1, CommandLine: 2, CreationTime: 4 } as const
/** Staged beside the orcad bundle by orcad-windows-process-tree.mjs. */
const RELAY_ADDON_FILENAME = `./${ORCAD_WINDOWS_PROCESS_TREE_FILENAME}`
/** The import whose absence tells the patched binary from the published prebuilt. */
const FLAGGED_ADDON_IMPORT = 'ReadProcessMemory'
/** Reject staged addons importing ReadProcessMemory: independently deployed old binaries can load yet restore EDR-flagged PEB reads. Match the build inspector without importing its node-gyp/child_process dependencies; fall back to CIM. */
function stagedRelayAddonIsUnpatched(): boolean {
// Missing resolver denotes a test double; production resolves the binary it just loaded.
const addonPath = requireNative.resolve?.(RELAY_ADDON_FILENAME)
if (!addonPath) {
return false
}
try {
return readFileSync(addonPath).includes(FLAGGED_ADDON_IMPORT)
} catch {
return false
}
}
/** Warn once where stderr survives; daemon startup reports capability through daemonLog instead. */
let warnedAboutCimFallback = false
let cachedModule: WindowsProcessTreeModule | null | undefined
let moduleLoader: () => WindowsProcessTreeModule | null = loadWindowsProcessTree
let cimScan: () => Promise<WindowsProcessRow[]> = readWindowsProcessRowsWithCim
/** Present the bare addon through the same shape as the npm package. */
function adaptAddon(addon: WindowsProcessTreeAddon): WindowsProcessTreeModule {
return {
ProcessDataFlag: PROCESS_DATA_FLAG,
supportedProcessDataFlags: addon.supportedProcessDataFlags,
getProcessCreationTime: addon.getProcessCreationTime,
getAllProcesses: (callback, flags) => addon.getProcessList(callback, flags ?? 0)
}
}
/** Load the desktop npm package or staged bare relay addon. Both remain optional across platforms/older deployments; null selects CIM on Windows and remains unavailable elsewhere. */
function loadWindowsProcessTree(): WindowsProcessTreeModule | null {
if (cachedModule !== undefined) {
return cachedModule
}
if (process.platform !== 'win32') {
cachedModule = null
return cachedModule
}
try {
cachedModule = requireNative('@vscode/windows-process-tree') as WindowsProcessTreeModule
return cachedModule
} catch {
// Relays lack the npm package; try their staged addon.
}
try {
const addon = requireNative(RELAY_ADDON_FILENAME) as WindowsProcessTreeAddon
// Reject unusable shapes so an invalid addon can fall back to CIM.
if (typeof addon?.getProcessList !== 'function') {
/* v8 ignore next 2 */
cachedModule = null
return cachedModule
}
if (stagedRelayAddonIsUnpatched()) {
console.warn(
`[windows-process-table] the addon staged beside the relay bundle still imports ` +
`${FLAGGED_ADDON_IMPORT}, so it was built from unpatched source and reads every ` +
'process address space. Refusing it and falling back to the CIM scan; redeploy the ' +
'relay so the staged addon is rebuilt.'
)
cachedModule = null
return cachedModule
}
cachedModule = adaptAddon(addon)
} catch {
cachedModule = null
}
return cachedModule
}
/** Bound a native read: a missing callback otherwise strands every queued caller. */
const WINDOWS_PROCESS_QUERY_TIMEOUT_MS = 3_000
/** Block native re-entry after timeout until that read's own callback drains. One shared gate bounds retained callbacks/workers across both flag sets; monotonic IDs prevent stale callbacks clearing newer wedges. */
const unreturnedReads = new Set<number>()
let readSequence = 0
let nativeReaderEpoch = 0
/** Serialize both flag sets: the npm wrapper coalesces overlapping callers and discards later flags; the bare addon has no queue and can crash on overlap. Deadline rejection lets waiters recheck outstanding work instead of stranding them. */
let nativeReadGate: Promise<unknown> = Promise.resolve()
function resetNativeReaderState(): void {
nativeReaderEpoch += 1
warnedAboutCimFallback = false
unreturnedReads.clear()
// Chain rather than replace: existing waiters must not run beside newly queued reads.
nativeReadGate = nativeReadGate.then(ignoreSettlement, ignoreSettlement)
}
/** A flag set and the row shape it can honestly produce. */
type ProcessRowProjection<Row> = {
flags: (native: WindowsProcessTreeModule) => number
fromNative: (row: NativeProcessInfo) => Row
/** Only detailed rows run CIM; identity projects that same expensive scan instead of spawning another. */
cimFallback?: () => Promise<Row[]>
}
function toIdentityRow(row: {
pid: number
ppid: number
name: string
creationTimeMs?: number
}): WindowsProcessIdentityRow {
return {
pid: row.pid,
ppid: row.ppid,
name: row.name,
...(typeof row.creationTimeMs === 'number' ? { creationTimeMs: row.creationTimeMs } : {})
}
}
/** Request creation time via limited query handles when supported; identity omits command lines and never reads process memory. */
const IDENTITY_PROJECTION: ProcessRowProjection<WindowsProcessIdentityRow> = {
flags: (native) => native.ProcessDataFlag.None | (native.ProcessDataFlag.CreationTime ?? 0),
fromNative: toIdentityRow
}
/** Add kernel command-line queries through limited handles. Omit unused Memory: it requests VM_READ and its working set wraps above 4GB. */
const DETAILED_PROJECTION: ProcessRowProjection<WindowsProcessRow> = {
flags: (native) => IDENTITY_PROJECTION.flags(native) | native.ProcessDataFlag.CommandLine,
fromNative: (row) => ({ ...toIdentityRow(row), command: row.commandLine ?? '' }),
cimFallback: readCimRows
}
function ignoreSettlement(): void {}
function readNativeRows<Row>(projection: ProcessRowProjection<Row>): Promise<Row[]> {
const attempt = nativeReadGate.then(() => readOneSnapshot(projection))
nativeReadGate = attempt.then(ignoreSettlement, ignoreSettlement)
return attempt
}
function readOneSnapshot<Row>(projection: ProcessRowProjection<Row>): Promise<Row[]> {
const native = moduleLoader()
if (!native) {
if (process.platform === 'win32' && projection.cimFallback) {
// Only missing/unusable bindings use CIM; native failure/timeouts must not fork shells at poll cadence.
if (!warnedAboutCimFallback) {
warnedAboutCimFallback = true
console.warn(
'[windows-process-table] no native binding; falling back to a powershell.exe CIM scan at each caller poll. See docs/reference/windows-process-enumeration.md.'
)
}
return projection.cimFallback()
}
// Reject missing evidence; an empty table would falsely prove tree death.
return Promise.reject(new Error('windows process table unavailable'))
}
if (unreturnedReads.size > 0) {
return Promise.reject(
new WindowsProcessTableTimeoutError(
'windows process table is wedged: an earlier read has not returned'
)
)
}
const readId = ++readSequence
const readerEpoch = nativeReaderEpoch
const flags = projection.flags(native)
return new Promise((resolve, reject) => {
// Synchronous native throws must clear the deadline or it could wedge a recovered reader.
let deadline: ReturnType<typeof setTimeout> | undefined
try {
deadline = setTimeout(() => {
// Test resets invalidate deadlines owned by the prior injected reader.
if (readerEpoch === nativeReaderEpoch) {
unreturnedReads.add(readId)
}
reject(new WindowsProcessTableTimeoutError('windows process table timed out'))
}, WINDOWS_PROCESS_QUERY_TIMEOUT_MS)
deadline.unref?.()
native.getAllProcesses((processes) => {
clearTimeout(deadline)
// Only this read's callback proves its outstanding native work drained.
unreturnedReads.delete(readId)
if (!processes) {
reject(new Error('windows process table returned no snapshot'))
return
}
// Missing self means unreadable/truncated data, never proof that no processes run.
if (!processes.some((row) => row.pid === process.pid)) {
reject(new Error('windows process table is unreadable'))
return
}
// Only meaningful when a command line was actually asked for.
if ((flags & native.ProcessDataFlag.CommandLine) !== 0) {
reportWindowsCommandLineRecoveryHealth(processes)
}
resolve(processes.map(projection.fromNative))
}, flags)
} catch (error) {
clearTimeout(deadline)
reject(error instanceof Error ? error : new Error(String(error)))
}
})
}
/** Reject CIM tables missing self: truncated/filtered results cannot prove process death. */
async function readCimRows(): Promise<WindowsProcessRow[]> {
const rows = await cimScan()
if (!rows.some((row) => row.pid === process.pid)) {
throw new Error('windows process table is unreadable')
}
return rows
}
// TTL/single-flight caches collapse teardown bursts into at most one scan per flag set.
const identityReader = createProcessTableSnapshotReader<WindowsProcessIdentityRow[]>({
runPs: () => readNativeRows(IDENTITY_PROJECTION),
now: () => Date.now()
})
const detailedReader = createProcessTableSnapshotReader<WindowsProcessRow[]>({
runPs: () => readNativeRows(DETAILED_PROJECTION),
now: () => Date.now()
})
/** Without a binding, project identity from the shared CIM scan and strip command lines to preserve the row contract. */
async function readIdentityRows(fresh: boolean): Promise<WindowsProcessIdentityRow[]> {
if (moduleLoader() === null) {
const rows = await (fresh ? detailedReader.getFreshSnapshot() : detailedReader.getSnapshot())
return rows.map(toIdentityRow)
}
return fresh ? identityReader.getFreshSnapshot() : identityReader.getSnapshot()
}
/** Cached command-line snapshot, refreshed on the shared TTL. */
export function readWindowsProcessTable(): Promise<WindowsProcessRow[]> {
return detailedReader.getSnapshot()
}
/** Fresh teardown evidence must start after this call; cached rows may predate exit. */
export function readWindowsProcessTableFresh(): Promise<WindowsProcessRow[]> {
return detailedReader.getFreshSnapshot()
}
/** Cached identity; prefer it when no command line is needed. */
export function readWindowsProcessIdentityTable(): Promise<WindowsProcessIdentityRow[]> {
return readIdentityRows(false)
}
/** The identity snapshot, from a scan that starts after this call. */
export function readWindowsProcessIdentityTableFresh(): Promise<WindowsProcessIdentityRow[]> {
return readIdentityRows(true)
}
/** Whether the native table can be read at all on this host. */
export function isWindowsProcessTableAvailable(): boolean {
return moduleLoader() !== null
}
/** PID-reuse protection requires the binary's creation-time capability: pnpm can patch the JS enum while retaining a prebuilt that ignores flag4. */
export function isWindowsProcessStartTimeAvailable(): boolean {
const native = moduleLoader()
return (
native !== null &&
((native.supportedProcessDataFlags ?? 0) & PROCESS_DATA_FLAG.CreationTime) !== 0
)
}
/** Fresh identity for synchronous profile admission; opens one PID without taking a table snapshot. */
export function readWindowsProcessCreationTime(pid: number): number | null {
if (process.platform !== 'win32' || !Number.isSafeInteger(pid) || pid <= 0 || pid > 0xffffffff) {
return null
}
try {
const value = moduleLoader()?.getProcessCreationTime?.(pid)
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : null
} catch {
return null
}
}
function resetSnapshotReaders(): void {
identityReader.reset()
detailedReader.reset()
}
/** Inject the native loader: createRequire bypasses vi.mock and non-Windows installs may lack the module. */
export function __setWindowsProcessTreeLoaderForTests(
loader?: () => WindowsProcessTreeModule | null
): void {
moduleLoader = loader ?? loadWindowsProcessTree
cachedModule = undefined
resetNativeReaderState()
resetSnapshotReaders()
}
/** Inject resolution; attach resolve to exercise staged binary inspection. */
export function __setWindowsProcessTreeRequireForTests(resolve?: NativeRequire): void {
requireNative = resolve ?? requireFromMain
moduleLoader = loadWindowsProcessTree
cachedModule = undefined
resetNativeReaderState()
resetSnapshotReaders()
}
/** Inject CIM to avoid spawning PowerShell in tests. */
export function __setWindowsProcessTableCimScanForTests(
scan?: () => Promise<WindowsProcessRow[]>
): void {
cimScan = scan ?? readWindowsProcessRowsWithCim
resetSnapshotReaders()
}
/** Test-only: drop the shared snapshots so suites cannot serve each other's rows. */
export function resetWindowsProcessTableForTests(): void {
resetSnapshotReaders()
cachedModule = undefined
resetNativeReaderState()
}