mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 11:07:42 +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>
388 lines
15 KiB
TypeScript
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()
|
|
}
|